Merge "Expose ConnectivitySettingsManager as module-lib API" into sc-dev
diff --git a/Android.bp b/Android.bp
index 6a47db1..33b66b6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1461,7 +1461,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 a3e05dc..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,7 @@
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 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>>);
}
public interface BatchResultCallback<KeyType, ValueType> {
@@ -180,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...);
@@ -207,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();
}
@@ -264,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 {
@@ -294,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();
@@ -321,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();
}
@@ -404,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();
@@ -420,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 ce4aad1..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}.
@@ -113,7 +117,9 @@
* no-op call.
*
* @param request the schema to set or update the AppSearch database to.
- * @param executor Executor on which to invoke the callback.
+ * @param workExecutor Executor on which to schedule heavy client-side background work such as
+ * transforming documents.
+ * @param callbackExecutor Executor on which to invoke the callback.
* @param callback Callback to receive errors resulting from setting the schema. If the
* operation succeeds, the callback will be invoked with {@code null}.
*/
@@ -121,10 +127,12 @@
// exposed.
public void setSchema(
@NonNull SetSchemaRequest request,
- @NonNull @CallbackExecutor Executor executor,
+ @NonNull Executor workExecutor,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
Objects.requireNonNull(request);
- Objects.requireNonNull(executor);
+ Objects.requireNonNull(workExecutor);
+ Objects.requireNonNull(callbackExecutor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
List<Bundle> schemaBundles = new ArrayList<>(request.getSchemas().size());
@@ -141,24 +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) {
- executor.execute(() -> {
+ callbackExecutor.execute(() -> {
if (result.isSuccess()) {
- callback.accept(
- // TODO(b/177266929) implement Migration in platform.
- 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);
}
@@ -168,6 +217,8 @@
mIsMutated = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (Throwable t) {
+ callback.accept(AppSearchResult.throwableToFailedResult(t));
}
}
@@ -332,8 +383,7 @@
// Translate successful results
for (Map.Entry<String, Bundle> bundleEntry :
- (Set<Map.Entry<String, Bundle>>)
- result.getSuccesses().entrySet()) {
+ ((Map<String, Bundle>) result.getSuccesses()).entrySet()) {
GenericDocument document;
try {
document = new GenericDocument(bundleEntry.getValue());
@@ -352,8 +402,8 @@
// Translate failed results
for (Map.Entry<String, AppSearchResult<Bundle>> bundleEntry :
- (Set<Map.Entry<String, AppSearchResult<Bundle>>>)
- result.getFailures().entrySet()) {
+ ((Map<String, AppSearchResult<Bundle>>)
+ result.getFailures()).entrySet()) {
documentResultBuilder.setFailure(
bundleEntry.getKey(),
bundleEntry.getValue().getResultCode(),
@@ -610,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/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
index bc30641..f0de496 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
@@ -89,7 +89,7 @@
@NonNull
public ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request) {
SettableFuture<AppSearchResult<SetSchemaResponse>> future = SettableFuture.create();
- mAppSearchSession.setSchema(request, mExecutor, future::set);
+ mAppSearchSession.setSchema(request, mExecutor, mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
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/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index 80698f7..bebf019 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -69,10 +69,12 @@
method public boolean advance(@NonNull android.media.MediaParser.SeekableInputReader) throws java.io.IOException;
method @NonNull public static android.media.MediaParser create(@NonNull android.media.MediaParser.OutputConsumer, @NonNull java.lang.String...);
method @NonNull public static android.media.MediaParser createByName(@NonNull String, @NonNull android.media.MediaParser.OutputConsumer);
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
method @NonNull public String getParserName();
method @NonNull public static java.util.List<java.lang.String> getParserNames(@NonNull android.media.MediaFormat);
method public void release();
method public void seek(@NonNull android.media.MediaParser.SeekPoint);
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
method @NonNull public android.media.MediaParser setParameter(@NonNull String, @NonNull Object);
method public boolean supportsParameter(@NonNull String);
field public static final String PARAMETER_ADTS_ENABLE_CBR_SEEKING = "android.media.mediaparser.adts.enableCbrSeeking";
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 8bdca76..cff422d 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.media.MediaCodec.CryptoInfo;
+import android.media.metrics.LogSessionId;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
@@ -74,6 +75,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
@@ -1066,6 +1068,7 @@
private boolean mReleased;
// MediaMetrics fields.
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
private final boolean mCreatedByName;
private final SparseArray<Format> mTrackFormats;
private String mLastObservedExceptionName;
@@ -1328,6 +1331,7 @@
MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH));
nativeSubmitMetrics(
+ // TODO: mLogSessionId,
mParserName,
mCreatedByName,
String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool),
@@ -1341,6 +1345,15 @@
videoHeight);
}
+ public void setLogSessionId(@NonNull LogSessionId sessionId) {
+ this.mLogSessionId = Objects.requireNonNull(sessionId);
+ }
+
+ @NonNull
+ public LogSessionId getLogSessionId() {
+ return mLogSessionId;
+ }
+
// Private methods.
private MediaParser(
@@ -2184,6 +2197,7 @@
// Native methods.
private native void nativeSubmitMetrics(
+ // TODO: String logSessionId,
String parserName,
boolean createdByName,
String parserPool,
diff --git a/core/api/current.txt b/core/api/current.txt
index af0a739..2e2147f 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);
@@ -20278,6 +20304,7 @@
method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAvailableCommunicationDevices();
method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice();
method public android.media.AudioDeviceInfo[] getDevices(int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public int getEncodedSurroundMode();
method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
method public int getMode();
method public String getParameters(String);
@@ -20300,6 +20327,7 @@
method public static boolean isOffloadedPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
method public boolean isSpeakerphoneOn();
method public boolean isStreamMute(int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean isSurroundFormatEnabled(int);
method public boolean isVolumeFixed();
method @Deprecated public boolean isWiredHeadsetOn();
method public void loadSoundEffects();
@@ -20319,6 +20347,7 @@
method @Deprecated public void setBluetoothA2dpOn(boolean);
method public void setBluetoothScoOn(boolean);
method public boolean setCommunicationDevice(@NonNull android.media.AudioDeviceInfo);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean setEncodedSurroundMode(int);
method public void setMicrophoneMute(boolean);
method public void setMode(int);
method public void setParameters(String);
@@ -20328,6 +20357,7 @@
method @Deprecated public void setStreamMute(int, boolean);
method @Deprecated public void setStreamSolo(int, boolean);
method public void setStreamVolume(int, int, int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean setSurroundFormatEnabled(int, boolean);
method @Deprecated public void setVibrateSetting(int, int);
method @Deprecated public void setWiredHeadsetOn(boolean);
method @Deprecated public boolean shouldVibrate(int);
@@ -20366,6 +20396,10 @@
field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
field public static final int AUDIO_SESSION_ID_GENERATE = 0; // 0x0
+ field public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2; // 0x2
+ field public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0; // 0x0
+ field public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3; // 0x3
+ field public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1; // 0x1
field public static final int ERROR = -1; // 0xffffffff
field public static final int ERROR_DEAD_OBJECT = -6; // 0xfffffffa
field public static final String EXTRA_AUDIO_PLUG_STATE = "android.media.extra.AUDIO_PLUG_STATE";
@@ -20579,6 +20613,7 @@
method public int getChannelConfiguration();
method public int getChannelCount();
method @NonNull public android.media.AudioFormat getFormat();
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
method public android.os.PersistableBundle getMetrics();
method public static int getMinBufferSize(int, int, int);
method public int getNotificationMarkerPosition();
@@ -20601,6 +20636,7 @@
method public void release();
method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method @Deprecated public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
method public int setNotificationMarkerPosition(int);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -20716,6 +20752,7 @@
method public int getChannelCount();
method public int getDualMonoMode();
method @NonNull public android.media.AudioFormat getFormat();
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
method public static float getMaxVolume();
method public android.os.PersistableBundle getMetrics();
method public static int getMinBufferSize(int, int, int);
@@ -20753,6 +20790,7 @@
method public int setAuxEffectSendLevel(@FloatRange(from=0.0) float);
method public int setBufferSizeInFrames(@IntRange(from=0) int);
method public boolean setDualMonoMode(int);
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
method public int setLoopPoints(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int);
method public int setNotificationMarkerPosition(int);
method public void setOffloadDelayPadding(@IntRange(from=0) int, @IntRange(from=0) int);
@@ -21307,7 +21345,7 @@
method @NonNull public String getDiagnosticInfo();
}
- public final class MediaCodec implements android.media.metrics.PlaybackComponent {
+ public final class MediaCodec {
method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, @Nullable android.media.MediaCrypto, int);
method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, int, @Nullable android.media.MediaDescrambler);
method @NonNull public static android.media.MediaCodec createByCodecName(@NonNull String) throws java.io.IOException;
@@ -21333,8 +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 public String getPlaybackId();
+ 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;
@@ -21350,11 +21389,12 @@
method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler);
method public void setOutputSurface(@NonNull android.view.Surface);
method public void setParameters(@Nullable android.os.Bundle);
- method public void setPlaybackId(@NonNull String);
method public void setVideoScalingMode(int);
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
@@ -21482,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);
@@ -21807,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
}
@@ -21964,7 +22010,7 @@
method @NonNull public java.util.List<byte[]> getOfflineLicenseKeySetIds();
method public int getOfflineLicenseState(@NonNull byte[]);
method public int getOpenSessionCount();
- method @Nullable public android.media.metrics.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
+ method @Nullable public android.media.MediaDrm.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
method @NonNull public byte[] getPropertyByteArray(String);
method @NonNull public String getPropertyString(@NonNull String);
method @NonNull public android.media.MediaDrm.ProvisionRequest getProvisionRequest();
@@ -22169,6 +22215,11 @@
method public void onSessionLostState(@NonNull android.media.MediaDrm, @NonNull byte[]);
}
+ public final class MediaDrm.PlaybackComponent {
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
+ }
+
public static final class MediaDrm.ProvisionRequest {
method @NonNull public byte[] getData();
method @NonNull public String getDefaultUrl();
@@ -22201,6 +22252,7 @@
method public long getCachedDuration();
method public android.media.MediaExtractor.CasInfo getCasInfo(int);
method public android.media.DrmInitData getDrmInitData();
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
method public android.os.PersistableBundle getMetrics();
method @Nullable public java.util.Map<java.util.UUID,byte[]> getPsshInfo();
method public boolean getSampleCryptoInfo(@NonNull android.media.MediaCodec.CryptoInfo);
@@ -22222,6 +22274,7 @@
method public void setDataSource(@NonNull android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(@NonNull java.io.FileDescriptor) throws java.io.IOException;
method public void setDataSource(@NonNull java.io.FileDescriptor, long, long) throws java.io.IOException;
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
method public void setMediaCas(@NonNull android.media.MediaCas);
method public void unselectTrack(int);
field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
@@ -22825,6 +22878,7 @@
method public java.util.List<android.media.MicrophoneInfo> getActiveMicrophones() throws java.io.IOException;
method @Nullable public android.media.AudioRecordingConfiguration getActiveRecordingConfiguration();
method public static final int getAudioSourceMax();
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
method public int getMaxAmplitude() throws java.lang.IllegalStateException;
method public android.os.PersistableBundle getMetrics();
method public android.media.AudioDeviceInfo getPreferredDevice();
@@ -22847,6 +22901,7 @@
method public void setCaptureRate(double);
method public void setInputSurface(@NonNull android.view.Surface);
method public void setLocation(float, float);
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
method public void setMaxDuration(int) throws java.lang.IllegalArgumentException;
method public void setMaxFileSize(long) throws java.lang.IllegalArgumentException;
method public void setNextOutputFile(java.io.FileDescriptor) throws java.io.IOException;
@@ -24478,6 +24533,8 @@
}
public final class LogSessionId {
+ method @NonNull public String getStringId();
+ field @NonNull public static final android.media.metrics.LogSessionId LOG_SESSION_ID_NONE;
}
public class MediaMetricsManager {
@@ -24511,11 +24568,6 @@
method @NonNull public android.media.metrics.NetworkEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
}
- public interface PlaybackComponent {
- method @NonNull public String getPlaybackId();
- method public void setPlaybackId(@NonNull String);
- }
-
public final class PlaybackErrorEvent extends android.media.metrics.Event implements android.os.Parcelable {
method public int describeContents();
method public int getErrorCode();
@@ -34891,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";
@@ -37460,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";
}
@@ -37735,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();
@@ -42300,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 {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 8dc5c15..5925b72 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -201,14 +201,6 @@
method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidRestrictedOnMeteredNetworks(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void registerNetworkPolicyCallback(@Nullable java.util.concurrent.Executor, @NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback);
method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void unregisterNetworkPolicyCallback(@NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback);
- field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000
- field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000
- field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000
- field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4
- field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1
- field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
- field public static final int BLOCKED_REASON_NONE = 0; // 0x0
- field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
}
public static interface NetworkPolicyManager.NetworkPolicyCallback {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d54526d..761774b 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";
@@ -92,6 +93,7 @@
field public static final String CREATE_USERS = "android.permission.CREATE_USERS";
field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER";
+ field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS";
field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
@@ -242,6 +244,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";
@@ -311,6 +314,7 @@
field public static final int hotwordDetectionService = 16844326; // 0x1010626
field public static final int isVrOnly = 16844152; // 0x1010578
field public static final int minExtensionVersion = 16844305; // 0x1010611
+ field public static final int playHomeTransitionSound = 16844358; // 0x1010646
field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
field public static final int sdkVersion = 16844304; // 0x1010610
@@ -420,6 +424,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 {
@@ -949,6 +954,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
@@ -1951,7 +1959,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 {
@@ -2178,6 +2185,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);
}
@@ -2805,7 +2813,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);
}
}
@@ -2871,7 +2881,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;
@@ -4825,7 +4835,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";
}
@@ -5233,7 +5243,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();
@@ -5257,6 +5266,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 {
@@ -7575,9 +7588,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
}
@@ -8687,9 +8702,15 @@
method @WorkerThread public void allocateBytes(@NonNull java.util.UUID, long, @RequiresPermission int) throws java.io.IOException;
method @WorkerThread public void allocateBytes(java.io.FileDescriptor, long, @RequiresPermission int) throws java.io.IOException;
method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String);
method public static boolean hasIsolatedStorage();
method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
+ field public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE = 4; // 0x4
+ field public static final int MOUNT_MODE_EXTERNAL_DEFAULT = 1; // 0x1
+ field public static final int MOUNT_MODE_EXTERNAL_INSTALLER = 2; // 0x2
+ field public static final int MOUNT_MODE_EXTERNAL_NONE = 0; // 0x0
+ field public static final int MOUNT_MODE_EXTERNAL_PASS_THROUGH = 3; // 0x3
field public static final int QUOTA_TYPE_MEDIA_AUDIO = 2; // 0x2
field public static final int QUOTA_TYPE_MEDIA_IMAGE = 1; // 0x1
field public static final int QUOTA_TYPE_MEDIA_NONE = 0; // 0x0
@@ -8953,6 +8974,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";
@@ -9014,6 +9036,8 @@
method @NonNull public static android.net.Uri setManageMode(@NonNull android.net.Uri);
field public static final String ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
field public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
+ field public static final String DOWNLOADS_PROVIDER_AUTHORITY = "downloads";
+ field public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents";
field public static final String EXTRA_SHOW_ADVANCED = "android.provider.extra.SHOW_ADVANCED";
}
@@ -10340,6 +10364,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
@@ -10368,7 +10393,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();
}
@@ -12293,6 +12320,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);
@@ -14040,7 +14081,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/api/test-current.txt b/core/api/test-current.txt
index 4d0c765..97ad48c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -233,6 +233,8 @@
field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time";
field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
+ field public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
+ field public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone";
field public static final String OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = "android:use_icc_auth_with_device_identifier";
field public static final int OP_COARSE_LOCATION = 0; // 0x0
field public static final int OP_RECORD_AUDIO = 27; // 0x1b
@@ -1478,7 +1480,7 @@
package android.media.metrics {
public final class LogSessionId {
- method @NonNull public String getStringId();
+ ctor public LogSessionId(@NonNull String);
}
}
@@ -1931,6 +1933,17 @@
package android.permission {
+ public final class PermGroupUsage {
+ ctor public PermGroupUsage(@NonNull String, int, @NonNull String, long, boolean, boolean, @Nullable CharSequence);
+ method @Nullable public CharSequence getAttribution();
+ method public long getLastAccess();
+ method @NonNull public String getPackageName();
+ method @NonNull public String getPermGroupName();
+ method public int getUid();
+ method public boolean isActive();
+ method public boolean isPhoneCall();
+ }
+
public final class PermissionControllerManager {
method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void countPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
@@ -1945,6 +1958,10 @@
method public void onGetAppPermissions(@NonNull java.util.List<android.permission.RuntimePermissionPresentationInfo>);
}
+ public final class PermissionManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
+ }
+
}
package android.print {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index a6aa28e..7e4af1a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1561,12 +1561,14 @@
*
* @hide
*/
+ @TestApi
public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone";
/**
* Phone call is using camera
*
* @hide
*/
+ @TestApi
public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
/**
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 117df02..26e6741 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -872,8 +872,7 @@
*
* The name is displayed only during provisioning.
*
- * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
- * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
+ * <p>Use in an intent with action {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -3307,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
@@ -13375,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/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/OWNERS b/core/java/android/content/OWNERS
index d0d406a..01b554a 100644
--- a/core/java/android/content/OWNERS
+++ b/core/java/android/content/OWNERS
@@ -8,3 +8,5 @@
per-file AutofillOptions* = file:/core/java/android/service/autofill/OWNERS
per-file ContentCaptureOptions* = file:/core/java/android/service/contentcapture/OWNERS
per-file LocusId* = file:/core/java/android/service/contentcapture/OWNERS
+per-file ComponentCallbacksController = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ComponentCallbacksController = charlesccchen@google.com
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index feb58a30..0952b3e 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -550,9 +550,18 @@
public static final int FLAG_INHERIT_SHOW_WHEN_LOCKED = 0x1;
/**
+ * Bit in {@link #privateFlags} indicating whether a home sound effect should be played if the
+ * home app moves to front after the activity with this flag set.
+ * Set from the {@link android.R.attr#playHomeTransitionSound} attribute.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_HOME_TRANSITION_SOUND = 0x2;
+
+ /**
* Options that have been set in the activity declaration in the manifest.
* These include:
- * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED}.
+ * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED},
+ * {@link #PRIVATE_FLAG_HOME_TRANSITION_SOUND}.
* @hide
*/
public int privateFlags;
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/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
index d99c410..ff6aaad 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
@@ -149,7 +149,10 @@
| flag(ActivityInfo.FLAG_TURN_SCREEN_ON, R.styleable.AndroidManifestActivity_turnScreenOn, sa)
| flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa);
- activity.privateFlags |= flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa);
+ activity.privateFlags |= flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED,
+ R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa)
+ | flag(ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND,
+ R.styleable.AndroidManifestActivity_playHomeTransitionSound, true, sa);
activity.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode, ActivityInfo.COLOR_MODE_DEFAULT);
activity.documentLaunchMode = sa.getInt(R.styleable.AndroidManifestActivity_documentLaunchMode, ActivityInfo.DOCUMENT_LAUNCH_NONE);
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/Resources.java b/core/java/android/content/res/Resources.java
index aed0823..ac4b7b7 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -360,7 +360,7 @@
WeakReference<Theme> weakThemeRef = mThemeRefs.get(i);
Theme theme = weakThemeRef != null ? weakThemeRef.get() : null;
if (theme != null) {
- theme.setImpl(mResourcesImpl.newThemeImpl(theme.getKey()));
+ theme.setNewResourcesImpl(mResourcesImpl);
}
}
}
@@ -1500,6 +1500,9 @@
* retrieve XML attributes with style and theme information applied.
*/
public final class Theme {
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
@UnsupportedAppUsage
private ResourcesImpl.ThemeImpl mThemeImpl;
@@ -1507,7 +1510,15 @@
}
void setImpl(ResourcesImpl.ThemeImpl impl) {
- mThemeImpl = impl;
+ synchronized (mLock) {
+ mThemeImpl = impl;
+ }
+ }
+
+ void setNewResourcesImpl(ResourcesImpl resImpl) {
+ synchronized (mLock) {
+ mThemeImpl = resImpl.newThemeImpl(mThemeImpl.getKey());
+ }
}
/**
@@ -1528,7 +1539,9 @@
* if not already defined in the theme.
*/
public void applyStyle(int resId, boolean force) {
- mThemeImpl.applyStyle(resId, force);
+ synchronized (mLock) {
+ mThemeImpl.applyStyle(resId, force);
+ }
}
/**
@@ -1541,7 +1554,11 @@
* @param other The existing Theme to copy from.
*/
public void setTo(Theme other) {
- mThemeImpl.setTo(other.mThemeImpl);
+ synchronized (mLock) {
+ synchronized (other.mLock) {
+ mThemeImpl.setTo(other.mThemeImpl);
+ }
+ }
}
/**
@@ -1566,7 +1583,9 @@
*/
@NonNull
public TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[] attrs) {
- return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0);
+ synchronized (mLock) {
+ return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0);
+ }
}
/**
@@ -1594,7 +1613,9 @@
public TypedArray obtainStyledAttributes(@StyleRes int resId,
@NonNull @StyleableRes int[] attrs)
throws NotFoundException {
- return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, resId);
+ synchronized (mLock) {
+ return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, resId);
+ }
}
/**
@@ -1650,7 +1671,10 @@
public TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
@NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
- return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes);
+ synchronized (mLock) {
+ return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr,
+ defStyleRes);
+ }
}
/**
@@ -1671,7 +1695,9 @@
@NonNull
@UnsupportedAppUsage
public TypedArray resolveAttributes(@NonNull int[] values, @NonNull int[] attrs) {
- return mThemeImpl.resolveAttributes(this, values, attrs);
+ synchronized (mLock) {
+ return mThemeImpl.resolveAttributes(this, values, attrs);
+ }
}
/**
@@ -1692,7 +1718,9 @@
* <var>outValue</var> is valid, else false.
*/
public boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
- return mThemeImpl.resolveAttribute(resid, outValue, resolveRefs);
+ synchronized (mLock) {
+ return mThemeImpl.resolveAttribute(resid, outValue, resolveRefs);
+ }
}
/**
@@ -1702,7 +1730,9 @@
* @hide
*/
public int[] getAllAttributes() {
- return mThemeImpl.getAllAttributes();
+ synchronized (mLock) {
+ return mThemeImpl.getAllAttributes();
+ }
}
/**
@@ -1738,7 +1768,9 @@
* @see ActivityInfo
*/
public @Config int getChangingConfigurations() {
- return mThemeImpl.getChangingConfigurations();
+ synchronized (mLock) {
+ return mThemeImpl.getChangingConfigurations();
+ }
}
/**
@@ -1749,23 +1781,31 @@
* @param prefix Text to prefix each line printed.
*/
public void dump(int priority, String tag, String prefix) {
- mThemeImpl.dump(priority, tag, prefix);
+ synchronized (mLock) {
+ mThemeImpl.dump(priority, tag, prefix);
+ }
}
// Needed by layoutlib.
/*package*/ long getNativeTheme() {
- return mThemeImpl.getNativeTheme();
+ synchronized (mLock) {
+ return mThemeImpl.getNativeTheme();
+ }
}
/*package*/ int getAppliedStyleResId() {
- return mThemeImpl.getAppliedStyleResId();
+ synchronized (mLock) {
+ return mThemeImpl.getAppliedStyleResId();
+ }
}
/**
* @hide
*/
public ThemeKey getKey() {
- return mThemeImpl.getKey();
+ synchronized (mLock) {
+ return mThemeImpl.getKey();
+ }
}
private String getResourceNameFromHexString(String hexString) {
@@ -1781,7 +1821,9 @@
*/
@ViewDebug.ExportedProperty(category = "theme", hasAdjacentMapping = true)
public String[] getTheme() {
- return mThemeImpl.getTheme();
+ synchronized (mLock) {
+ return mThemeImpl.getTheme();
+ }
}
/** @hide */
@@ -1800,7 +1842,9 @@
* {@link #applyStyle(int, boolean)}.
*/
public void rebase() {
- mThemeImpl.rebase();
+ synchronized (mLock) {
+ mThemeImpl.rebase();
+ }
}
/**
@@ -1862,12 +1906,14 @@
@NonNull
public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr,
@StyleRes int defStyleRes, @StyleRes int explicitStyleRes) {
- int[] stack = mThemeImpl.getAttributeResolutionStack(
- defStyleAttr, defStyleRes, explicitStyleRes);
- if (stack == null) {
- return new int[0];
- } else {
- return stack;
+ synchronized (mLock) {
+ int[] stack = mThemeImpl.getAttributeResolutionStack(
+ defStyleAttr, defStyleRes, explicitStyleRes);
+ if (stack == null) {
+ return new int[0];
+ } else {
+ return stack;
+ }
}
}
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index cbcdb37..553e11b 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1314,22 +1314,16 @@
}
void applyStyle(int resId, boolean force) {
- synchronized (mKey) {
- mAssets.applyStyleToTheme(mTheme, resId, force);
- mThemeResId = resId;
- mKey.append(resId, force);
- }
+ mAssets.applyStyleToTheme(mTheme, resId, force);
+ mThemeResId = resId;
+ mKey.append(resId, force);
}
void setTo(ThemeImpl other) {
- synchronized (mKey) {
- synchronized (other.mKey) {
- mAssets.setThemeTo(mTheme, other.mAssets, other.mTheme);
+ mAssets.setThemeTo(mTheme, other.mAssets, other.mTheme);
- mThemeResId = other.mThemeResId;
- mKey.setTo(other.getKey());
- }
- }
+ mThemeResId = other.mThemeResId;
+ mKey.setTo(other.getKey());
}
@NonNull
@@ -1338,46 +1332,40 @@
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
- synchronized (mKey) {
- final int len = attrs.length;
- final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
+ final int len = attrs.length;
+ final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
- // XXX note that for now we only work with compiled XML files.
- // To support generic XML files we will need to manually parse
- // out the attributes from the XML file (applying type information
- // contained in the resources and such).
- final XmlBlock.Parser parser = (XmlBlock.Parser) set;
- mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
- array.mDataAddress, array.mIndicesAddress);
- array.mTheme = wrapper;
- array.mXml = parser;
- return array;
- }
+ // XXX note that for now we only work with compiled XML files.
+ // To support generic XML files we will need to manually parse
+ // out the attributes from the XML file (applying type information
+ // contained in the resources and such).
+ final XmlBlock.Parser parser = (XmlBlock.Parser) set;
+ mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
+ array.mDataAddress, array.mIndicesAddress);
+ array.mTheme = wrapper;
+ array.mXml = parser;
+ return array;
}
@NonNull
TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
@NonNull int[] values,
@NonNull int[] attrs) {
- synchronized (mKey) {
- final int len = attrs.length;
- if (values == null || len != values.length) {
- throw new IllegalArgumentException(
- "Base attribute values must the same length as attrs");
- }
-
- final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
- mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
- array.mTheme = wrapper;
- array.mXml = null;
- return array;
+ final int len = attrs.length;
+ if (values == null || len != values.length) {
+ throw new IllegalArgumentException(
+ "Base attribute values must the same length as attrs");
}
+
+ final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
+ mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
+ array.mTheme = wrapper;
+ array.mXml = null;
+ return array;
}
boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
- synchronized (mKey) {
- return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
- }
+ return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
}
int[] getAllAttributes() {
@@ -1385,35 +1373,29 @@
}
@Config int getChangingConfigurations() {
- synchronized (mKey) {
- final @NativeConfig int nativeChangingConfig =
- AssetManager.nativeThemeGetChangingConfigurations(mTheme);
- return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
- }
+ final @NativeConfig int nativeChangingConfig =
+ AssetManager.nativeThemeGetChangingConfigurations(mTheme);
+ return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
}
public void dump(int priority, String tag, String prefix) {
- synchronized (mKey) {
- mAssets.dumpTheme(mTheme, priority, tag, prefix);
- }
+ mAssets.dumpTheme(mTheme, priority, tag, prefix);
}
String[] getTheme() {
- synchronized (mKey) {
- final int N = mKey.mCount;
- final String[] themes = new String[N * 2];
- for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) {
- final int resId = mKey.mResId[j];
- final boolean forced = mKey.mForce[j];
- try {
- themes[i] = getResourceName(resId);
- } catch (NotFoundException e) {
- themes[i] = Integer.toHexString(i);
- }
- themes[i + 1] = forced ? "forced" : "not forced";
+ final int n = mKey.mCount;
+ final String[] themes = new String[n * 2];
+ for (int i = 0, j = n - 1; i < themes.length; i += 2, --j) {
+ final int resId = mKey.mResId[j];
+ final boolean forced = mKey.mForce[j];
+ try {
+ themes[i] = getResourceName(resId);
+ } catch (NotFoundException e) {
+ themes[i] = Integer.toHexString(i);
}
- return themes;
+ themes[i + 1] = forced ? "forced" : "not forced";
}
+ return themes;
}
/**
@@ -1422,15 +1404,13 @@
* {@link #applyStyle(int, boolean)}.
*/
void rebase() {
- synchronized (mKey) {
- AssetManager.nativeThemeClear(mTheme);
+ AssetManager.nativeThemeClear(mTheme);
- // Reapply the same styles in the same order.
- for (int i = 0; i < mKey.mCount; i++) {
- final int resId = mKey.mResId[i];
- final boolean force = mKey.mForce[i];
- mAssets.applyStyleToTheme(mTheme, resId, force);
- }
+ // Reapply the same styles in the same order.
+ for (int i = 0; i < mKey.mCount; i++) {
+ final int resId = mKey.mResId[i];
+ final boolean force = mKey.mForce[i];
+ mAssets.applyStyleToTheme(mTheme, resId, force);
}
}
@@ -1455,10 +1435,8 @@
@Nullable
public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr,
@StyleRes int defStyleRes, @StyleRes int explicitStyleRes) {
- synchronized (mKey) {
- return mAssets.getAttributeResolutionStack(
- mTheme, defStyleAttr, defStyleRes, explicitStyleRes);
- }
+ return mAssets.getAttributeResolutionStack(
+ mTheme, defStyleAttr, defStyleRes, explicitStyleRes);
}
}
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/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 96e7d3b..799ea4a 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.procStateToString;
import static android.content.pm.PackageManager.GET_SIGNATURES;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -203,78 +204,6 @@
})
public @interface SubscriptionOverrideMask {}
- /**
- * Flag to indicate that an app is not subject to any restrictions that could result in its
- * network access blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_REASON_NONE = 0;
-
- /**
- * Flag to indicate that an app is subject to Battery saver restrictions that would
- * result in its network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_REASON_BATTERY_SAVER = 1 << 0;
-
- /**
- * Flag to indicate that an app is subject to Doze restrictions that would
- * result in its network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_REASON_DOZE = 1 << 1;
-
- /**
- * Flag to indicate that an app is subject to App Standby restrictions that would
- * result in its network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_REASON_APP_STANDBY = 1 << 2;
-
- /**
- * Flag to indicate that an app is subject to Restricted mode restrictions that would
- * result in its network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3;
-
- /**
- * Flag to indicate that an app is subject to Data saver restrictions that would
- * result in its metered network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_METERED_REASON_DATA_SAVER = 1 << 16;
-
- /**
- * Flag to indicate that an app is subject to user restrictions that would
- * result in its metered network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 1 << 17;
-
- /**
- * Flag to indicate that an app is subject to Device admin restrictions that would
- * result in its metered network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 1 << 18;
-
/** @hide */
public static final int BLOCKED_METERED_REASON_MASK = 0xffff0000;
@@ -344,22 +273,6 @@
/** @hide */
public static final int ALLOWED_METERED_REASON_MASK = 0xffff0000;
- /**
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, prefix = {"BLOCKED_"}, value = {
- BLOCKED_REASON_NONE,
- BLOCKED_REASON_BATTERY_SAVER,
- BLOCKED_REASON_DOZE,
- BLOCKED_REASON_APP_STANDBY,
- BLOCKED_REASON_RESTRICTED_MODE,
- BLOCKED_METERED_REASON_DATA_SAVER,
- BLOCKED_METERED_REASON_USER_RESTRICTED,
- BLOCKED_METERED_REASON_ADMIN_DISABLED,
- })
- public @interface BlockedReason {}
-
private final Context mContext;
@UnsupportedAppUsage
private INetworkPolicyManager mService;
@@ -883,14 +796,15 @@
* {@code BLOCKED_REASON_*} and/or {@code BLOCKED_METERED_REASON_*} constants.
*
* @param blockedReasons Value indicating the reasons for why the network access of an UID is
- * blocked. If the value is equal to {@link #BLOCKED_REASON_NONE}, then
+ * blocked. If the value is equal to
+ * {@link ConnectivityManager#BLOCKED_REASON_NONE}, then
* it indicates that an app's network access is not blocked.
* @param meteredNetwork Value indicating whether the network is metered or not.
* @return Whether network access is blocked or not.
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static boolean isUidBlocked(@BlockedReason int blockedReasons, boolean meteredNetwork) {
+ public static boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) {
if (blockedReasons == BLOCKED_REASON_NONE) {
return false;
}
@@ -913,7 +827,7 @@
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@NonNull
- public static String blockedReasonsToString(@BlockedReason int blockedReasons) {
+ public static String blockedReasonsToString(int blockedReasons) {
return DebugUtils.flagsToString(NetworkPolicyManager.class, "BLOCKED_", blockedReasons);
}
@@ -977,7 +891,7 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- default void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {}
+ default void onUidBlockedReasonChanged(int uid, int blockedReasons) {}
}
/** @hide */
@@ -992,8 +906,7 @@
}
@Override
- public void onBlockedReasonChanged(int uid, @BlockedReason int oldBlockedReasons,
- @BlockedReason int newBlockedReasons) {
+ public void onBlockedReasonChanged(int uid, int oldBlockedReasons, int newBlockedReasons) {
if (oldBlockedReasons != newBlockedReasons) {
dispatchOnUidBlockedReasonChanged(mExecutor, mCallback, uid, newBlockedReasons);
}
@@ -1001,7 +914,7 @@
}
private static void dispatchOnUidBlockedReasonChanged(@Nullable Executor executor,
- @NonNull NetworkPolicyCallback callback, int uid, @BlockedReason int blockedReasons) {
+ @NonNull NetworkPolicyCallback callback, int uid, int blockedReasons) {
if (executor == null) {
callback.onUidBlockedReasonChanged(uid, blockedReasons);
} else {
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
index 4078b24..74c3ba4 100644
--- a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
+++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
@@ -23,6 +23,6 @@
*/
oneway interface INetworkStatsProvider {
void onRequestStatsUpdate(int token);
- void onSetLimit(String iface, long quotaBytes);
void onSetAlert(long quotaBytes);
+ void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes);
}
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
index bd336dd..7eaa01e 100644
--- a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
+++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
@@ -26,6 +26,6 @@
oneway interface INetworkStatsProviderCallback {
void notifyStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats);
void notifyAlertReached();
- void notifyLimitReached();
+ void notifyWarningOrLimitReached();
void unregister();
}
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProvider.java b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
index 7639d22..23fc069 100644
--- a/core/java/android/net/netstats/provider/NetworkStatsProvider.java
+++ b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
@@ -29,7 +29,8 @@
@SystemApi
public abstract class NetworkStatsProvider {
/**
- * A value used by {@link #onSetLimit} and {@link #onSetAlert} indicates there is no limit.
+ * A value used by {@link #onSetLimit}, {@link #onSetAlert} and {@link #onSetWarningAndLimit}
+ * indicates there is no limit.
*/
public static final int QUOTA_UNLIMITED = -1;
@@ -42,13 +43,13 @@
}
@Override
- public void onSetLimit(String iface, long quotaBytes) {
- NetworkStatsProvider.this.onSetLimit(iface, quotaBytes);
+ public void onSetAlert(long quotaBytes) {
+ NetworkStatsProvider.this.onSetAlert(quotaBytes);
}
@Override
- public void onSetAlert(long quotaBytes) {
- NetworkStatsProvider.this.onSetAlert(quotaBytes);
+ public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) {
+ NetworkStatsProvider.this.onSetWarningAndLimit(iface, warningBytes, limitBytes);
}
};
@@ -145,11 +146,25 @@
}
/**
- * Notify system that the quota set by {@code onSetLimit} has been reached.
+ * Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached.
+ */
+ public void notifyWarningReached() {
+ try {
+ // Reuse the code path to notify warning reached with limit reached
+ // since framework handles them in the same way.
+ getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Notify system that the quota set by {@link #onSetLimit} or limit set by
+ * {@link #onSetWarningAndLimit} has been reached.
*/
public void notifyLimitReached() {
try {
- getProviderCallbackBinderOrThrow().notifyLimitReached();
+ getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached();
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
@@ -183,6 +198,28 @@
public abstract void onSetLimit(@NonNull String iface, long quotaBytes);
/**
+ * Called by {@code NetworkStatsService} when setting the interface quotas for the specified
+ * upstream interface. If a provider implements {@link #onSetWarningAndLimit}, the system
+ * will not call {@link #onSetLimit}. When this method is called, the implementation
+ * should behave as follows:
+ * 1. If {@code warningBytes} is reached on {@code iface}, block all further traffic on
+ * {@code iface} and call {@link NetworkStatsProvider@notifyWarningReached()}.
+ * 2. If {@code limitBytes} is reached on {@code iface}, block all further traffic on
+ * {@code iface} and call {@link NetworkStatsProvider#notifyLimitReached()}.
+ *
+ * @param iface the interface requiring the operation.
+ * @param warningBytes the warning defined as the number of bytes, starting from zero and
+ * counting from now. A value of {@link #QUOTA_UNLIMITED} indicates
+ * 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.
+ */
+ public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) {
+ // Backward compatibility for those who didn't override this function.
+ onSetLimit(iface, limitBytes);
+ }
+
+ /**
* Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations
* MUST call {@link NetworkStatsProvider#notifyAlertReached()} when {@code quotaBytes} bytes
* have been reached. Unlike {@link #onSetLimit(String, long)}, the custom implementation should
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/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 81c38f8..9385402 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -202,4 +202,5 @@
void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92;
PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93;
boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 94;
- }
+ int getExternalStorageMountMode(int uid, in String packageName) = 95;
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index cae20ed..8107168 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2124,6 +2124,52 @@
}
}
+
+ /** @hide */
+ @IntDef(prefix = { "MOUNT_MODE_" }, value = {
+ MOUNT_MODE_EXTERNAL_NONE,
+ MOUNT_MODE_EXTERNAL_DEFAULT,
+ MOUNT_MODE_EXTERNAL_INSTALLER,
+ MOUNT_MODE_EXTERNAL_PASS_THROUGH,
+ MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE
+ })
+ /** @hide */
+ public @interface MountMode {}
+
+ /**
+ * No external storage should be mounted.
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
+ /**
+ * Default external storage should be mounted.
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT;
+ /**
+ * Mount mode for package installers which should give them access to
+ * all obb dirs in addition to their package sandboxes
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER;
+ /**
+ * The lower file system should be bind mounted directly on external storage
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;
+
+ /**
+ * Use the regular scoped storage filesystem, but Android/ should be writable.
+ * Used to support the applications hosting DownloadManager and the MTP server.
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE =
+ IVold.REMOUNT_MODE_ANDROID_WRITABLE;
/**
* Flag indicating that a disk space allocation request should operate in an
* aggressive mode. This flag should only be rarely used in situations that
@@ -2301,6 +2347,28 @@
}
/**
+ * Returns the External Storage mount mode corresponding to the given uid and packageName.
+ * These mount modes specify different views and access levels for
+ * different apps on external storage.
+ *
+ * @params uid UID of the application
+ * @params packageName name of the package
+ * @return {@code MountMode} for the given uid and packageName.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
+ @SystemApi
+ @MountMode
+ public int getExternalStorageMountMode(int uid, @NonNull String packageName) {
+ try {
+ return mStorageManager.getExternalStorageMountMode(uid, packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Allocate the requested number of bytes for your application to use in the
* given open file. This will cause the system to delete any cached files
* necessary to satisfy your request.
diff --git a/core/java/android/permission/PermGroupUsage.java b/core/java/android/permission/PermGroupUsage.java
index c94c0ff..440d6f2 100644
--- a/core/java/android/permission/PermGroupUsage.java
+++ b/core/java/android/permission/PermGroupUsage.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
/**
* Represents the usage of a permission group by an app. Supports package name, user, permission
@@ -26,6 +27,7 @@
*
* @hide
*/
+@TestApi
public final class PermGroupUsage {
private final String mPackageName;
@@ -36,7 +38,19 @@
private final boolean mIsPhoneCall;
private final CharSequence mAttribution;
- PermGroupUsage(@NonNull String packageName, int uid,
+ /**
+ *
+ * @param packageName The package name of the using app
+ * @param uid The uid of the using app
+ * @param permGroupName The name of the permission group being used
+ * @param lastAccess The time of last access
+ * @param isActive Whether this is active
+ * @param isPhoneCall Whether this is a usage by the phone
+ * @param attribution An optional string attribution to show
+ * @hide
+ */
+ @TestApi
+ public PermGroupUsage(@NonNull String packageName, int uid,
@NonNull String permGroupName, long lastAccess, boolean isActive, boolean isPhoneCall,
@Nullable CharSequence attribution) {
this.mPackageName = packageName;
@@ -48,30 +62,58 @@
this.mAttribution = attribution;
}
+ /**
+ * @hide
+ */
+ @TestApi
public @NonNull String getPackageName() {
return mPackageName;
}
+ /**
+ * @hide
+ */
+ @TestApi
public int getUid() {
return mUid;
}
+ /**
+ * @hide
+ */
+ @TestApi
public @NonNull String getPermGroupName() {
return mPermGroupName;
}
+ /**
+ * @hide
+ */
+ @TestApi
public long getLastAccess() {
return mLastAccess;
}
+ /**
+ * @hide
+ */
+ @TestApi
public boolean isActive() {
return mIsActive;
}
+ /**
+ * @hide
+ */
+ @TestApi
public boolean isPhoneCall() {
return mIsPhoneCall;
}
+ /**
+ * @hide
+ */
+ @TestApi
public @Nullable CharSequence getAttribution() {
return mAttribution;
}
@@ -80,6 +122,7 @@
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this))
+ " packageName: " + mPackageName + ", UID: " + mUid + ", permGroup: "
- + mPermGroupName + ", isActive: " + mIsActive + ", attribution: " + mAttribution;
+ + mPermGroupName + ", lastAccess: " + mLastAccess + ", isActive: " + mIsActive
+ + ", attribution: " + mAttribution;
}
}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 177e422..baa25f0 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -26,6 +26,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -79,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 */
@@ -102,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;
@@ -318,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;
}
@@ -347,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 {
@@ -851,6 +860,7 @@
*
* @hide
*/
+ @TestApi
@NonNull
@RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
public List<PermGroupUsage> getIndicatorAppOpUsageData() {
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 921911b..2d6fa3c 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -71,13 +71,10 @@
private static final String PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled";
/** How long after an access to show it as "recent" */
- private static final String RECENT_ACCESS_TIME_MS = "recent_acccess_time_ms";
+ private static final String RECENT_ACCESS_TIME_MS = "recent_access_time_ms";
/** How long after an access to show it as "running" */
- private static final String RUNNING_ACCESS_TIME_MS = "running_acccess_time_ms";
-
- /** The name of the expected voice IME subtype */
- private static final String VOICE_IME_SUBTYPE = "voice";
+ private static final String RUNNING_ACCESS_TIME_MS = "running_access_time_ms";
private static final String SYSTEM_PKG = "android";
@@ -279,6 +276,10 @@
opEntry.getAttributedOpEntries().get(attributionTag);
long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags);
+ if (attrOpEntry.isRunning()) {
+ lastAccessTime = now;
+ }
+
if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) {
continue;
}
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/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 0fea484..620fa65 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -236,10 +236,21 @@
public static final String
ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
- /** {@hide} */
+ /**
+ * External Storage Provider's authority string
+ * {@hide}
+ */
+ @SystemApi
public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
"com.android.externalstorage.documents";
+ /**
+ * Download Manager's authority string
+ * {@hide}
+ */
+ @SystemApi
+ public static final String DOWNLOADS_PROVIDER_AUTHORITY = Downloads.Impl.AUTHORITY;
+
/** {@hide} */
public static final String EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID = "primary";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 719c383..591f05f 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..e573056d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -26136,9 +26136,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;
}
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/window/WindowContext.java b/core/java/android/window/WindowContext.java
index bff2252..375f4cf 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -27,10 +27,7 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.RemoteException;
-import android.view.IWindowManager;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
import com.android.internal.annotations.VisibleForTesting;
@@ -48,13 +45,11 @@
@UiContext
public class WindowContext extends ContextWrapper {
private final WindowManager mWindowManager;
- private final IWindowManager mWms;
- private final @NonNull IBinder mToken;
private final @WindowManager.LayoutParams.WindowType int mType;
private final @Nullable Bundle mOptions;
- private boolean mListenerRegistered;
private final ComponentCallbacksController mCallbacksController =
new ComponentCallbacksController();
+ private final WindowContextController mController;
/**
* Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
@@ -69,9 +64,9 @@
mType = type;
mOptions = options;
- mWms = WindowManagerGlobal.getWindowManagerService();
- mToken = getWindowContextToken();
mWindowManager = createWindowContextWindowManager(this);
+ IBinder token = getWindowContextToken();
+ mController = new WindowContextController(token);
Reference.reachabilityFence(this);
}
@@ -81,12 +76,7 @@
* to receive configuration changes of the associated {@link WindowManager} node.
*/
public void registerWithServer() {
- try {
- mListenerRegistered = mWms.registerWindowContextListener(mToken, mType, getDisplayId(),
- mOptions);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mController.registerListener(mType, getDisplayId(), mOptions);
}
@Override
@@ -106,14 +96,7 @@
/** Used for test to invoke because we can't invoke finalize directly. */
@VisibleForTesting
public void release() {
- if (mListenerRegistered) {
- mListenerRegistered = false;
- try {
- mWms.unregisterWindowContextListener(mToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ mController.unregisterListenerIfNeeded();
destroy();
}
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
new file mode 100644
index 0000000..6143414
--- /dev/null
+++ b/core/java/android/window/WindowContextController.java
@@ -0,0 +1,96 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * The controller to manage {@link WindowContext} listener, such as registering and unregistering
+ * the listener.
+ *
+ * @hide
+ */
+public class WindowContextController {
+ private final IWindowManager mWms;
+ @VisibleForTesting
+ public boolean mListenerRegistered;
+ @NonNull
+ private final IBinder mToken;
+
+ /**
+ * Window Context Controller constructor
+ *
+ * @param token The token to register to the window context listener. It is usually from
+ * {@link Context#getWindowContextToken()}.
+ */
+ public WindowContextController(@NonNull IBinder token) {
+ mToken = token;
+ mWms = WindowManagerGlobal.getWindowManagerService();
+ }
+
+ /** Used for test only. DO NOT USE it in production code. */
+ @VisibleForTesting
+ public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) {
+ mToken = token;
+ mWms = mockWms;
+ }
+
+ /**
+ * Registers the {@code mToken} to the window context listener.
+ *
+ * @param type The window type of the {@link WindowContext}
+ * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+ * @param options The window context launched option
+ */
+ public void registerListener(@WindowType int type, int displayId, @Nullable Bundle options) {
+ if (mListenerRegistered) {
+ throw new UnsupportedOperationException("A Window Context can only register a listener"
+ + " once.");
+ }
+ try {
+ mListenerRegistered = mWms.registerWindowContextListener(mToken, type, displayId,
+ options);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters the window context listener associated with the {@code mToken} if it has been
+ * registered.
+ */
+ public void unregisterListenerIfNeeded() {
+ if (mListenerRegistered) {
+ try {
+ mWms.unregisterWindowContextListener(mToken);
+ mListenerRegistered = false;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index dd837fc..3b6a877 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -194,12 +194,12 @@
if (mIsSendAction) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_share_with_work_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_share_with_work_apps_explanation);
} else {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_work_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_work_apps_explanation);
}
}
@@ -209,44 +209,31 @@
if (mIsSendAction) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_share_with_personal_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_share_with_personal_apps_explanation);
} else {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_personal_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_personal_apps_explanation);
}
}
@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 2464fc7..622f166 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -205,7 +205,7 @@
protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_work_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_work_apps_explanation);
}
@@ -213,7 +213,7 @@
protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_personal_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_personal_apps_explanation);
}
@@ -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/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_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 8a47135..b3b66c4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1230,7 +1230,7 @@
android:description="@string/permdesc_answerPhoneCalls"
android:protectionLevel="dangerous|runtime" />
- <!-- Allows a calling application which manages it own calls through the self-managed
+ <!-- Allows a calling application which manages its own calls through the self-managed
{@link android.telecom.ConnectionService} APIs. See
{@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED} for more information on the
self-managed ConnectionService APIs.
@@ -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
@@ -4440,6 +4459,13 @@
<permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to disable system sound effects when the user exits one of
+ the application's activities.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.DISABLE_SYSTEM_SOUND_EFFECTS"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows an application to provide remote displays.
<p>Not for use by third-party applications.</p>
@hide -->
@@ -5417,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"
@@ -5552,6 +5572,7 @@
@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"/>
<!-- 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 38e8f83..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
@@ -2862,6 +2878,13 @@
{@link android.content.Context#sendBroadcast(Intent, String)} being used.
Multiple tags can be specified separated by '|'. -->
<attr name="attributionTags"/>
+ <!-- Specifies whether a home sound effect should be played if the home app moves to
+ front after an activity with this flag set to <code>true</code>.
+ <p>The default value of this attribute is <code>true</code>.
+ <p>Also note that home sounds are only played if the device supports home sounds,
+ usually TVs.
+ <p>Requires permission {@code android.permission.DISABLE_SYSTEM_SOUND_EFFECTS}. -->
+ <attr name="playHomeTransitionSound" format="boolean"/>
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new
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 3259caf..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" />
@@ -3093,6 +3093,8 @@
<public name="suppressesSpellChecker" />
<public name="usesPermissionFlags" />
<public name="requestOptimizedExternalStorageAccess" />
+ <!-- @hide @SystemApi -->
+ <public name="playHomeTransitionSound" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0b3c405..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>
@@ -2815,6 +2823,15 @@
<!-- Displayed to the user to inform them that an app has accessed clipboard data (pasted as in "copy and paste") [CHAR LIMIT=50] -->
<string name="pasted_from_clipboard"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted from clipboard</string>
+ <!-- Displayed to the user to inform them that an app has accessed text from clipboard (pasted as in "copy and paste") [CHAR LIMIT=50] -->
+ <string name="pasted_text"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted text you copied</string>
+
+ <!-- Displayed to the user to inform them that an app has accessed an image from clipboard (pasted as in "copy and paste") [CHAR LIMIT=50] -->
+ <string name="pasted_image"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted an image you copied</string>
+
+ <!-- Displayed to the user to inform them that an app has accessed content from clipboard (pasted as in "copy and paste") [CHAR LIMIT=50] -->
+ <string name="pasted_content"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted content you copied</string>
+
<!-- Menu item displayed at the end of a menu to allow users to see another page worth of menu items. This is shown on any app's menu as long as the app has too many items in the menu.-->
<string name="more_item_label">More</string>
<!-- Prepended to the shortcut for a menu item to indicate that the user should hold the MENU button together with the shortcut to invoke the item. For example, if the shortcut to open a new tab in browser is MENU and B together, then this would be prepended to the letter "B" -->
@@ -5626,40 +5643,39 @@
<!-- Accessibility label for the work tab button. [CHAR LIMIT=NONE] -->
<string name="resolver_work_tab_accessibility">Work view</string>
- <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this specific content with work apps. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_work_apps">Can\u2019t share this with work apps</string>
+ <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this content across profiles. [CHAR LIMIT=NONE] -->
+ <string name="resolver_cross_profile_blocked">Blocked by your IT admin</string>
<!-- Error message. This text is explaining that the user's IT admin doesn't allow this specific content to be shared with apps in the work profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_work_apps_explanation">Your IT admin doesn\u2019t allow you to share this content with apps in your work profile</string>
+ <string name="resolver_cant_share_with_work_apps_explanation">This content can\u2019t be shared with work apps</string>
- <!-- Title of an error screen. This error message lets the user know that their IT admin doesn't allow them to open this specific content with a work app. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_work_apps">Can\u2019t open this with work apps</string>
<!-- Error message. This message lets the user know that their IT admin doesn't allow them to open this specific content with an app in their work profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_work_apps_explanation">Your IT admin doesn\u2019t allow you to open this content with apps in your work profile</string>
+ <string name="resolver_cant_access_work_apps_explanation">This content can\u2019t be opened with work apps</string>
- <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this specific content with personal apps. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_personal_apps">Can\u2019t share this with personal apps</string>
<!-- Error message. This text is explaining that the user's IT admin doesn't allow them to share this specific content with apps in their personal profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_personal_apps_explanation">Your IT admin doesn\u2019t allow you to share this content with apps in your personal profile</string>
+ <string name="resolver_cant_share_with_personal_apps_explanation">This content can\u2019t be shared with personal apps</string>
- <!-- Title of an error screen. This error message lets the user know that their IT admin doesn't allow them to open this specific content with a personal app. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_personal_apps">Can\u2019t open this with personal apps</string>
<!-- Error message. This message lets the user know that their IT admin doesn't allow them to open this specific content with an app in their personal profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_personal_apps_explanation">Your IT admin doesn\u2019t allow you to open this content with apps in your personal profile</string>
+ <string name="resolver_cant_access_personal_apps_explanation">This content can\u2019t be opened with personal apps</string>
<!-- Error message. This text lets the user know that they need to turn on work apps in order to share or open content. There's also a button a user can tap to turn on the apps. [CHAR LIMIT=NONE] -->
<string name="resolver_turn_on_work_apps">Work profile is paused</string>
<!-- 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">Turn on</string>
+ <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] -->
@@ -5906,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 a704936..b924ecd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -555,6 +555,9 @@
<java-symbol type="string" name="paste_as_plain_text" />
<java-symbol type="string" name="pasted_from_app" />
<java-symbol type="string" name="pasted_from_clipboard" />
+ <java-symbol type="string" name="pasted_text" />
+ <java-symbol type="string" name="pasted_image" />
+ <java-symbol type="string" name="pasted_content" />
<java-symbol type="string" name="replace" />
<java-symbol type="string" name="undo" />
<java-symbol type="string" name="redo" />
@@ -3533,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" />
@@ -4092,19 +4096,14 @@
<java-symbol type="id" name="resolver_empty_state_container" />
<java-symbol type="id" name="button_bar_container" />
<java-symbol type="id" name="title_container" />
- <java-symbol type="string" name="resolver_cant_share_with_work_apps" />
+ <java-symbol type="string" name="resolver_cross_profile_blocked" />
<java-symbol type="string" name="resolver_cant_share_with_work_apps_explanation" />
- <java-symbol type="string" name="resolver_cant_share_with_personal_apps" />
<java-symbol type="string" name="resolver_cant_share_with_personal_apps_explanation" />
- <java-symbol type="string" name="resolver_cant_access_work_apps" />
<java-symbol type="string" name="resolver_cant_access_work_apps_explanation" />
- <java-symbol type="string" name="resolver_cant_access_personal_apps" />
<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 7ef1d5e..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();
@@ -51,7 +51,7 @@
CompletableFuture<AppSearchResult<SetSchemaResponse>> schemaFuture =
new CompletableFuture<>();
mSearchSession.setSchema(
- new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor,
+ new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor, mExecutor,
schemaFuture::complete);
schemaFuture.get().getResultValue();
diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS
index c61a4b5..0b94589 100644
--- a/core/tests/coretests/src/android/content/OWNERS
+++ b/core/tests/coretests/src/android/content/OWNERS
@@ -2,3 +2,5 @@
per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
+per-file ComponentCallbacksControllerTest = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ComponentCallbacksControllerTest = charlesccchen@google.com
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/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
new file mode 100644
index 0000000..e4fc19c
--- /dev/null
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.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.window;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.view.IWindowManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link WindowContextController}
+ *
+ * <p>Build/Install/Run:
+ * atest FrameworksCoreTests:WindowContextControllerTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowContextControllerTest {
+ private WindowContextController mController;
+ private IWindowManager mMockWms;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockWms = mock(IWindowManager.class);
+ mController = new WindowContextController(new Binder(), mMockWms);
+
+ doReturn(true).when(mMockWms).registerWindowContextListener(
+ any(), anyInt(), anyInt(), any());
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testRegisterListenerTwiceThrowException() {
+ mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+ null /* options */);
+ mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+ null /* options */);
+ }
+
+ @Test
+ public void testUnregisterListenerIfNeeded_NotRegisteredYet_DoNothing() throws Exception {
+ mController.unregisterListenerIfNeeded();
+
+ verify(mMockWms, never()).registerWindowContextListener(any(), anyInt(), anyInt(), any());
+ }
+
+ @Test
+ public void testRegisterAndUnRegisterListener() {
+ mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+ null /* options */);
+
+ assertThat(mController.mListenerRegistered).isTrue();
+
+ mController.unregisterListenerIfNeeded();
+
+ assertThat(mController.mListenerRegistered).isFalse();
+ }
+}
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 4c58ad3..1633d28 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -1608,14 +1608,13 @@
onView(withId(R.id.contentPanel))
.perform(swipeUp());
- onView(withText(R.string.resolver_cant_share_with_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
@Test
public void testWorkTab_workProfileDisabled_emptyStateShown() {
// enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -1627,6 +1626,7 @@
Intent sendIntent = createSendTextIntent();
sendIntent.setType("TestType");
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
final ChooserWrapperActivity activity =
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
@@ -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()));
}
@@ -1686,7 +1686,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_cant_share_with_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.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()));
}
@@ -2116,7 +2116,7 @@
onView(withId(R.id.contentPanel))
.perform(swipeUp());
- onView(withText(R.string.resolver_cant_access_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.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 7dc5a8b..97652a9 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -650,7 +650,7 @@
onView(withId(R.id.contentPanel))
.perform(swipeUp());
- onView(withText(R.string.resolver_cant_access_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
@@ -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()));
}
@@ -726,7 +726,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_cant_access_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.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/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 24d73ef..3616a4d 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -276,7 +276,11 @@
*/
@Nullable
public float[] getCornerRadii() {
- return mGradientState.mRadiusArray.clone();
+ float[] radii = mGradientState.mRadiusArray;
+ if (radii == null) {
+ return null;
+ }
+ return radii.clone();
}
/**
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 5dd250c..98b9584 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -60,17 +60,13 @@
+ " float d = distance(uv, xy);\n"
+ " return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);\n"
+ "}\n"
- + "\n"
- + "float getRingMask(vec2 frag, vec2 center, float r, float progress) {\n"
- + " float dist = distance(frag, center);\n"
- + " float expansion = r * .6;\n"
- + " r = r * min(1.,progress);\n"
- + " float minD = max(r - expansion, 0.);\n"
- + " float maxD = r + expansion;\n"
- + " if (dist > maxD || dist < minD) return .0;\n"
- + " return min(maxD - dist, dist - minD) / expansion; \n"
+ + "float softRing(vec2 uv, vec2 xy, float radius, float progress, float blur) {\n"
+ + " float thickness = 0.2 * radius;\n"
+ + " float currentRadius = radius * progress;\n"
+ + " float circle_outer = softCircle(uv, xy, currentRadius + thickness, blur);\n"
+ + " float circle_inner = softCircle(uv, xy, currentRadius - thickness, blur);\n"
+ + " return clamp(circle_outer - circle_inner, 0., 1.);\n"
+ "}\n"
- + "\n"
+ "float subProgress(float start, float end, float progress) {\n"
+ " float sub = clamp(progress, start, end);\n"
+ " return (sub - start) / (end - start); \n"
@@ -80,8 +76,8 @@
+ " float fadeOutNoise = subProgress(0.375, 1., in_progress);\n"
+ " float fadeOutRipple = subProgress(0.375, 0.75, in_progress);\n"
+ " vec2 center = mix(in_touch, in_origin, fadeIn);\n"
- + " float ring = getRingMask(p, center, in_maxRadius, fadeIn);\n"
- + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n"
+ + " float ring = softRing(p, center, in_maxRadius, fadeIn, 0.45);\n"
+ + " float alpha = 1. - fadeOutNoise;\n"
+ " vec2 uv = p * in_resolutionScale;\n"
+ " vec2 densityUv = uv - mod(uv, in_noiseScale);\n"
+ " float sparkle = sparkles(densityUv, in_noisePhase) * ring * alpha;\n"
@@ -137,8 +133,7 @@
}
public void setResolution(float w, float h, int density) {
- float noiseScale = 0.8f;
- float densityScale = density * DisplayMetrics.DENSITY_DEFAULT_SCALE * 0.5f * noiseScale;
+ float densityScale = density * DisplayMetrics.DENSITY_DEFAULT_SCALE * 0.5f;
setUniform("in_resolutionScale", new float[] {1f / w, 1f / h});
setUniform("in_noiseScale", new float[] {densityScale / w, densityScale / h});
}
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/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index ade63e5..5d9fad5 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -45,7 +45,8 @@
return SkColorSpace::MakeSRGB();
}
-ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker)
+ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker,
+ SkCodec::ZeroInitialized zeroInit)
: mCodec(std::move(codec))
, mPeeker(std::move(peeker))
, mDecodeSize(mCodec->codec()->dimensions())
@@ -57,6 +58,7 @@
mTargetSize = swapWidthHeight() ? SkISize { mDecodeSize.height(), mDecodeSize.width() }
: mDecodeSize;
this->rewind();
+ mOptions.fZeroInitialized = zeroInit;
}
ImageDecoder::~ImageDecoder() = default;
@@ -446,10 +448,17 @@
ALOGE("Failed to invert matrix!");
}
}
+
+ // Even if the client did not provide zero initialized memory, the
+ // memory we decode into is.
+ mOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized;
}
auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions);
+ // The next call to decode() may not provide zero initialized memory.
+ mOptions.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
+
if (scale || handleOrigin || mCropRect) {
SkBitmap scaledBm;
if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) {
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index cbfffd5..cef2233 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -34,8 +34,8 @@
std::unique_ptr<SkAndroidCodec> mCodec;
sk_sp<SkPngChunkReader> mPeeker;
- ImageDecoder(std::unique_ptr<SkAndroidCodec> codec,
- sk_sp<SkPngChunkReader> peeker = nullptr);
+ ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker = nullptr,
+ SkCodec::ZeroInitialized zeroInit = SkCodec::kNo_ZeroInitialized);
~ImageDecoder();
SkISize getSampledDimensions(int sampleSize) const;
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index ad7741b..f7b8c01 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -141,7 +141,8 @@
}
const bool isNinePatch = peeker->mPatch != nullptr;
- ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker));
+ ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker),
+ SkCodec::kYes_ZeroInitialized);
return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
reinterpret_cast<jlong>(decoder), decoder->width(), decoder->height(),
animated, isNinePatch);
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 f957a73..343d04f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -568,6 +568,42 @@
public static final int FLAG_FROM_KEY = 1 << 12;
/** @hide */
+ @IntDef(prefix = {"ENCODED_SURROUND_OUTPUT_"}, value = {
+ ENCODED_SURROUND_OUTPUT_AUTO,
+ ENCODED_SURROUND_OUTPUT_NEVER,
+ ENCODED_SURROUND_OUTPUT_ALWAYS,
+ ENCODED_SURROUND_OUTPUT_MANUAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EncodedSurroundOutputMode {}
+
+ /**
+ * The surround sound formats are available for use if they are detected. This is the default
+ * mode.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0;
+
+ /**
+ * The surround sound formats are NEVER available, even if they are detected by the hardware.
+ * Those formats will not be reported.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1;
+
+ /**
+ * The surround sound formats are ALWAYS available, even if they are not detected by the
+ * hardware. Those formats will be reported as part of the HDMI output capability.
+ * Applications are then free to use either PCM or encoded output.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2;
+
+ /**
+ * Surround sound formats are available according to the choice of user, even if they are not
+ * detected by the hardware. Those formats will be reported as part of the HDMI output
+ * capability. Applications are then free to use either PCM or encoded output.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3;
+
+ /** @hide */
@IntDef(flag = true, prefix = "FLAG", value = {
FLAG_SHOW_UI,
FLAG_ALLOW_RINGER_MODES,
@@ -3185,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
*/
@@ -3260,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;
}
@@ -3294,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;
}
@@ -3330,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;
}
@@ -6754,6 +6774,34 @@
}
/**
+ * Sets the surround sound mode.
+ *
+ * @return true if successful, otherwise false
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
+ public boolean setEncodedSurroundMode(@EncodedSurroundOutputMode int mode) {
+ try {
+ return getService().setEncodedSurroundMode(mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the surround sound mode.
+ *
+ * @return true if successful, otherwise false
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
+ public @EncodedSurroundOutputMode int getEncodedSurroundMode() {
+ try {
+ return getService().getEncodedSurroundMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
* Returns all surround formats.
* @return a map where the key is a surround format and
@@ -6771,7 +6819,6 @@
}
/**
- * @hide
* Set a certain surround format as enabled or not.
* @param audioFormat a surround format, the value is one of
* {@link AudioFormat#ENCODING_AC3}, {@link AudioFormat#ENCODING_E_AC3},
@@ -6785,10 +6832,29 @@
* @param enabled the required surround format state, true for enabled, false for disabled
* @return true if successful, otherwise false
*/
+ @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
public boolean setSurroundFormatEnabled(
@AudioFormat.SurroundSoundEncoding int audioFormat, boolean enabled) {
- int status = AudioSystem.setSurroundFormatEnabled(audioFormat, enabled);
- return status == AudioManager.SUCCESS;
+ try {
+ return getService().setSurroundFormatEnabled(audioFormat, enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets whether a certain surround format is enabled or not.
+ * @param audioFormat a surround format
+ *
+ * @return whether the required surround format is enabled
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
+ public boolean isSurroundFormatEnabled(@AudioFormat.SurroundSoundEncoding int audioFormat) {
+ try {
+ return getService().isSurroundFormatEnabled(audioFormat);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 0d61399..3399377 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -32,6 +32,7 @@
import android.media.MediaRecorder.Source;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
+import android.media.metrics.LogSessionId;
import android.media.permission.Identity;
import android.media.projection.MediaProjection;
import android.os.Binder;
@@ -282,9 +283,9 @@
/**
* The log session id used for metrics.
- * A null or empty string here means it is not set.
+ * {@link LogSessionId#LOG_SESSION_ID_NONE} here means it is not set.
*/
- private String mLogSessionId;
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
//---------------------------------------------------------
// Constructor, Finalize
@@ -1963,24 +1964,34 @@
}
/**
- * Sets a string handle to this AudioRecord for metrics collection.
+ * Sets a {@link LogSessionId} instance to this AudioRecord for metrics collection.
*
- * @param logSessionId a string which is used to identify this object
- * to the metrics service. Proper generated Ids must be obtained
- * from the Java metrics service and should be considered opaque.
- * Use null to remove the logSessionId association.
+ * @param logSessionId a {@link LogSessionId} instance which is used to
+ * identify this object to the metrics service. Proper generated
+ * Ids must be obtained from the Java metrics service and should
+ * be considered opaque. Use
+ * {@link LogSessionId#LOG_SESSION_ID_NONE} to remove the
+ * logSessionId association.
* @throws IllegalStateException if AudioRecord not initialized.
- *
- * @hide
*/
- public void setLogSessionId(@Nullable String logSessionId) {
+ public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+ Objects.requireNonNull(logSessionId);
if (mState == STATE_UNINITIALIZED) {
throw new IllegalStateException("AudioRecord not initialized");
}
- native_setLogSessionId(logSessionId);
+ String stringId = logSessionId.getStringId();
+ native_setLogSessionId(stringId);
mLogSessionId = logSessionId;
}
+ /**
+ * Returns the {@link LogSessionId}.
+ */
+ @NonNull
+ public LogSessionId getLogSessionId() {
+ return mLogSessionId;
+ }
+
//---------------------------------------------------------
// Interface definitions
//--------------------
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index bccefdf..7a2b022 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -26,6 +26,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.media.metrics.LogSessionId;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -45,6 +46,7 @@
import java.nio.ByteOrder;
import java.nio.NioUtils;
import java.util.LinkedList;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -567,9 +569,9 @@
/**
* The log session id used for metrics.
- * A null or empty string here means it is not set.
+ * {@link LogSessionId#LOG_SESSION_ID_NONE} here means it is not set.
*/
- private String mLogSessionId;
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
//--------------------------------
// Used exclusively by native code
@@ -4044,24 +4046,35 @@
}
/**
- * Sets a string handle to this AudioTrack for metrics collection.
+ * Sets a {@link LogSessionId} instance to this AudioTrack for metrics collection.
*
- * @param logSessionId a string which is used to identify this object
- * to the metrics service. Proper generated Ids must be obtained
- * from the Java metrics service and should be considered opaque.
- * Use null to remove the logSessionId association.
+ * @param logSessionId a {@link LogSessionId} instance which is used to
+ * identify this object to the metrics service. Proper generated
+ * Ids must be obtained from the Java metrics service and should
+ * be considered opaque. Use
+ * {@link LogSessionId#LOG_SESSION_ID_NONE} to remove the
+ * logSessionId association.
* @throws IllegalStateException if AudioTrack not initialized.
*
- * @hide
*/
- public void setLogSessionId(@Nullable String logSessionId) {
+ public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+ Objects.requireNonNull(logSessionId);
if (mState == STATE_UNINITIALIZED) {
throw new IllegalStateException("track not initialized");
}
- native_setLogSessionId(logSessionId);
+ String stringId = logSessionId.getStringId();
+ native_setLogSessionId(stringId);
mLogSessionId = logSessionId;
}
+ /**
+ * Returns the {@link LogSessionId}.
+ */
+ @NonNull
+ public LogSessionId getLogSessionId() {
+ return mLogSessionId;
+ }
+
//---------------------------------------------------------
// Inner classes
//--------------------
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 4f87fe6..ee945d5 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -158,6 +158,14 @@
oneway void reloadAudioSettings();
+ boolean setSurroundFormatEnabled(int audioFormat, boolean enabled);
+
+ boolean isSurroundFormatEnabled(int audioFormat);
+
+ boolean setEncodedSurroundMode(int mode);
+
+ int getEncodedSurroundMode();
+
oneway void avrcpSupportsAbsoluteVolume(String address, boolean support);
void setSpeakerphoneOn(IBinder cb, boolean on);
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index a7e2b65..b51777c 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -25,7 +25,6 @@
import android.graphics.SurfaceTexture;
import android.hardware.HardwareBuffer;
import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.metrics.PlaybackComponent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -46,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;
@@ -1539,7 +1539,7 @@
</tbody>
</table>
*/
-final public class MediaCodec implements PlaybackComponent {
+final public class MediaCodec {
/**
* Per buffer metadata includes an offset and size specifying
@@ -1697,22 +1697,6 @@
private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
- /**
- * @hide
- */
- @Override
- public void setPlaybackId(@NonNull String playbackId) {
- // TODO: add a native method to pass the ID to the native code for logging.
- mPlaybackId = playbackId;
- }
- /**
- * @hide
- */
- @Override
- public String getPlaybackId() {
- return mPlaybackId;
- }
-
private class EventHandler extends Handler {
private MediaCodec mCodec;
@@ -4707,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/MediaDrm.java b/media/java/android/media/MediaDrm.java
index ae64c02..10b99dc 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -27,7 +27,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
-import android.media.metrics.PlaybackComponent;
+import android.media.metrics.LogSessionId;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -50,6 +50,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -1379,7 +1380,7 @@
public byte[] openSession(@SecurityLevel int level) throws
NotProvisionedException, ResourceBusyException {
byte[] sessionId = openSessionNative(level);
- mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponentImpl(sessionId));
+ mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponent(sessionId));
return sessionId;
}
@@ -2929,8 +2930,8 @@
/**
* Obtain a {@link PlaybackComponent} associated with a DRM session.
- * Call {@link PlaybackComponent#setPlaybackId(String)} on the returned object
- * to associate a playback session with the DRM session.
+ * Call {@link PlaybackComponent#setLogSessionId(LogSessionId)} on
+ * the returned object to associate a playback session with the DRM session.
*
* @param sessionId a DRM session ID obtained from {@link #openSession()}
* @return a {@link PlaybackComponent} associated with the session,
@@ -2945,28 +2946,37 @@
return mPlaybackComponentMap.get(ByteBuffer.wrap(sessionId));
}
- private native void setPlaybackId(byte[] sessionId, String playbackId);
+ private native void setPlaybackId(byte[] sessionId, String logSessionId);
- private final class PlaybackComponentImpl implements PlaybackComponent {
+ /** This class contains the Drm session ID and log session ID */
+ public final class PlaybackComponent {
private final byte[] mSessionId;
- private String mPlaybackId = "";
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
- public PlaybackComponentImpl(byte[] sessionId) {
+ /** @hide */
+ public PlaybackComponent(byte[] sessionId) {
mSessionId = sessionId;
}
- @Override
- public void setPlaybackId(@NonNull String playbackId) {
- if (playbackId == null) {
+
+ /**
+ * Gets the {@link LogSessionId}.
+ */
+ public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+ Objects.requireNonNull(logSessionId);
+ if (logSessionId.getStringId() == null) {
throw new IllegalArgumentException("playbackId is null");
}
- MediaDrm.this.setPlaybackId(mSessionId, playbackId);
- mPlaybackId = playbackId;
+ MediaDrm.this.setPlaybackId(mSessionId, logSessionId.getStringId());
+ mLogSessionId = logSessionId;
}
- @Override
- @NonNull public String getPlaybackId() {
- return mPlaybackId;
+
+ /**
+ * Returns the {@link LogSessionId}.
+ */
+ @NonNull public LogSessionId getLogSessionId() {
+ return mLogSessionId;
}
}
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 8f60330..283f1f1 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -22,6 +22,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
+import android.media.metrics.LogSessionId;
import android.net.Uri;
import android.os.IBinder;
import android.os.IHwBinder;
@@ -40,6 +41,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -73,7 +75,7 @@
* <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
* when used with network-based content.
*/
-final public class MediaExtractor {
+public final class MediaExtractor {
public MediaExtractor() {
native_setup();
}
@@ -768,6 +770,22 @@
public native boolean hasCacheReachedEndOfStream();
/**
+ * Sets the {@link LogSessionId} for MediaExtractor.
+ */
+ public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+ mLogSessionId = Objects.requireNonNull(logSessionId);
+ // TODO: implement native_setPlaybackId(playbackId);
+ }
+
+ /**
+ * Returns the {@link LogSessionId} for MediaExtractor.
+ */
+ @NonNull
+ public LogSessionId getLogSessionId() {
+ return mLogSessionId;
+ }
+
+ /**
* Return Metrics data about the current media container.
*
* @return a {@link PersistableBundle} containing the set of attributes and values
@@ -796,6 +814,7 @@
}
private MediaCas mMediaCas;
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
private long mNativeContext;
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index dd08d8a..f960ff2 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -29,6 +29,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.Camera;
+import android.media.metrics.LogSessionId;
import android.media.permission.Identity;
import android.os.Build;
import android.os.Handler;
@@ -130,6 +131,8 @@
private int mChannelCount;
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
+
/**
* Default constructor.
*
@@ -165,6 +168,27 @@
}
/**
+ * Sets the {@link LogSessionId} for MediaRecorder.
+ *
+ * @param id the global ID for monitoring the MediaRecorder performance
+ */
+ public void setLogSessionId(@NonNull LogSessionId id) {
+ Objects.requireNonNull(id);
+ mLogSessionId = id;
+ setParameter("log-session-id=" + id.getStringId());
+ }
+
+ /**
+ * Returns the {@link LogSessionId} for MediaRecorder.
+ *
+ * @return the global ID for monitoring the MediaRecorder performance
+ */
+ @NonNull
+ public LogSessionId getLogSessionId() {
+ return mLogSessionId;
+ }
+
+ /**
* Sets a {@link android.hardware.Camera} to use for recording.
*
* <p>Use this function to switch quickly between preview and capture mode without a teardown of
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/java/android/media/metrics/LogSessionId.java b/media/java/android/media/metrics/LogSessionId.java
index f68ef4b..41f3093 100644
--- a/media/java/android/media/metrics/LogSessionId.java
+++ b/media/java/android/media/metrics/LogSessionId.java
@@ -17,20 +17,29 @@
package android.media.metrics;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
+import java.util.Objects;
+
/**
* An instances of this class represents the ID of a log session.
*/
public final class LogSessionId {
- private final String mSessionId;
+ @NonNull private final String mSessionId;
- /* package */ LogSessionId(@NonNull String id) {
- mSessionId = id;
- }
+ /**
+ * A {@link LogSessionId} object which is used to clear any existing session ID.
+ */
+ @NonNull public static final LogSessionId LOG_SESSION_ID_NONE = new LogSessionId("");
/** @hide */
@TestApi
+ public LogSessionId(@NonNull String id) {
+ mSessionId = Objects.requireNonNull(id);
+ }
+
+ /** Returns the ID represented by a string. */
@NonNull
public String getStringId() {
return mSessionId;
@@ -40,4 +49,17 @@
public String toString() {
return mSessionId;
}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ LogSessionId that = (LogSessionId) o;
+ return Objects.equals(mSessionId, that.mSessionId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSessionId);
+ }
}
diff --git a/media/java/android/media/metrics/PlaybackComponent.java b/media/java/android/media/metrics/PlaybackComponent.java
deleted file mode 100644
index 1cadf3b..0000000
--- a/media/java/android/media/metrics/PlaybackComponent.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.metrics;
-
-import android.annotation.NonNull;
-
-/**
- * Interface for playback related components used by playback metrics.
- */
-public interface PlaybackComponent {
-
- /**
- * Sets the playback ID of the component.
- */
- void setPlaybackId(@NonNull String playbackId);
-
- /**
- * Gets playback ID.
- */
- @NonNull String getPlaybackId();
-}
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/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 6d7badb..35249f6 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -258,6 +258,9 @@
ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29
ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
ASurfaceTransaction_setOnComplete; # introduced=29
+ ASurfaceTransaction_setPosition; # introduced=31
+ ASurfaceTransaction_setSourceRect; # introduced=31
+ ASurfaceTransaction_setTransform; # introduced=31
ASurfaceTransaction_setVisibility; # introduced=29
ASurfaceTransaction_setZOrder; # introduced=29
ASystemFontIterator_open; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index e8cf63f..195fd5e 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -446,6 +446,44 @@
transaction->setTransformToDisplayInverse(surfaceControl, transformToInverseDisplay);
}
+void ASurfaceTransaction_setSourceRect(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl, const ARect& source) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+ CHECK_VALID_RECT(source);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setCrop(surfaceControl, static_cast<const Rect&>(source));
+}
+
+void ASurfaceTransaction_setPosition(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl, const ARect& destination) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+ CHECK_VALID_RECT(destination);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setFrame(surfaceControl, static_cast<const Rect&>(destination));
+}
+
+void ASurfaceTransaction_setTransform(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl, int32_t transform) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setTransform(surfaceControl, transform);
+ bool transformToInverseDisplay = (NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY & transform) ==
+ NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+ transaction->setTransformToDisplayInverse(surfaceControl, transformToInverseDisplay);
+}
+
void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl,
int8_t transparency) {
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index 3553c1f..fdfb108 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -26,6 +26,7 @@
java_library {
name: "framework-connectivity-protos",
sdk_version: "module_current",
+ min_sdk_version: "30",
proto: {
type: "nano",
},
@@ -81,11 +82,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: [
@@ -97,10 +100,34 @@
"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: [
+ "//apex_available:platform",
+ "com.android.tethering",
+ ],
}
cc_defaults {
@@ -109,13 +136,15 @@
"-Wall",
"-Werror",
"-Wno-unused-parameter",
+ // Don't warn about S API usage even with
+ // min_sdk 30: the library is only loaded
+ // on S+ devices
+ "-Wno-unguarded-availability",
"-Wthread-safety",
],
shared_libs: [
- "libbase",
"liblog",
"libnativehelper",
- "libnetd_client",
],
header_libs: [
"dnsproxyd_protocol_headers",
@@ -137,43 +166,15 @@
cc_library_shared {
name: "libframework-connectivity-jni",
+ min_sdk_version: "30",
defaults: ["libframework-connectivity-defaults"],
srcs: [
+ "jni/android_net_NetworkUtils.cpp",
"jni/onload.cpp",
],
shared_libs: ["libandroid"],
- static_libs: ["libconnectivityframeworkutils"],
+ stl: "libc++_static",
apex_available: [
- "//apex_available:platform",
"com.android.tethering",
],
}
-
-java_library {
- name: "framework-connectivity.impl",
- sdk_version: "module_current",
- 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 4a03c15..41c5609 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -28,6 +28,14 @@
field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION";
field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY";
field public static final String ACTION_PROMPT_UNVALIDATED = "android.net.action.PROMPT_UNVALIDATED";
+ field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000
+ field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000
+ field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000
+ field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4
+ field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1
+ field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
+ field public static final int BLOCKED_REASON_NONE = 0; // 0x0
+ field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
field public static final String PRIVATE_DNS_MODE_OFF = "off";
field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
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/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
index fd4d9db..e8bb42d 100644
--- a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
+++ b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
@@ -37,7 +37,6 @@
#include <utils/Log.h>
#include <utils/misc.h>
-#include "NetdClient.h"
#include "jni.h"
extern "C" {
@@ -113,14 +112,14 @@
}
static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
- jint netId)
+ jint netId, jlong netHandle)
{
- return (jboolean) !setNetworkForResolv(netId);
+ return (jboolean) !android_setprocdns(netHandle);
}
-static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd,
- jint netId) {
- return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd));
+static jint android_net_utils_bindSocketToNetworkHandle(JNIEnv *env, jobject thiz, jobject javaFd,
+ jlong netHandle) {
+ return android_setsocknetwork(netHandle, AFileDescriptor_getFD(env, javaFd));
}
static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
@@ -132,7 +131,7 @@
return true;
}
-static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
+static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jlong netHandle,
jstring dname, jint ns_class, jint ns_type, jint flags) {
const jsize javaCharsCount = env->GetStringLength(dname);
const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
@@ -142,7 +141,8 @@
std::vector<char> queryname(byteCountUTF8 + 1, 0);
env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
- int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
+
+ int fd = android_res_nquery(netHandle, queryname.data(), ns_class, ns_type, flags);
if (fd < 0) {
jniThrowErrnoException(env, "resNetworkQuery", -fd);
@@ -152,12 +152,12 @@
return jniCreateFileDescriptor(env, fd);
}
-static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId,
+static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jlong netHandle,
jbyteArray msg, jint msgLen, jint flags) {
uint8_t data[MAXCMDSIZE];
checkLenAndCopy(env, msg, msgLen, data);
- int fd = resNetworkSend(netId, data, msgLen, flags);
+ int fd = android_res_nsend(netHandle, data, msgLen, flags);
if (fd < 0) {
jniThrowErrnoException(env, "resNetworkSend", -fd);
@@ -172,7 +172,7 @@
int rcode;
std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
- int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
+ int res = android_res_nresult(fd, &rcode, buf.data(), MAXPACKETSIZE);
jniSetFileDescriptorOfFD(env, javaFd, -1);
if (res < 0) {
jniThrowErrnoException(env, "resNetworkResult", -res);
@@ -196,23 +196,22 @@
static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
int fd = AFileDescriptor_getFD(env, javaFd);
- resNetworkCancel(fd);
+ android_res_cancel(fd);
jniSetFileDescriptorOfFD(env, javaFd, -1);
}
static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
- unsigned dnsNetId = 0;
- if (int res = getNetworkForDns(&dnsNetId) < 0) {
- jniThrowErrnoException(env, "getDnsNetId", -res);
+ net_handle_t dnsNetHandle = NETWORK_UNSPECIFIED;
+ if (int res = android_getprocdns(&dnsNetHandle) < 0) {
+ jniThrowErrnoException(env, "getDnsNetwork", -res);
return nullptr;
}
- bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS;
static jclass class_Network = MakeGlobalRefOrDie(
env, FindClassOrDie(env, "android/net/Network"));
- static jmethodID ctor = env->GetMethodID(class_Network, "<init>", "(IZ)V");
- return env->NewObject(
- class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass);
+ static jmethodID method = env->GetStaticMethodID(class_Network, "fromNetworkHandle",
+ "(J)Landroid/net/Network;");
+ return env->CallStaticObjectMethod(class_Network, method, static_cast<jlong>(dnsNetHandle));
}
static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
@@ -261,12 +260,12 @@
{ "bindProcessToNetworkHandle", "(J)Z", (void*) android_net_utils_bindProcessToNetworkHandle },
{ "getBoundNetworkHandleForProcess", "()J", (void*) android_net_utils_getBoundNetworkHandleForProcess },
{ "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
- { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
+ { "bindSocketToNetworkHandle", "(Ljava/io/FileDescriptor;J)I", (void*) android_net_utils_bindSocketToNetworkHandle },
{ "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
{ "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
{ "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
- { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
- { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
+ { "resNetworkSend", "(J[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
+ { "resNetworkQuery", "(JLjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
{ "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
{ "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
{ "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork },
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 8c93676..c8bc662 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -829,6 +829,94 @@
})
public @interface PrivateDnsMode {}
+ /**
+ * Flag to indicate that an app is not subject to any restrictions that could result in its
+ * network access blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_NONE = 0;
+
+ /**
+ * Flag to indicate that an app is subject to Battery saver restrictions that would
+ * result in its network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_BATTERY_SAVER = 1 << 0;
+
+ /**
+ * Flag to indicate that an app is subject to Doze restrictions that would
+ * result in its network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_DOZE = 1 << 1;
+
+ /**
+ * Flag to indicate that an app is subject to App Standby restrictions that would
+ * result in its network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_APP_STANDBY = 1 << 2;
+
+ /**
+ * Flag to indicate that an app is subject to Restricted mode restrictions that would
+ * result in its network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3;
+
+ /**
+ * Flag to indicate that an app is subject to Data saver restrictions that would
+ * result in its metered network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_METERED_REASON_DATA_SAVER = 1 << 16;
+
+ /**
+ * Flag to indicate that an app is subject to user restrictions that would
+ * result in its metered network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 1 << 17;
+
+ /**
+ * Flag to indicate that an app is subject to Device admin restrictions that would
+ * result in its metered network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 1 << 18;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"BLOCKED_"}, value = {
+ BLOCKED_REASON_NONE,
+ BLOCKED_REASON_BATTERY_SAVER,
+ BLOCKED_REASON_DOZE,
+ BLOCKED_REASON_APP_STANDBY,
+ BLOCKED_REASON_RESTRICTED_MODE,
+ BLOCKED_METERED_REASON_DATA_SAVER,
+ BLOCKED_METERED_REASON_USER_RESTRICTED,
+ BLOCKED_METERED_REASON_ADMIN_DISABLED,
+ })
+ public @interface BlockedReason {}
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private final IConnectivityManager mService;
@@ -3238,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
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/Network.java b/packages/Connectivity/framework/src/android/net/Network.java
index 41fad63..1f49033 100644
--- a/packages/Connectivity/framework/src/android/net/Network.java
+++ b/packages/Connectivity/framework/src/android/net/Network.java
@@ -92,6 +92,7 @@
// value in the native/android/net.c NDK implementation.
private static final long HANDLE_MAGIC = 0xcafed00dL;
private static final int HANDLE_MAGIC_SIZE = 32;
+ private static final int USE_LOCAL_NAMESERVERS_FLAG = 0x80000000;
// A boolean to control how getAllByName()/getByName() behaves in the face
// of Private DNS.
@@ -189,7 +190,7 @@
*/
public int getNetIdForResolv() {
return mPrivateDnsBypass
- ? (int) (0x80000000L | (long) netId) // Non-portable DNS resolution flag.
+ ? (USE_LOCAL_NAMESERVERS_FLAG | netId) // Non-portable DNS resolution flag.
: netId;
}
@@ -452,12 +453,13 @@
throw new IllegalArgumentException(
"Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network.");
}
- if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC
- || networkHandle < 0) {
+ if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC) {
throw new IllegalArgumentException(
"Value passed to fromNetworkHandle() is not a network handle.");
}
- return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE));
+ final int netIdForResolv = (int) (networkHandle >>> HANDLE_MAGIC_SIZE);
+ return new Network((netIdForResolv & ~USE_LOCAL_NAMESERVERS_FLAG),
+ ((netIdForResolv & USE_LOCAL_NAMESERVERS_FLAG) != 0) /* privateDnsBypass */);
}
/**
@@ -485,7 +487,7 @@
if (netId == 0) {
return 0L; // make this zero condition obvious for debugging
}
- return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC;
+ return (((long) getNetIdForResolv()) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC;
}
// implement the Parcelable interface
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 f524859..16a49bc 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -92,12 +92,16 @@
@Deprecated
public native static boolean bindProcessToNetworkForHostResolution(int netId);
+ private static native int bindSocketToNetworkHandle(FileDescriptor fd, long netHandle);
+
/**
* Explicitly binds {@code fd} to the network designated by {@code netId}. This
* overrides any binding via {@link #bindProcessToNetwork}.
* @return 0 on success or negative errno on failure.
*/
- public static native int bindSocketToNetwork(FileDescriptor fd, int netId);
+ public static int bindSocketToNetwork(FileDescriptor fd, int netId) {
+ return bindSocketToNetworkHandle(fd, new Network(netId).getNetworkHandle());
+ }
/**
* Determine if {@code uid} can access network designated by {@code netId}.
@@ -108,14 +112,22 @@
return false;
}
+ private static native FileDescriptor resNetworkSend(
+ long netHandle, byte[] msg, int msglen, int flags) throws ErrnoException;
+
/**
* DNS resolver series jni method.
* Issue the query {@code msg} on the network designated by {@code netId}.
* {@code flags} is an additional config to control actual querying behavior.
* @return a file descriptor to watch for read events
*/
- public static native FileDescriptor resNetworkSend(
- int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
+ public static FileDescriptor resNetworkSend(
+ int netId, byte[] msg, int msglen, int flags) throws ErrnoException {
+ return resNetworkSend(new Network(netId).getNetworkHandle(), msg, msglen, flags);
+ }
+
+ private static native FileDescriptor resNetworkQuery(
+ long netHandle, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
/**
* DNS resolver series jni method.
@@ -124,8 +136,11 @@
* {@code flags} is an additional config to control actual querying behavior.
* @return a file descriptor to watch for read events
*/
- public static native FileDescriptor resNetworkQuery(
- int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
+ public static FileDescriptor resNetworkQuery(
+ int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException {
+ return resNetworkQuery(new Network(netId).getNetworkHandle(), dname, nsClass, nsType,
+ flags);
+ }
/**
* DNS resolver series jni method.
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 37dd9ff..b44128b 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -25,7 +25,7 @@
cc_library_shared {
name: "libservice-connectivity",
- // TODO: build against the NDK (sdk_version: "30" for example)
+ min_sdk_version: "30",
cflags: [
"-Wall",
"-Werror",
@@ -36,13 +36,13 @@
"jni/com_android_server_TestNetworkService.cpp",
"jni/onload.cpp",
],
+ stl: "libc++_static",
+ header_libs: [
+ "libbase_headers",
+ ],
shared_libs: [
- "libbase",
"liblog",
"libnativehelper",
- // TODO: remove dependency on ifc_[add/del]_address by having Java code to add/delete
- // addresses, and remove dependency on libnetutils.
- "libnetutils",
],
apex_available: [
"com.android.tethering",
@@ -52,6 +52,7 @@
java_library {
name: "service-connectivity-pre-jarjar",
sdk_version: "system_server_current",
+ min_sdk_version: "30",
srcs: [
":connectivity-service-srcs",
":framework-connectivity-shared-srcs",
@@ -82,7 +83,6 @@
"service-connectivity-protos",
],
apex_available: [
- "//apex_available:platform",
"com.android.tethering",
],
}
@@ -90,6 +90,7 @@
java_library {
name: "service-connectivity-protos",
sdk_version: "system_current",
+ min_sdk_version: "30",
proto: {
type: "nano",
},
@@ -98,7 +99,6 @@
],
libs: ["libprotobuf-java-nano"],
apex_available: [
- "//apex_available:platform",
"com.android.tethering",
],
}
@@ -106,13 +106,14 @@
java_library {
name: "service-connectivity",
sdk_version: "system_server_current",
+ min_sdk_version: "30",
installable: true,
static_libs: [
"service-connectivity-pre-jarjar",
],
jarjar_rules: "jarjar-rules.txt",
apex_available: [
- "//apex_available:platform",
+ "//apex_available:platform", // For arc-services
"com.android.tethering",
],
}
diff --git a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
index fa4501a..d783738 100644
--- a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
+++ b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
@@ -22,6 +22,7 @@
android_app {
name: "ServiceConnectivityResources",
sdk_version: "module_current",
+ min_sdk_version: "30",
resource_dirs: [
"res",
],
diff --git a/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp b/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp
index 36a6fde..e7a40e5 100644
--- a/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp
+++ b/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp
@@ -35,8 +35,6 @@
#include <log/log.h>
-#include "netutils/ifc.h"
-
#include "jni.h"
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
@@ -48,9 +46,8 @@
//------------------------------------------------------------------------------
static void throwException(JNIEnv* env, int error, const char* action, const char* iface) {
- const std::string& msg =
- android::base::StringPrintf("Error %s %s: %s", action, iface, strerror(error));
-
+ const std::string& msg = "Error: " + std::string(action) + " " + std::string(iface) + ": "
+ + std::string(strerror(error));
jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index d6c66b5..5e69a4e 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -17,8 +17,8 @@
// TODO(b/149540986): revert this change.
static_libs: [
- // All other dependent components should be put in
- // "SettingsLibDependenciesWithoutWifiTracker".
+ // All other dependent components should be put in
+ // "SettingsLibDependenciesWithoutWifiTracker".
"WifiTrackerLib",
],
@@ -27,7 +27,10 @@
resource_dirs: ["res"],
- srcs: ["src/**/*.java", "src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
min_sdk_version: "21",
@@ -68,6 +71,7 @@
"SettingsLibUsageProgressBarPreference",
"SettingsLibCollapsingToolbarBaseActivity",
"SettingsLibTwoTargetPreference",
+ "SettingsLibSettingsTransition",
],
}
diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
index 8c208e3..0390e86 100644
--- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
@@ -21,7 +21,7 @@
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingStart="@dimen/app_preference_padding_start"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<LinearLayout
@@ -29,7 +29,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start|center_vertical"
- android:minWidth="56dp"
+ android:minWidth="@dimen/app_icon_min_width"
android:orientation="horizontal"
android:paddingEnd="8dp"
android:paddingTop="4dp"
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/res/values/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
index e1a64d4..1157a34 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
@@ -17,10 +17,10 @@
<resources>
<style name="CollapsingToolbarTitle.Collapsed"
parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
</style>
- <style name="CollapsingToolbarTitle"
- parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <style name="CollapsingToolbarTitle" parent="CollapsingToolbarTitle.Collapsed">
<item name="android:textSize">36sp</item>
</style>
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/FooterPreference/res/layout/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
index 5496a01..7a550ae 100644
--- a/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
+++ b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
@@ -23,6 +23,7 @@
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical"
android:clipToPadding="false">
<LinearLayout
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/SettingsTheme/res/layout/image_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout/image_frame.xml
new file mode 100644
index 0000000..5567790
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout/image_frame.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/icon_min_width"
+ android:gravity="start|center_vertical"
+ android:orientation="horizontal"
+ android:paddingLeft="0dp"
+ android:paddingStart="0dp"
+ android:paddingRight="8dp"
+ android:paddingEnd="8dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:maxWidth="48dp"
+ app:maxHeight="48dp"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml b/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml
deleted file mode 100644
index 4a1b089..0000000
--- a/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml
+++ /dev/null
@@ -1,60 +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.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingRight="?android:attr/listPreferredItemPaddingRight"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:background="?android:attr/selectableItemBackground"
- android:baselineAligned="false"
- android:layout_marginTop="16dp"
- android:gravity="center_vertical"
- style="@style/PreferenceCategoryStartMargin">
-
- <RelativeLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:paddingTop="8dp"
- android:paddingBottom="8dp">
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- style="@style/PreferenceCategoryTitleTextStyle"/>
-
- <TextView
- android:id="@android:id/summary"
- android:ellipsize="end"
- android:singleLine="true"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@android:id/title"
- android:layout_alignLeft="@android:id/title"
- android:layout_alignStart="@android:id/title"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:textColor="?android:attr/textColorSecondary"
- android:maxLines="10"
- style="@style/PreferenceSummaryTextStyle"/>
- </RelativeLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout/settings_dropdown_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout/settings_dropdown_preference.xml
new file mode 100644
index 0000000..87977bd
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout/settings_dropdown_preference.xml
@@ -0,0 +1,35 @@
+<?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.
+ -->
+
+<!-- We use a FrameLayout as we want to place the invisible spinner on top of the other views -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <!-- This spinner should be invisible in the layout and take up no space, when the Preference
+ is clicked the dropdown will appear from this location on screen. -->
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/preference_dropdown_padding_start"
+ android:layout_marginLeft="@dimen/preference_dropdown_padding_start"
+ android:visibility="invisible" />
+
+ <include layout="@layout/settings_preference" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout/settings_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout/settings_preference.xml
new file mode 100644
index 0000000..3a289a7
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout/settings_preference.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false"
+ android:baselineAligned="false">
+
+ <include layout="@layout/image_frame"/>
+
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignLeft="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="10"
+ style="@style/PreferenceSummaryTextStyle"/>
+
+ </RelativeLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="end|center_vertical"
+ android:paddingLeft="16dp"
+ android:paddingStart="16dp"
+ android:paddingRight="0dp"
+ android:paddingEnd="0dp"
+ android:orientation="vertical"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml
new file mode 100644
index 0000000..acf06c4
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <bool name="config_icon_space_reserved">false</bool>
+ <bool name="config_allow_divider">false</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
new file mode 100644
index 0000000..17b6805
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="preference_title_font_size">20sp</dimen>
+ <dimen name="icon_min_width">48dp</dimen>
+ <dimen name="preference_padding_start">24dp</dimen>
+ <dimen name="preference_padding_end">24dp</dimen>
+ <dimen name="app_preference_padding_start">20dp</dimen>
+ <dimen name="app_icon_min_width">52dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/config.xml b/packages/SettingsLib/SettingsTheme/res/values/config.xml
new file mode 100644
index 0000000..a3bb1da
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <bool name="config_icon_space_reserved">true</bool>
+ <bool name="config_allow_divider">true</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
index 9485655..009ae6b 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
@@ -17,4 +17,10 @@
<resources>
<dimen name="secondary_app_icon_size">32dp</dimen>
+ <dimen name="preference_title_font_size">16sp</dimen>
+ <dimen name="icon_min_width">56dp</dimen>
+ <dimen name="preference_padding_start">?android:attr/dialogPreferredPadding</dimen>
+ <dimen name="preference_padding_end">?android:attr/dialogPreferredPadding</dimen>
+ <dimen name="app_preference_padding_start">?android:attr/listPreferredItemPaddingStart</dimen>
+ <dimen name="app_icon_min_width">56dp</dimen>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
index a6623b0..f24e008 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
@@ -15,16 +15,8 @@
limitations under the License.
-->
<resources>
- <style name="TextAppearance.CategoryTitle"
- parent="@*android:style/TextAppearance.DeviceDefault.Body2">
- <item name="android:textAllCaps">true</item>
- <item name="android:textSize">11sp</item>
- <!-- 0.8 Spacing, 0.8/11 = 0.072727273 -->
- <item name="android:letterSpacing">0.072727273</item>
- </style>
-
- <style name="PreferenceCategoryStartMargin">
- <item name="android:paddingLeft">?android:attr/listPreferredItemPaddingLeft</item>
- <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
+ <style name="TextAppearance.PreferenceTitle"
+ parent="@*android:style/TextAppearance.DeviceDefault.ListItem">
+ <item name="android:textSize">@dimen/preference_title_font_size</item>
</style>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml b/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
index 6ca8f577..17596ac2 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
@@ -16,17 +16,54 @@
-->
<resources>
<style name="PreferenceTheme" parent="@style/PreferenceThemeOverlay">
- <item name="footerPreferenceStyle">@style/Preference.Material</item>
<item name="preferenceCategoryStyle">@style/SettingsCategoryPreference</item>
- <!-- For preference category color -->
- <item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle
- </item>
- <item name="preferenceCategoryTitleTextColor">?android:attr/textColorSecondary</item>
+ <item name="preferenceStyle">@style/SettingsPreference</item>
+ <item name="checkBoxPreferenceStyle">@style/SettingsCheckBoxPreference</item>
+ <item name="dialogPreferenceStyle">@style/SettingsPreference</item>
+ <item name="editTextPreferenceStyle">@style/SettingsEditTextPreference</item>
+ <item name="dropdownPreferenceStyle">@style/SettingsDropdownPreference</item>
+ <item name="switchPreferenceStyle">@style/SettingsSwitchPreference</item>
+ <item name="seekBarPreferenceStyle">@style/SettingsSeekbarPreference</item>
+ <item name="footerPreferenceStyle">@style/Preference.Material</item>
</style>
<style name="SettingsCategoryPreference" parent="@style/Preference.Category.Material">
- <item name="android:layout">@layout/preference_category_settings</item>
- <item name="allowDividerAbove">false</item>
- <item name="allowDividerBelow">false</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ <item name="allowDividerAbove">@bool/config_allow_divider</item>
+ <item name="allowDividerBelow">@bool/config_allow_divider</item>
+ </style>
+
+ <style name="SettingsPreference" parent="@style/Preference.Material">
+ <item name="layout">@layout/settings_preference</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingsCheckBoxPreference" parent="@style/Preference.CheckBoxPreference.Material">
+ <item name="layout">@layout/settings_preference</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingsEditTextPreference"
+ parent="@style/Preference.DialogPreference.EditTextPreference.Material">
+ <item name="layout">@layout/settings_preference</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingsDropdownPreference" parent="@style/Preference.DropDown.Material">
+ <item name="layout">@layout/settings_dropdown_preference</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingsSwitchPreference" parent="@style/Preference.SwitchPreference.Material">
+ <item name="layout">@layout/settings_preference</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingsSeekbarPreference" parent="@style/Preference.SeekBarPreference.Material">
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingFooterPreference" parent="@style/Preference.Material">
+ <item name="allowDividerAbove">@bool/config_allow_divider</item>
</style>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
new file mode 100644
index 0000000..36ca684
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <!-- Only using in Settings application -->
+ <style name="Theme.SettingsBase" parent="@android:style/Theme.DeviceDefault.Settings" >
+ <item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle</item>
+ <item name="android:listPreferredItemPaddingStart">@dimen/preference_padding_start</item>
+ <item name="android:listPreferredItemPaddingEnd">@dimen/preference_padding_end</item>
+ <item name="preferenceTheme">@style/PreferenceTheme</item>
+ </style>
+
+ <!-- Using in SubSettings page including injected settings page -->
+ <style name="Theme.SubSettingsBase" parent="Theme.SettingsBase">
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
new file mode 100644
index 0000000..c12f6f7
--- /dev/null
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -0,0 +1,22 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SettingsLibSettingsTransition",
+
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "com.google.android.material_material",
+ ],
+
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
new file mode 100644
index 0000000..11660a5f
--- /dev/null
+++ b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.transition">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml b/packages/SettingsLib/SettingsTransition/res/values/dimens.xml
similarity index 79%
rename from packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml
rename to packages/SettingsLib/SettingsTransition/res/values/dimens.xml
index 4f40256..0630ca8 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml
+++ b/packages/SettingsLib/SettingsTransition/res/values/dimens.xml
@@ -15,8 +15,5 @@
-->
<resources>
- <style name="PreferenceCategoryStartMargin">
- <item name="android:paddingLeft">24dp</item>
- <item name="android:paddingStart">24dp</item>
- </style>
-</resources>
+ <dimen name="settings_shared_axis_x_slide_distance">96dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java b/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java
new file mode 100644
index 0000000..f99fda0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java
@@ -0,0 +1,103 @@
+/*
+ * 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.transition;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+import android.view.Window;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import com.google.android.material.transition.platform.MaterialSharedAxis;
+import com.google.android.material.transition.platform.SlideDistanceProvider;
+
+/**
+ * A helper class to apply Settings Transition
+ */
+public class SettingsTransitionHelper {
+
+ private static final String TAG = "SettingsTransitionHelper";
+ private static final long DURATION = 450L;
+
+ private static MaterialSharedAxis createSettingsSharedAxis(Context context, boolean forward) {
+ final MaterialSharedAxis transition = new MaterialSharedAxis(MaterialSharedAxis.X, forward);
+ transition.excludeTarget(android.R.id.statusBarBackground, true);
+ transition.excludeTarget(android.R.id.navigationBarBackground, true);
+
+ final SlideDistanceProvider forwardDistanceProvider =
+ (SlideDistanceProvider) transition.getPrimaryAnimatorProvider();
+ final int distance = context.getResources().getDimensionPixelSize(
+ R.dimen.settings_shared_axis_x_slide_distance);
+ forwardDistanceProvider.setSlideDistance(distance);
+ transition.setDuration(DURATION);
+
+ final Interpolator interpolator =
+ AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.fast_out_extra_slow_in);
+ transition.setInterpolator(interpolator);
+
+ // TODO(b/177480673): Update fade through threshold once (cl/362065364) is released
+
+ return transition;
+ }
+
+ /**
+ * Apply the forward transition to the {@link Activity}, including Exit Transition and Enter
+ * Transition.
+ *
+ * The Exit Transition takes effect when leaving the page, while the Enter Transition is
+ * triggered when the page is launched/entering.
+ */
+ public static void applyForwardTransition(Activity activity) {
+ if (activity == null) {
+ Log.w(TAG, "applyForwardTransition: Invalid activity!");
+ return;
+ }
+ final Window window = activity.getWindow();
+ if (window == null) {
+ Log.w(TAG, "applyForwardTransition: Invalid window!");
+ return;
+ }
+ final MaterialSharedAxis forward = createSettingsSharedAxis(activity, true);
+ window.setExitTransition(forward);
+ window.setEnterTransition(forward);
+ }
+
+ /**
+ * Apply the backward transition to the {@link Activity}, including Return Transition and
+ * Reenter Transition.
+ *
+ * Return Transition will be used to move Views out of the scene when the Window is preparing
+ * to close. Reenter Transition will be used to move Views in to the scene when returning from a
+ * previously-started Activity.
+ */
+ public static void applyBackwardTransition(Activity activity) {
+ if (activity == null) {
+ Log.w(TAG, "applyBackwardTransition: Invalid activity!");
+ return;
+ }
+ final Window window = activity.getWindow();
+ if (window == null) {
+ Log.w(TAG, "applyBackwardTransition: Invalid window!");
+ return;
+ }
+ final MaterialSharedAxis backward = createSettingsSharedAxis(activity, false);
+ window.setReturnTransition(backward);
+ window.setReenterTransition(backward);
+ }
+}
diff --git a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
index 9130662..17f257d 100644
--- a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
+++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
@@ -72,9 +72,9 @@
private void init(Context context) {
setLayoutResource(R.layout.preference_two_target);
mSmallIconSize = context.getResources().getDimensionPixelSize(
- R.dimen.two_target_pref_small_icon_size);
+ resourceId(context, "dimen", "two_target_pref_small_icon_size"));
mMediumIconSize = context.getResources().getDimensionPixelSize(
- R.dimen.two_target_pref_medium_icon_size);
+ resourceId(context, "dimen", "two_target_pref_medium_icon_size"));
final int secondTargetResId = getSecondTargetResId();
if (secondTargetResId != 0) {
setWidgetLayoutResource(secondTargetResId);
@@ -116,4 +116,8 @@
protected int getSecondTargetResId() {
return 0;
}
+
+ 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/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
index dfde3c7..c61f8a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
@@ -250,6 +250,7 @@
* @param callback Callbacks triggered when recovery status changes.
*/
public void triggerSubsystemRestart(String reason, @NonNull RecoveryStatusCallback callback) {
+ // TODO: b/183530649 : clean-up or make use of the `reason` argument
mHandler.post(() -> {
boolean someSubsystemRestarted = false;
@@ -264,7 +265,7 @@
}
if (isWifiEnabled()) {
- mWifiManager.restartWifiSubsystem(reason);
+ mWifiManager.restartWifiSubsystem();
mWifiRestartInProgress = true;
someSubsystemRestarted = true;
startTrackingWifiRestart();
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 1474f18..f62ca32 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -86,6 +86,15 @@
}
}
+ @Override
+ protected void onDeveloperOptionsSwitchEnabled() {
+ super.onDeveloperOptionsSwitchEnabled();
+ if (isAvailable()) {
+ mPreference.setDisabledByAdmin(
+ checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
+ }
+ }
+
public void enablePreference(boolean enabled) {
if (isAvailable()) {
mPreference.setEnabled(enabled);
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 beee03b..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
@@ -16,6 +16,7 @@
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.view.View;
import android.view.ViewGroup;
@@ -34,7 +35,31 @@
}
View createDetailView(Context context, View convertView, ViewGroup parent);
+
+ /**
+ * @return intent for opening more settings related to this detail panel. If null, the more
+ * settings button will not be shown
+ */
Intent getSettingsIntent();
+
+ /**
+ * @return resource id of the string to use for opening the settings intent. If
+ * {@code Resources.ID_NULL}, then use the default string:
+ * {@code com.android.systemui.R.string.quick_settings_more_settings}
+ */
+ default int getSettingsText() {
+ 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/drawable-television/volume_row_seekbar.xml b/packages/SystemUI/res/drawable-television/volume_row_seekbar.xml
new file mode 100644
index 0000000..e49fc15
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/volume_row_seekbar.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingMode="stack">
+ <item android:id="@android:id/background"
+ android:gravity="center_vertical|fill_horizontal">
+ <layer-list>
+ <item android:id="@+id/volume_seekbar_background_solid">
+ <shape>
+ <size android:height="@dimen/volume_dialog_slider_width" />
+ <solid android:color="@color/tv_volume_dialog_seek_bar_background"/>
+ <corners android:radius="@dimen/volume_dialog_slider_corner_radius" />
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+ <item android:id="@android:id/progress"
+ android:gravity="center_vertical|fill_horizontal">
+ <com.android.systemui.util.RoundedCornerProgressDrawable
+ android:drawable="@drawable/volume_row_seekbar_progress"
+ />
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable-television/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable-television/volume_row_seekbar_progress.xml
new file mode 100644
index 0000000..bce193a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/volume_row_seekbar_progress.xml
@@ -0,0 +1,29 @@
+<?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.
+ -->
+
+<!-- Progress drawable for volume row SeekBars. This is the accent-colored round rect that moves up
+ and down as the progress value changes. -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true">
+ <item android:id="@+id/volume_seekbar_progress_solid">
+ <shape android:shape="rectangle">
+ <size android:height="@dimen/volume_dialog_slider_width"/>
+ <solid android:color="@color/tv_volume_dialog_seek_bar_fill" />
+ <corners android:radius="@dimen/volume_dialog_slider_width" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml b/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml
deleted file mode 100644
index fe76b63..0000000
--- a/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@android:id/background">
- <shape android:shape="rectangle">
- <solid android:color="@color/tv_volume_dialog_seek_bar_background" />
- <corners android:radius="@dimen/tv_volume_seek_bar_width" />
- </shape>
- </item>
- <item android:id="@android:id/progress">
- <clip>
- <shape android:shape="rectangle">
- <solid android:color="@color/tv_volume_dialog_seek_bar_fill" />
- <corners android:radius="@dimen/tv_volume_seek_bar_width" />
- </shape>
- </clip>
- </item>
-</layer-list>
diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar.xml b/packages/SystemUI/res/drawable/volume_row_seekbar.xml
index b0e0ed5..a845e73 100644
--- a/packages/SystemUI/res/drawable/volume_row_seekbar.xml
+++ b/packages/SystemUI/res/drawable/volume_row_seekbar.xml
@@ -25,9 +25,9 @@
<layer-list>
<item android:id="@+id/volume_seekbar_background_solid">
<shape>
- <size android:height="@dimen/volume_dialog_panel_width" />
+ <size android:height="@dimen/volume_dialog_slider_width" />
<solid android:color="?android:attr/colorBackgroundFloating" />
- <corners android:radius="@dimen/volume_dialog_panel_width_half" />
+ <corners android:radius="@dimen/volume_dialog_slider_corner_radius" />
</shape>
</item>
<item
@@ -53,4 +53,4 @@
android:drawable="@drawable/volume_row_seekbar_progress"
/>
</item>
-</layer-list>
\ No newline at end of file
+</layer-list>
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
index 4f6cb01..0dec981 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -17,7 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:tag="row"
android:layout_width="wrap_content"
- android:layout_height="@dimen/volume_dialog_row_height"
+ android:layout_height="@dimen/volume_dialog_panel_height"
android:background="@android:color/transparent"
android:clipChildren="false"
android:clipToPadding="false"
@@ -54,18 +54,18 @@
<FrameLayout
android:id="@+id/volume_row_slider_frame"
android:layout_width="match_parent"
- android:layout_height="@dimen/volume_dialog_row_height">
+ android:layout_height="@dimen/volume_dialog_panel_height">
<SeekBar
android:id="@+id/volume_row_slider"
android:clickable="false"
- android:layout_width="@dimen/volume_dialog_row_height"
+ android:layout_width="@dimen/volume_dialog_panel_height"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layoutDirection="ltr"
- android:maxHeight="@dimen/tv_volume_seek_bar_width"
- android:minHeight="@dimen/tv_volume_seek_bar_width"
+ android:maxHeight="@dimen/volume_dialog_slider_width"
+ android:minHeight="@dimen/volume_dialog_slider_width"
+ android:progressDrawable="@drawable/volume_row_seekbar"
android:thumb="@drawable/tv_volume_row_seek_thumb"
- android:progressDrawable="@drawable/tv_volume_row_seek_bar"
android:splitTrack="false"
android:rotation="270" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/people_tile_large_with_content.xml b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
index f2341b5..9990244 100644
--- a/packages/SystemUI/res/layout/people_tile_large_with_content.xml
+++ b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
@@ -22,27 +22,50 @@
android:padding="16dp"
android:orientation="vertical">
- <LinearLayout
- android:layout_width="wrap_content"
+ <RelativeLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="start|top"
- android:orientation="horizontal">
+ android:gravity="start|top">
- <ImageView
- android:id="@+id/person_icon"
- android:layout_marginStart="-2dp"
- android:layout_marginTop="-2dp"
+ <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1" />
+ android:layout_alignParentStart="true"
+ android:gravity="start|top"
+ android:orientation="horizontal">
- <ImageView
- android:id="@+id/availability"
- android:layout_marginStart="-2dp"
- android:layout_width="10dp"
- android:layout_height="10dp"
- android:background="@drawable/circle_green_10dp" />
- </LinearLayout>
+ <ImageView
+ android:id="@+id/person_icon"
+ android:layout_marginStart="-2dp"
+ android:layout_marginTop="-2dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+ <ImageView
+ android:id="@+id/availability"
+ android:layout_marginStart="-2dp"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="@drawable/circle_green_10dp" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/messages_count"
+ android:layout_alignParentEnd="true"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:background="@drawable/people_space_messages_count_background"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ />
+ </RelativeLayout>
<TextView
android:layout_gravity="center"
diff --git a/packages/SystemUI/res/layout/people_tile_small.xml b/packages/SystemUI/res/layout/people_tile_small.xml
index 914ee3c..f0ab187 100644
--- a/packages/SystemUI/res/layout/people_tile_small.xml
+++ b/packages/SystemUI/res/layout/people_tile_small.xml
@@ -33,20 +33,36 @@
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1" />
+ android:layout_weight="1"
+ android:paddingBottom="4dp"/>
<ImageView
android:id="@+id/predefined_icon"
android:layout_gravity="center"
- android:paddingTop="4dp"
android:layout_width="18dp"
android:layout_height="22dp"
android:layout_weight="1" />
<TextView
+ android:id="@+id/messages_count"
+ android:layout_gravity="center"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:background="@drawable/people_space_messages_count_background"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+
+ <TextView
android:id="@+id/name"
android:layout_gravity="center"
- android:paddingTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index fda59b5..1d5bca9 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -54,6 +54,7 @@
android:layout_width="@dimen/volume_row_slider_height"
android:layout_height="match_parent"
android:layout_gravity="center"
+ android:thumb="@android:color/transparent"
android:rotation="270" />
</FrameLayout>
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-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml
index 95ff59b..8c90573 100644
--- a/packages/SystemUI/res/values-land-television/dimens.xml
+++ b/packages/SystemUI/res/values-land-television/dimens.xml
@@ -16,14 +16,17 @@
<resources>
<!-- Height of volume bar -->
- <dimen name="volume_dialog_row_height">190dp</dimen>
- <dimen name="volume_dialog_row_width">48dp</dimen>
+ <dimen name="volume_dialog_panel_height">190dp</dimen>
+ <dimen name="volume_dialog_panel_width">48dp</dimen>
+ <dimen name="volume_dialog_panel_width_half">24dp</dimen>
<dimen name="volume_dialog_panel_transparent_padding">24dp</dimen>
+ <dimen name="volume_dialog_slider_width">4dp</dimen>
+ <dimen name="volume_dialog_slider_corner_radius">@dimen/volume_dialog_slider_width</dimen>
+
<dimen name="tv_volume_dialog_bubble_size">36dp</dimen>
<dimen name="tv_volume_dialog_corner_radius">36dp</dimen>
<dimen name="tv_volume_dialog_row_padding">6dp</dimen>
<dimen name="tv_volume_number_text_size">16sp</dimen>
- <dimen name="tv_volume_seek_bar_width">4dp</dimen>
<dimen name="tv_volume_seek_bar_thumb_diameter">24dp</dimen>
<dimen name="tv_volume_seek_bar_thumb_focus_ring_width">8dp</dimen>
<dimen name="tv_volume_icons_size">20dp</dimen>
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 af6df32..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 -->
@@ -591,4 +591,12 @@
<!-- Determines whether to allow the nav bar handle to be forced to be opaque. -->
<bool name="allow_force_nav_bar_handle_opaque">true</bool>
+
+ <!-- Whether a transition of ACTIVITY_TYPE_DREAM to the home app should play a home sound
+ effect -->
+ <bool name="config_playHomeSoundAfterDream">false</bool>
+
+ <!-- Whether a transition of ACTIVITY_TYPE_ASSISTANT to the home app should play a home sound
+ effect -->
+ <bool name="config_playHomeSoundAfterAssistant">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2393b74..d5c6398 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -462,6 +462,10 @@
<dimen name="volume_dialog_slider_height">116dp</dimen>
+ <dimen name="volume_dialog_slider_width">@dimen/volume_dialog_panel_width</dimen>
+
+ <dimen name="volume_dialog_slider_corner_radius">@dimen/volume_dialog_panel_width_half</dimen>
+
<dimen name="volume_dialog_ringer_size">64dp</dimen>
<dimen name="volume_dialog_ringer_icon_padding">20dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 94bf86a..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] -->
@@ -889,8 +887,12 @@
<string name="quick_settings_color_space_label">Color correction mode</string>
<!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] -->
<string name="quick_settings_more_settings">More settings</string>
+ <!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] -->
+ <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] -->
@@ -1282,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/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 72dd72e..02a8958 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -487,5 +487,9 @@
mView.setLayoutParams(lp);
}
}
+
+ if (mKeyguardSecurityContainerController != null) {
+ mKeyguardSecurityContainerController.updateResources();
+ }
}
}
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/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 4887767..708b2d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -33,7 +33,6 @@
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
-import android.view.OrientationEventListener;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
@@ -107,7 +106,6 @@
private boolean mOneHandedMode = false;
private SecurityMode mSecurityMode = SecurityMode.Invalid;
private ViewPropertyAnimator mRunningOneHandedAnimator;
- private final OrientationEventListener mOrientationEventListener;
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -247,13 +245,6 @@
super(context, attrs, defStyle);
mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
mViewConfiguration = ViewConfiguration.get(context);
-
- mOrientationEventListener = new OrientationEventListener(context) {
- @Override
- public void onOrientationChanged(int orientation) {
- updateLayoutForSecurityMode(mSecurityMode);
- }
- };
}
void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
@@ -262,7 +253,6 @@
updateBiometricRetry(securityMode, faceAuthEnabled);
updateLayoutForSecurityMode(securityMode);
- mOrientationEventListener.enable();
}
void updateLayoutForSecurityMode(SecurityMode securityMode) {
@@ -385,7 +375,6 @@
mAlertDialog = null;
}
mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
- mOrientationEventListener.disable();
}
@Override
@@ -663,6 +652,15 @@
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ // After a layout pass, we need to re-place the inner bouncer, as our bounds may have
+ // changed.
+ updateSecurityViewLocation(/* animate= */false);
+ }
+
void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
String message = null;
switch (userType) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index ccba1d5..760eaec 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -29,6 +29,7 @@
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.metrics.LogMaker;
import android.os.UserHandle;
import android.util.Log;
@@ -74,6 +75,8 @@
private final SecurityCallback mSecurityCallback;
private final ConfigurationController mConfigurationController;
+ private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
+
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
@@ -212,6 +215,7 @@
mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
mKeyguardSecurityCallback);
mConfigurationController = configurationController;
+ mLastOrientation = getResources().getConfiguration().orientation;
}
@Override
@@ -498,6 +502,19 @@
return getCurrentSecurityController();
}
+ /**
+ * Apply keyguard configuration from the currently active resources. This can be called when the
+ * device configuration changes, to re-apply some resources that are qualified on the device
+ * configuration.
+ */
+ public void updateResources() {
+ int newOrientation = getResources().getConfiguration().orientation;
+ if (newOrientation != mLastOrientation) {
+ mLastOrientation = newOrientation;
+ mView.updateLayoutForSecurityMode(mCurrentSecurityMode);
+ }
+ }
+
static class Factory {
private final KeyguardSecurityContainer mView;
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/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 7e7cdce..6812f77 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -46,7 +46,6 @@
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Named;
@@ -107,11 +106,14 @@
}
};
- private final FalsingDataProvider.GestureCompleteListener mGestureCompleteListener =
- new FalsingDataProvider.GestureCompleteListener() {
+ private final FalsingDataProvider.GestureFinalizedListener mGestureFinalizedListener =
+ new FalsingDataProvider.GestureFinalizedListener() {
@Override
- public void onGestureComplete(long completionTimeMs) {
+ public void onGestureFinalized(long completionTimeMs) {
if (mPriorResults != null) {
+ boolean boolResult = mPriorResults.stream().anyMatch(
+ FalsingClassifier.Result::isFalse);
+
mPriorResults.forEach(result -> {
if (result.isFalse()) {
String reason = result.getReason();
@@ -121,8 +123,28 @@
}
});
+ if (Build.IS_ENG || Build.IS_USERDEBUG) {
+ // Copy motion events, as the results returned by
+ // #getRecentMotionEvents are recycled elsewhere.
+ RECENT_SWIPES.add(new DebugSwipeRecord(
+ boolResult,
+ mPriorInteractionType,
+ mDataProvider.getRecentMotionEvents().stream().map(
+ motionEvent -> new XYDt(
+ (int) motionEvent.getX(),
+ (int) motionEvent.getY(),
+ (int) (motionEvent.getEventTime()
+ - motionEvent.getDownTime())))
+ .collect(Collectors.toList())));
+ while (RECENT_SWIPES.size() > RECENT_INFO_LOG_SIZE) {
+ RECENT_SWIPES.remove();
+ }
+ }
+
+
mHistoryTracker.addResults(mPriorResults, completionTimeMs);
mPriorResults = null;
+ mPriorInteractionType = Classifier.GENERIC;
} else {
// Gestures that were not classified get treated as a false.
mHistoryTracker.addResults(
@@ -135,6 +157,7 @@
};
private Collection<FalsingClassifier.Result> mPriorResults;
+ private @Classifier.InteractionType int mPriorInteractionType = Classifier.GENERIC;
@Inject
public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
@@ -154,7 +177,7 @@
mTestHarness = testHarness;
mDataProvider.addSessionListener(mSessionListener);
- mDataProvider.addGestureCompleteListener(mGestureCompleteListener);
+ mDataProvider.addGestureCompleteListener(mGestureFinalizedListener);
mHistoryTracker.addBeliefListener(mBeliefListener);
}
@@ -165,50 +188,33 @@
@Override
public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+ mPriorInteractionType = interactionType;
if (skipFalsing()) {
return false;
}
- boolean result;
+ final boolean booleanResult;
if (!mTestHarness && !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()) {
- Stream<FalsingClassifier.Result> results =
- mClassifiers.stream().map(falsingClassifier ->
- falsingClassifier.classifyGesture(
- interactionType,
- mHistoryTracker.falseBelief(),
- mHistoryTracker.falseConfidence()));
- mPriorResults = new ArrayList<>();
final boolean[] localResult = {false};
- results.forEach(classifierResult -> {
- localResult[0] |= classifierResult.isFalse();
- mPriorResults.add(classifierResult);
- });
- result = localResult[0];
+ mPriorResults = mClassifiers.stream().map(falsingClassifier -> {
+ FalsingClassifier.Result r = falsingClassifier.classifyGesture(
+ interactionType,
+ mHistoryTracker.falseBelief(),
+ mHistoryTracker.falseConfidence());
+ localResult[0] |= r.isFalse();
+
+ return r;
+ }).collect(Collectors.toList());
+ booleanResult = localResult[0];
} else {
- result = false;
+ booleanResult = false;
mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(1));
}
- logDebug("False Gesture: " + result);
+ logDebug("False Gesture: " + booleanResult);
- if (Build.IS_ENG || Build.IS_USERDEBUG) {
- // Copy motion events, as the passed in list gets emptied out elsewhere in the code.
- RECENT_SWIPES.add(new DebugSwipeRecord(
- result,
- interactionType,
- mDataProvider.getRecentMotionEvents().stream().map(
- motionEvent -> new XYDt(
- (int) motionEvent.getX(),
- (int) motionEvent.getY(),
- (int) (motionEvent.getEventTime() - motionEvent.getDownTime())))
- .collect(Collectors.toList())));
- while (RECENT_SWIPES.size() > RECENT_INFO_LOG_SIZE) {
- RECENT_SWIPES.remove();
- }
- }
-
- return result;
+ return booleanResult;
}
@Override
@@ -354,7 +360,7 @@
@Override
public void cleanup() {
mDataProvider.removeSessionListener(mSessionListener);
- mDataProvider.removeGestureCompleteListener(mGestureCompleteListener);
+ mDataProvider.removeGestureCompleteListener(mGestureFinalizedListener);
mClassifiers.forEach(FalsingClassifier::cleanup);
mFalsingBeliefListeners.clear();
mHistoryTracker.removeBeliefListener(mBeliefListener);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index f665a74..1aaa139 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -45,7 +45,7 @@
private final float mYdpi;
private final List<SessionListener> mSessionListeners = new ArrayList<>();
private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>();
- private final List<GestureCompleteListener> mGestureCompleteListeners = new ArrayList<>();
+ private final List<GestureFinalizedListener> mGestureFinalizedListeners = new ArrayList<>();
private TimeLimitedMotionEventBuffer mRecentMotionEvents =
new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
@@ -90,7 +90,7 @@
mMotionEventListeners.forEach(listener -> listener.onMotionEvent(motionEvent));
- // We explicitly do not complete a gesture on UP or CANCEL events.
+ // We explicitly do not "finalize" a gesture on UP or CANCEL events.
// We wait for the next gesture to start before marking the prior gesture as complete. This
// has multiple benefits. First, it makes it trivial to track the "current" or "recent"
// gesture, as it will always be found in mRecentMotionEvents. Second, and most importantly,
@@ -102,7 +102,7 @@
private void completePriorGesture() {
if (!mRecentMotionEvents.isEmpty()) {
- mGestureCompleteListeners.forEach(listener -> listener.onGestureComplete(
+ mGestureFinalizedListeners.forEach(listener -> listener.onGestureFinalized(
mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime()));
mPriorMotionEvents = mRecentMotionEvents;
@@ -312,14 +312,14 @@
mMotionEventListeners.remove(listener);
}
- /** Register a {@link GestureCompleteListener}. */
- public void addGestureCompleteListener(GestureCompleteListener listener) {
- mGestureCompleteListeners.add(listener);
+ /** Register a {@link GestureFinalizedListener}. */
+ public void addGestureCompleteListener(GestureFinalizedListener listener) {
+ mGestureFinalizedListeners.add(listener);
}
- /** Unregister a {@link GestureCompleteListener}. */
- public void removeGestureCompleteListener(GestureCompleteListener listener) {
- mGestureCompleteListeners.remove(listener);
+ /** Unregister a {@link GestureFinalizedListener}. */
+ public void removeGestureCompleteListener(GestureFinalizedListener listener) {
+ mGestureFinalizedListeners.remove(listener);
}
void onSessionStarted() {
@@ -362,8 +362,12 @@
}
/** Callback to be alerted when the current gesture ends. */
- public interface GestureCompleteListener {
- /** */
- void onGestureComplete(long completionTimeMs);
+ public interface GestureFinalizedListener {
+ /**
+ * Called just before a new gesture starts.
+ *
+ * Any pending work on a prior gesture can be considered cemented in place.
+ */
+ void onGestureFinalized(long completionTimeMs);
}
}
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/media/systemsounds/HomeSoundEffectController.java b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
index dd3d02a..31e4939 100644
--- a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
@@ -16,38 +16,64 @@
package com.android.systemui.media.systemsounds;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.media.AudioManager;
+import android.util.Slog;
+import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import javax.inject.Inject;
/**
- * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a sound is played
- * when the home task moves to front and the last task that moved to front was not the home task.
+ * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a
+ * {@link TaskStackChangeListener} is registered to play a home sound effect when conditions
+ * documented at {@link #handleTaskStackChanged} apply.
*/
@SysUISingleton
public class HomeSoundEffectController extends SystemUI {
+ private static final String TAG = "HomeSoundEffectController";
private final AudioManager mAudioManager;
private final TaskStackChangeListeners mTaskStackChangeListeners;
+ private final ActivityManagerWrapper mActivityManagerWrapper;
+ private final PackageManager mPm;
+ private final boolean mPlayHomeSoundAfterAssistant;
+ private final boolean mPlayHomeSoundAfterDream;
// Initialize true because home sound should not be played when the system boots.
private boolean mIsLastTaskHome = true;
+ // mLastHomePackageName could go out of sync in rare circumstances if launcher changes,
+ // but it's cheaper than the alternative and potential impact is low
+ private String mLastHomePackageName;
+ private @WindowConfiguration.ActivityType int mLastActivityType;
+ private boolean mLastActivityHasNoHomeSound = false;
+ private int mLastTaskId;
@Inject
public HomeSoundEffectController(
Context context,
AudioManager audioManager,
- TaskStackChangeListeners taskStackChangeListeners) {
+ TaskStackChangeListeners taskStackChangeListeners,
+ ActivityManagerWrapper activityManagerWrapper,
+ PackageManager packageManager) {
super(context);
mAudioManager = audioManager;
mTaskStackChangeListeners = taskStackChangeListeners;
+ mActivityManagerWrapper = activityManagerWrapper;
+ mPm = packageManager;
+ mPlayHomeSoundAfterAssistant = context.getResources().getBoolean(
+ R.bool.config_playHomeSoundAfterAssistant);
+ mPlayHomeSoundAfterDream = context.getResources().getBoolean(
+ R.bool.config_playHomeSoundAfterDream);
}
@Override
@@ -56,27 +82,94 @@
mTaskStackChangeListeners.registerTaskStackListener(
new TaskStackChangeListener() {
@Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
- handleHomeTaskMovedToFront(taskInfo);
+ public void onTaskStackChanged() {
+ ActivityManager.RunningTaskInfo currentTask =
+ mActivityManagerWrapper.getRunningTask();
+ if (currentTask == null || currentTask.topActivityInfo == null) {
+ return;
+ }
+ handleTaskStackChanged(currentTask);
}
});
}
}
- private boolean isHomeTask(ActivityManager.RunningTaskInfo taskInfo) {
- return taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME;
+ private boolean hasFlagNoSound(ActivityInfo activityInfo) {
+ if ((activityInfo.privateFlags & ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND) == 0) {
+ // Only allow flag if app has permission
+ if (mPm.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS,
+ activityInfo.packageName) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ } else {
+ Slog.w(TAG,
+ "Activity has flag playHomeTransition set to false but doesn't hold "
+ + "required permission "
+ + Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS);
+ return false;
+ }
+ }
+ return false;
}
/**
- * To enable a home sound, check if the home app moves to front.
+ * The home sound is played if all of the following conditions are met:
+ * <ul>
+ * <li>The last task which moved to front was not home. This avoids playing the sound
+ * e.g. after FallbackHome transitions to home, another activity of the home app like a
+ * notification panel moved to front, or in case the home app crashed.</li>
+ * <li>The current activity which moved to front is home</li>
+ * <li>The topActivity of the last task has {@link android.R.attr#playHomeTransitionSound} set
+ * to <code>true</code>.</li>
+ * <li>The topActivity of the last task is not of type
+ * {@link WindowConfiguration#ACTIVITY_TYPE_ASSISTANT} if config_playHomeSoundAfterAssistant is
+ * set to <code>false</code> (default).</li>
+ * <li>The topActivity of the last task is not of type
+ * {@link WindowConfiguration#ACTIVITY_TYPE_DREAM} if config_playHomeSoundAfterDream is
+ * set to <code>false</code> (default).</li>
+ * </ul>
*/
- private void handleHomeTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
- boolean isCurrentTaskHome = isHomeTask(taskInfo);
- // If the last task is home we don't want to play the home sound. This avoids playing
- // the home sound after FallbackHome transitions to Home
- if (!mIsLastTaskHome && isCurrentTaskHome) {
+ private boolean shouldPlayHomeSoundForCurrentTransition(
+ ActivityManager.RunningTaskInfo currentTask) {
+ boolean isHomeActivity =
+ currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME;
+ if (currentTask.taskId == mLastTaskId) {
+ return false;
+ }
+ if (mIsLastTaskHome || !isHomeActivity) {
+ return false;
+ }
+ if (mLastActivityHasNoHomeSound) {
+ return false;
+ }
+ if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
+ && !mPlayHomeSoundAfterAssistant) {
+ return false;
+ }
+ if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM
+ && !mPlayHomeSoundAfterDream) {
+ return false;
+ }
+ return true;
+ }
+
+ private void updateLastTaskInfo(ActivityManager.RunningTaskInfo currentTask) {
+ mLastTaskId = currentTask.taskId;
+ mLastActivityType = currentTask.topActivityType;
+ mLastActivityHasNoHomeSound = hasFlagNoSound(currentTask.topActivityInfo);
+ boolean isHomeActivity =
+ currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME;
+ boolean isHomePackage = currentTask.topActivityInfo.packageName.equals(
+ mLastHomePackageName);
+ mIsLastTaskHome = isHomeActivity || isHomePackage;
+ if (isHomeActivity && !isHomePackage) {
+ mLastHomePackageName = currentTask.topActivityInfo.packageName;
+ }
+ }
+
+ private void handleTaskStackChanged(ActivityManager.RunningTaskInfo frontTask) {
+ if (shouldPlayHomeSoundForCurrentTransition(frontTask)) {
mAudioManager.playSoundEffect(AudioManager.FX_HOME);
}
- mIsLastTaskHome = isCurrentTaskHome;
+ updateLastTaskInfo(frontTask);
}
}
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/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index bc196bf..8d1b712 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -361,10 +361,13 @@
views.setViewVisibility(R.id.image, View.GONE);
views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
}
- if (mTile.getMessagesCount() > 1 && mLayoutSize == LAYOUT_MEDIUM) {
+ if (mTile.getMessagesCount() > 1) {
views.setViewVisibility(R.id.messages_count, View.VISIBLE);
views.setTextViewText(R.id.messages_count,
getMessagesCountText(mTile.getMessagesCount()));
+ if (mLayoutSize == LAYOUT_SMALL) {
+ views.setViewVisibility(R.id.predefined_icon, View.GONE);
+ }
}
// TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile and
// subtract 1 from maxLines when present.
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 9967936..d54d3f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.drawable.Animatable;
import android.util.AttributeSet;
import android.util.SparseArray;
@@ -139,8 +140,12 @@
}
private void updateDetailText() {
- mDetailDoneButton.setText(R.string.quick_settings_done);
- mDetailSettingsButton.setText(R.string.quick_settings_more_settings);
+ 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);
}
public void updateResources() {
@@ -218,6 +223,7 @@
mQsPanelController.setGridContentVisibility(true);
mQsPanelCallback.onScanStateChanged(false);
}
+ updateDetailText();
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
if (mShouldAnimate) {
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/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 0da441d..83558cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -822,6 +822,11 @@
}
@Override
+ public int getSettingsText() {
+ return R.string.quick_settings_more_user_settings;
+ }
+
+ @Override
public Boolean getToggleState() {
return null;
}
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/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 5dc7006..044d828 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -546,9 +546,10 @@
row.sliderBgSolid = seekbarBgDrawable.findDrawableByLayerId(
R.id.volume_seekbar_background_solid);
- row.sliderBgIcon = (AlphaTintDrawableWrapper)
- ((RotateDrawable) seekbarBgDrawable.findDrawableByLayerId(
- R.id.volume_seekbar_background_icon)).getDrawable();
+ final Drawable sliderBgIcon = seekbarBgDrawable.findDrawableByLayerId(
+ R.id.volume_seekbar_background_icon);
+ row.sliderBgIcon = sliderBgIcon != null ? (AlphaTintDrawableWrapper)
+ ((RotateDrawable) sliderBgIcon).getDrawable() : null;
final LayerDrawable seekbarProgressDrawable = (LayerDrawable)
((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId(
@@ -556,13 +557,12 @@
row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId(
R.id.volume_seekbar_progress_solid);
-
- row.sliderProgressIcon = (AlphaTintDrawableWrapper)
- ((RotateDrawable) seekbarProgressDrawable.findDrawableByLayerId(
- R.id.volume_seekbar_progress_icon)).getDrawable();
+ final Drawable sliderProgressIcon = seekbarProgressDrawable.findDrawableByLayerId(
+ R.id.volume_seekbar_progress_icon);
+ row.sliderProgressIcon = sliderProgressIcon != null ? (AlphaTintDrawableWrapper)
+ ((RotateDrawable) sliderProgressIcon).getDrawable() : null;
row.slider.setProgressDrawable(seekbarDrawable);
- row.slider.setThumb(null);
row.icon = row.view.findViewById(R.id.volume_row_icon);
@@ -1484,10 +1484,14 @@
mContext, android.R.attr.colorBackgroundFloating);
row.sliderProgressSolid.setTintList(colorTint);
- row.sliderBgIcon.setTintList(colorTint);
+ if (row.sliderBgIcon != null) {
+ row.sliderBgIcon.setTintList(colorTint);
+ }
row.sliderBgSolid.setTintList(bgTint);
- row.sliderProgressIcon.setTintList(bgTint);
+ if (row.sliderProgressIcon != null) {
+ row.sliderProgressIcon.setTintList(bgTint);
+ }
if (row.icon != null) {
row.icon.setImageTintList(colorTint);
@@ -1878,8 +1882,12 @@
icon.setImageResource(iconRes);
}
- sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
- sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
+ if (sliderProgressIcon != null) {
+ sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
+ }
+ if (sliderBgIcon != null) {
+ sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index ddfa63a..709ccd4 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -16,26 +16,18 @@
package com.android.systemui.wmshell;
-import static android.os.Process.THREAD_PRIORITY_DISPLAY;
-import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
-
-import android.animation.AnimationHandler;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.os.HandlerThread;
import android.view.IWindowManager;
import android.view.WindowManager;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.R;
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.wm.shell.FullscreenTaskListener;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellCommandHandler;
@@ -53,13 +45,11 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
@@ -99,110 +89,9 @@
* dependencies that are device/form factor SystemUI implementation specific should go into their
* respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
*/
-@Module
+@Module(includes = WMShellConcurrencyModule.class)
public abstract class WMShellBaseModule {
- /**
- * Returns whether to enable a separate shell thread for the shell features.
- */
- private static boolean enableShellMainThread(Context context) {
- return context.getResources().getBoolean(R.bool.config_enableShellMainThread);
- }
-
- //
- // Shell Concurrency - Components used for managing threading in the Shell and SysUI
- //
-
- /**
- * Provide a SysUI main-thread Executor.
- */
- @WMSingleton
- @Provides
- @Main
- public static ShellExecutor provideSysUIMainExecutor(@Main Handler sysuiMainHandler) {
- return new HandlerExecutor(sysuiMainHandler);
- }
-
- /**
- * Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe
- * multiple types of messages, etc.)
- */
- @WMSingleton
- @Provides
- @ShellMainThread
- public static Handler provideShellMainHandler(Context context, @Main Handler sysuiMainHandler) {
- if (enableShellMainThread(context)) {
- HandlerThread mainThread = new HandlerThread("wmshell.main");
- mainThread.start();
- return mainThread.getThreadHandler();
- }
- return sysuiMainHandler;
- }
-
- /**
- * Provide a Shell main-thread Executor.
- */
- @WMSingleton
- @Provides
- @ShellMainThread
- public static ShellExecutor provideShellMainExecutor(Context context,
- @ShellMainThread Handler mainHandler, @Main ShellExecutor sysuiMainExecutor) {
- if (enableShellMainThread(context)) {
- return new HandlerExecutor(mainHandler);
- }
- return sysuiMainExecutor;
- }
-
- /**
- * Provide a Shell animation-thread Executor.
- */
- @WMSingleton
- @Provides
- @ShellAnimationThread
- public static ShellExecutor provideShellAnimationExecutor() {
- HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim",
- THREAD_PRIORITY_DISPLAY);
- shellAnimationThread.start();
- return new HandlerExecutor(shellAnimationThread.getThreadHandler());
- }
-
- /**
- * Provides a Shell splashscreen-thread Executor
- */
- @WMSingleton
- @Provides
- @ShellSplashscreenThread
- public static ShellExecutor provideSplashScreenExecutor() {
- HandlerThread shellSplashscreenThread = new HandlerThread("wmshell.splashscreen",
- THREAD_PRIORITY_TOP_APP_BOOST);
- shellSplashscreenThread.start();
- return new HandlerExecutor(shellSplashscreenThread.getThreadHandler());
- }
-
- /**
- * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on
- * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on
- * the Shell main-thread with the SF vsync.
- */
- @WMSingleton
- @Provides
- @ChoreographerSfVsync
- public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler(
- @ShellMainThread ShellExecutor mainExecutor) {
- try {
- AnimationHandler handler = new AnimationHandler();
- mainExecutor.executeBlocking(() -> {
- // This is called on the animation thread since it calls
- // Choreographer.getSfInstance() which returns a thread-local Choreographer instance
- // that uses the SF vsync
- handler.setProvider(new SfVsyncFrameCallbackProvider());
- });
- return handler;
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e);
- }
- }
-
//
// Internal common - Components used internally by multiple shell features
//
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java
new file mode 100644
index 0000000..61f50b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wmshell;
+
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
+
+import android.animation.AnimationHandler;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
+import com.android.systemui.dagger.WMSingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.wm.shell.common.HandlerExecutor;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
+import com.android.wm.shell.common.annotations.ShellAnimationThread;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides basic concurrency-related dependencies from {@link com.android.wm.shell}, these
+ * dependencies are only accessible from components within the WM subcomponent.
+ */
+@Module
+public abstract class WMShellConcurrencyModule {
+
+ private static final int MSGQ_SLOW_DELIVERY_THRESHOLD_MS = 30;
+ private static final int MSGQ_SLOW_DISPATCH_THRESHOLD_MS = 30;
+
+ /**
+ * Returns whether to enable a separate shell thread for the shell features.
+ */
+ private static boolean enableShellMainThread(Context context) {
+ return context.getResources().getBoolean(R.bool.config_enableShellMainThread);
+ }
+
+ //
+ // Shell Concurrency - Components used for managing threading in the Shell and SysUI
+ //
+
+ /**
+ * Provide a SysUI main-thread Executor.
+ */
+ @WMSingleton
+ @Provides
+ @Main
+ public static ShellExecutor provideSysUIMainExecutor(@Main Handler sysuiMainHandler) {
+ return new HandlerExecutor(sysuiMainHandler);
+ }
+
+ /**
+ * Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe
+ * multiple types of messages, etc.)
+ */
+ @WMSingleton
+ @Provides
+ @ShellMainThread
+ public static Handler provideShellMainHandler(Context context, @Main Handler sysuiMainHandler) {
+ if (enableShellMainThread(context)) {
+ HandlerThread mainThread = new HandlerThread("wmshell.main", THREAD_PRIORITY_DISPLAY);
+ mainThread.start();
+ if (Build.IS_DEBUGGABLE) {
+ mainThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
+ mainThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS,
+ MSGQ_SLOW_DELIVERY_THRESHOLD_MS);
+ }
+ return Handler.createAsync(mainThread.getLooper());
+ }
+ return sysuiMainHandler;
+ }
+
+ /**
+ * Provide a Shell main-thread Executor.
+ */
+ @WMSingleton
+ @Provides
+ @ShellMainThread
+ public static ShellExecutor provideShellMainExecutor(Context context,
+ @ShellMainThread Handler mainHandler, @Main ShellExecutor sysuiMainExecutor) {
+ if (enableShellMainThread(context)) {
+ return new HandlerExecutor(mainHandler);
+ }
+ return sysuiMainExecutor;
+ }
+
+ /**
+ * Provide a Shell animation-thread Executor.
+ */
+ @WMSingleton
+ @Provides
+ @ShellAnimationThread
+ public static ShellExecutor provideShellAnimationExecutor() {
+ HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim",
+ THREAD_PRIORITY_DISPLAY);
+ shellAnimationThread.start();
+ if (Build.IS_DEBUGGABLE) {
+ shellAnimationThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
+ shellAnimationThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS,
+ MSGQ_SLOW_DELIVERY_THRESHOLD_MS);
+ }
+ return new HandlerExecutor(Handler.createAsync(shellAnimationThread.getLooper()));
+ }
+
+ /**
+ * Provides a Shell splashscreen-thread Executor
+ */
+ @WMSingleton
+ @Provides
+ @ShellSplashscreenThread
+ public static ShellExecutor provideSplashScreenExecutor() {
+ HandlerThread shellSplashscreenThread = new HandlerThread("wmshell.splashscreen",
+ THREAD_PRIORITY_TOP_APP_BOOST);
+ shellSplashscreenThread.start();
+ return new HandlerExecutor(shellSplashscreenThread.getThreadHandler());
+ }
+
+ /**
+ * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on
+ * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on
+ * the Shell main-thread with the SF vsync.
+ */
+ @WMSingleton
+ @Provides
+ @ChoreographerSfVsync
+ public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler(
+ @ShellMainThread ShellExecutor mainExecutor) {
+ try {
+ AnimationHandler handler = new AnimationHandler();
+ mainExecutor.executeBlocking(() -> {
+ // This is called on the animation thread since it calls
+ // Choreographer.getSfInstance() which returns a thread-local Choreographer instance
+ // that uses the SF vsync
+ handler.setProvider(new SfVsyncFrameCallbackProvider());
+ });
+ return handler;
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 084c0b4..99b3f6f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -25,9 +25,11 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -96,6 +98,9 @@
private ConfigurationController mConfigurationController;
@Mock
private EmergencyButtonController mEmergencyButtonController;
+ @Mock
+ private Resources mResources;
+ private Configuration mConfiguration;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
private KeyguardPasswordViewController mKeyguardPasswordViewController;
@@ -103,6 +108,11 @@
@Before
public void setup() {
+ mConfiguration = new Configuration();
+ mConfiguration.setToDefaults(); // Defaults to ORIENTATION_UNDEFINED.
+
+ when(mResources.getConfiguration()).thenReturn(mConfiguration);
+ when(mView.getResources()).thenReturn(mResources);
when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
.thenReturn(mAdminSecondaryLockScreenController);
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
@@ -154,4 +164,17 @@
verify(mWindowInsetsController).controlWindowInsetsAnimation(
eq(ime()), anyLong(), any(), any(), any());
}
+
+ @Test
+ public void onResourcesUpdate_callsThroughOnRotationChange() {
+ // Rotation is the same, shouldn't cause an update
+ mKeyguardSecurityContainerController.updateResources();
+ verify(mView, times(0)).updateLayoutForSecurityMode(any());
+
+ // Update rotation. Should trigger update
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+ mKeyguardSecurityContainerController.updateResources();
+ verify(mView, times(1)).updateLayoutForSecurityMode(any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 67c1d07..1f165bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -34,7 +34,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingDataProvider.GestureCompleteListener;
+import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -82,7 +82,7 @@
private final FalsingClassifier.Result mFalsedResult =
FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
- private GestureCompleteListener mGestureCompleteListener;
+ private GestureFinalizedListener mGestureFinalizedListener;
@Before
public void setup() {
@@ -103,13 +103,13 @@
mHistoryTracker, mKeyguardStateController, false);
- ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
- ArgumentCaptor.forClass(GestureCompleteListener.class);
+ ArgumentCaptor<GestureFinalizedListener> gestureCompleteListenerCaptor =
+ ArgumentCaptor.forClass(GestureFinalizedListener.class);
verify(mFalsingDataProvider).addGestureCompleteListener(
gestureCompleteListenerCaptor.capture());
- mGestureCompleteListener = gestureCompleteListenerCaptor.getValue();
+ mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
}
@Test
@@ -211,7 +211,7 @@
@Test
public void testHistory() {
- mGestureCompleteListener.onGestureComplete(1000);
+ mGestureFinalizedListener.onGestureFinalized(1000);
verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
}
@@ -220,7 +220,7 @@
public void testHistory_singleTap() {
// When trying to classify single taps, we don't immediately add results to history.
mBrightLineFalsingManager.isFalseTap(false, 0);
- mGestureCompleteListener.onGestureComplete(1000);
+ mGestureFinalizedListener.onGestureFinalized(1000);
verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
}
@@ -228,9 +228,9 @@
public void testHistory_multipleSingleTaps() {
// When trying to classify single taps, we don't immediately add results to history.
mBrightLineFalsingManager.isFalseTap(false, 0);
- mGestureCompleteListener.onGestureComplete(1000);
+ mGestureFinalizedListener.onGestureFinalized(1000);
mBrightLineFalsingManager.isFalseTap(false, 0);
- mGestureCompleteListener.onGestureComplete(2000);
+ mGestureFinalizedListener.onGestureFinalized(2000);
verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
verify(mHistoryTracker).addResults(anyCollection(), eq(2000L));
}
@@ -239,11 +239,11 @@
public void testHistory_doubleTap() {
// When trying to classify single taps, we don't immediately add results to history.
mBrightLineFalsingManager.isFalseTap(false, 0);
- mGestureCompleteListener.onGestureComplete(1000);
+ mGestureFinalizedListener.onGestureFinalized(1000);
// Before checking for double tap, we may check for single-tap on the second gesture.
mBrightLineFalsingManager.isFalseTap(false, 0);
mBrightLineFalsingManager.isFalseDoubleTap();
- mGestureCompleteListener.onGestureComplete(2000);
+ mGestureFinalizedListener.onGestureFinalized(2000);
// Double tap is immediately added to history. Single tap is never added.
verify(mHistoryTracker).addResults(anyCollection(), eq(2000L));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
index 3a77f7ee..33a30e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
@@ -21,16 +21,20 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
-import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.media.AudioManager;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -41,30 +45,50 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class HomeSoundEffectControllerTest extends SysuiTestCase {
- private @Mock Context mContext;
+ private static final String HOME_PACKAGE_NAME = "com.android.apps.home";
+ private static final String NON_HOME_PACKAGE_NAME = "com.android.apps.not.home";
+ private static final int HOME_TASK_ID = 0;
+ private static final int NON_HOME_TASK_ID = 1;
+
private @Mock AudioManager mAudioManager;
private @Mock TaskStackChangeListeners mTaskStackChangeListeners;
- private @Mock ActivityManager.RunningTaskInfo mStandardActivityTaskInfo;
- private @Mock ActivityManager.RunningTaskInfo mHomeActivityTaskInfo;
+ private @Mock ActivityManagerWrapper mActivityManagerWrapper;
+ private @Mock PackageManager mPackageManager;
+ private ActivityManager.RunningTaskInfo mTaskAStandardActivity;
+ private ActivityManager.RunningTaskInfo mTaskAExceptionActivity;
+ private ActivityManager.RunningTaskInfo mHomeTaskHomeActivity;
+ private ActivityManager.RunningTaskInfo mHomeTaskStandardActivity;
+ private ActivityManager.RunningTaskInfo mEmptyTask;
private HomeSoundEffectController mController;
private TaskStackChangeListener mTaskStackChangeListener;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
-
- doReturn(WindowConfiguration.ACTIVITY_TYPE_STANDARD).when(
- mStandardActivityTaskInfo).getActivityType();
- doReturn(WindowConfiguration.ACTIVITY_TYPE_HOME).when(
- mHomeActivityTaskInfo).getActivityType();
-
+ mTaskAStandardActivity = createRunningTaskInfo(NON_HOME_PACKAGE_NAME,
+ WindowConfiguration.ACTIVITY_TYPE_STANDARD, NON_HOME_TASK_ID,
+ true /* playHomeTransitionSound */);
+ mTaskAExceptionActivity = createRunningTaskInfo(NON_HOME_PACKAGE_NAME,
+ WindowConfiguration.ACTIVITY_TYPE_STANDARD, NON_HOME_TASK_ID,
+ false /* playHomeTransitionSound */);
+ mHomeTaskHomeActivity = createRunningTaskInfo(HOME_PACKAGE_NAME,
+ WindowConfiguration.ACTIVITY_TYPE_HOME, HOME_TASK_ID,
+ true /* playHomeTransitionSound */);
+ mHomeTaskStandardActivity = createRunningTaskInfo(HOME_PACKAGE_NAME,
+ WindowConfiguration.ACTIVITY_TYPE_STANDARD, HOME_TASK_ID,
+ true /* playHomeTransitionSound */);
+ mEmptyTask = new ActivityManager.RunningTaskInfo();
+ mContext.setMockPackageManager(mPackageManager);
+ when(mPackageManager.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS,
+ NON_HOME_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_GRANTED);
mController = new HomeSoundEffectController(mContext, mAudioManager,
- mTaskStackChangeListeners);
+ mTaskStackChangeListeners, mActivityManagerWrapper, mPackageManager);
}
@Test
@@ -73,43 +97,60 @@
startController(true /* isHomeSoundEffectEnabled */);
// And the home task moves to the front,
- mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(mHomeTaskHomeActivity);
+ mTaskStackChangeListener.onTaskStackChanged();
// Then no home sound effect should be played.
verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
}
+ /**
+ * Task A (playHomeTransitionSound = true) -> HOME
+ * Expectation: Home sound is played
+ */
@Test
public void testHomeSoundEffectPlayedWhenEnabled() {
// When HomeSoundEffectController is started and the home sound effect is enabled,
startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mHomeTaskHomeActivity);
// And first a task different from the home task moves to front,
- mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo);
+ mTaskStackChangeListener.onTaskStackChanged();
// And the home task moves to the front,
- mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+ mTaskStackChangeListener.onTaskStackChanged();
// Then the home sound effect should be played.
verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
}
+ /**
+ * Task A (playHomeTransitionSound = true) -> HOME -> HOME
+ * Expectation: Home sound is played once after HOME moves to front the first time
+ */
@Test
public void testHomeSoundEffectNotPlayedTwiceInRow() {
// When HomeSoundEffectController is started and the home sound effect is enabled,
startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mHomeTaskHomeActivity,
+ mHomeTaskHomeActivity);
+
// And first a task different from the home task moves to front,
- mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo);
+ mTaskStackChangeListener.onTaskStackChanged();
// And the home task moves to the front,
- mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+ mTaskStackChangeListener.onTaskStackChanged();
// Then the home sound effect should be played.
verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
// If the home task moves to front a second time in a row,
- mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+ mTaskStackChangeListener.onTaskStackChanged();
// Then no home sound effect should be played.
verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME);
@@ -121,7 +162,59 @@
startController(true /* isHomeSoundEffectEnabled */);
// And a standard, non-home task, moves to the front,
- mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(mTaskAStandardActivity);
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * Task A (playHomeTransitionSound = true) -> HOME -> HOME (activity type standard)
+ * Expectation: Home sound is played once after HOME moves to front
+ */
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenOtherHomeActivityMovesToFront() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mHomeTaskHomeActivity,
+ mHomeTaskStandardActivity);
+
+ // And first a task different from the home task moves to front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then the home sound effect should be played.
+ verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
+
+ // If the home task moves to front a second time in a row,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * Task A (playHomeTransitionSound = true) -> HOME (activity type standard)
+ * Expectation: Home sound is not played
+ */
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenOtherHomeActivityMovesToFrontOfOtherApp() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+
+ // And first a task different from the home task moves to front,
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mHomeTaskStandardActivity);
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And an activity from the home package but not the home root activity moves to front
+ mTaskStackChangeListener.onTaskStackChanged();
// Then no home sound effect should be played.
verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
@@ -138,6 +231,171 @@
}
/**
+ * Task A (playHomeTransitionSound = false) -> HOME
+ * Expectation: Home sound is not played
+ */
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenHomeActivityMovesToFrontAfterException() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+
+ // And first a task different from the home task moves to front, which has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code>
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAExceptionActivity,
+ mHomeTaskHomeActivity);
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then no home sound effect should be played because the last package is an exception.
+ verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * HOME -> Task A (playHomeTransitionSound = true) -> Task A (playHomeTransitionSound = false)
+ * -> HOME
+ * Expectation: Home sound is not played
+ */
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenHomeActivityMovesToFrontAfterException2() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mTaskAExceptionActivity,
+ mHomeTaskHomeActivity);
+
+ // And first a task different from the home task moves to front
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then a different activity from the same task moves to front, which has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code>
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * HOME -> Task A (playHomeTransitionSound = false) -> Task A (playHomeTransitionSound = true)
+ * -> HOME
+ * Expectation: Home sound is played
+ */
+ @Test
+ public void testHomeSoundEffectPlayedWhenHomeActivityMovesToFrontAfterException() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAExceptionActivity,
+ mTaskAStandardActivity,
+ mHomeTaskHomeActivity);
+
+ // And first a task different from the home task moves to front,
+ // the topActivity of this task has {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND}
+ // set to <code>false</code>
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then a different activity from the same task moves to front, which has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>true</code>
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then the home sound effect should be played.
+ verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * HOME -> Task A (playHomeTransitionSound = false) -> Task A (empty task, no top activity)
+ * -> HOME
+ * Expectation: Home sound is not played
+ */
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenEmptyTaskMovesToFrontAfterException() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAExceptionActivity,
+ mEmptyTask,
+ mHomeTaskHomeActivity);
+
+ // And first a task different from the home task moves to front, whose topActivity has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code>
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then a task with no topActivity moves to front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * HOME -> Task A (playHomeTransitionSound = true) -> Task A (empty task, no top activity)
+ * -> HOME
+ * Expectation: Home sound is played
+ */
+ @Test
+ public void testHomeSoundEffectPlayedWhenEmptyTaskMovesToFrontAfterStandardActivity() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mEmptyTask,
+ mHomeTaskHomeActivity);
+
+ // And first a task different from the home task moves to front, whose topActivity has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code>
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then a task with no topActivity moves to front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then the home sound effect should be played.
+ verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * HOME -> Task A (playHomeTransitionSound = false, no permission) -> HOME
+ * Expectation: Home sound is played
+ */
+ @Test
+ public void testHomeSoundEffectPlayedWhenFlagSetButPermissionNotGranted() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mHomeTaskHomeActivity);
+ when(mPackageManager.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS,
+ NON_HOME_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_DENIED);
+
+ // And first a task different from the home task moves to front, whose topActivity has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>true</code>,
+ // but the app doesn't have the right permission granted
+ mTaskStackChangeListener.onTaskStackChanged();
+
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then the home sound effect should be played.
+ verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
* Sets {@link AudioManager#isHomeSoundEffectEnabled()} and starts HomeSoundEffectController.
* If the home sound effect is enabled, the registered TaskStackChangeListener is extracted.
*/
@@ -155,4 +413,20 @@
mTaskStackChangeListener = listenerCaptor.getValue();
}
}
+
+ private ActivityManager.RunningTaskInfo createRunningTaskInfo(String packageName,
+ int activityType, int taskId, boolean playHomeTransitionSound) {
+ ActivityManager.RunningTaskInfo res = new ActivityManager.RunningTaskInfo();
+ res.topActivityInfo = new ActivityInfo();
+ res.topActivityInfo.packageName = packageName;
+ res.topActivityType = activityType;
+ res.taskId = taskId;
+ if (!playHomeTransitionSound) {
+ // set the flag to 0
+ res.topActivityInfo.privateFlags &= ~ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND;
+ } else {
+ res.topActivityInfo.privateFlags |= ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND;
+ }
+ return res;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index c2e0d6b..8db0f33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -453,6 +453,9 @@
assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
assertThat(statusContent.getMaxLines()).isEqualTo(3);
+ // Has a single message, no count shown.
+ assertEquals(View.GONE, result.findViewById(R.id.messages_count).getVisibility());
+
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_medium) - 1);
RemoteViews smallView = new PeopleTileViewHelper(mContext,
@@ -492,6 +495,10 @@
assertEquals(View.VISIBLE, statusContent.getVisibility());
assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ // Has a single message, no count shown.
+ assertEquals(View.GONE, result.findViewById(R.id.messages_count).getVisibility());
+
}
@Test
@@ -507,19 +514,62 @@
TextView name = (TextView) result.findViewById(R.id.name);
assertEquals(name.getText(), NAME);
- TextView subtext = (TextView) result.findViewById(R.id.subtext);
- assertEquals(View.GONE, subtext.getVisibility());
+ assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.GONE, result.findViewById(R.id.predefined_icon).getVisibility());
// Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.VISIBLE, availability.getVisibility());
+ assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
// Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
// Has notification content.
TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
- // Has 2 messages, show count.
+ // Has two messages, show count.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility());
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithStatusAndNotification, 0, mOptions).getViews();
+ View smallResult = smallView.apply(mContext, null);
+
+ // Show icon instead of name.
+ assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+ assertEquals(View.GONE,
+ smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.person_icon).getVisibility());
+
+ // Has two messages, show count.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility());
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_large));
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_height_for_large));
+ RemoteViews largeView = new PeopleTileViewHelper(mContext,
+ tileWithStatusAndNotification, 0, mOptions).getViews();
+ View largeResult = largeView.apply(mContext, null);
+
+ name = (TextView) largeResult.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.GONE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ View personIcon = largeResult.findViewById(R.id.person_icon);
+ assertEquals(View.VISIBLE, personIcon.getVisibility());
+ // Has notification content.
+ statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ // Has two messages, show count.
assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility());
}
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 4c6e06f70..8bb9ce9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -29,6 +29,7 @@
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
@@ -74,7 +75,6 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE;
import static android.net.NetworkPolicyManager.blockedReasonsToString;
import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST;
import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired;
@@ -3716,6 +3716,7 @@
mDnsManager.removeNetwork(nai.network);
}
mNetIdManager.releaseNetId(nai.network.getNetId());
+ nai.onNetworkDisconnected();
}
private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
@@ -6467,6 +6468,13 @@
}
}
+ private boolean isNetworkProviderWithIdRegistered(final int providerId) {
+ for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ if (npi.providerId == providerId) return true;
+ }
+ return false;
+ }
+
/**
* Register or update a network offer.
* @param newOffer The new offer. If the callback member is the same as an existing
@@ -6474,7 +6482,13 @@
*/
private void handleRegisterNetworkOffer(@NonNull final NetworkOffer newOffer) {
ensureRunningOnConnectivityServiceThread();
-
+ if (!isNetworkProviderWithIdRegistered(newOffer.providerId)) {
+ // This may actually happen if a provider updates its score or registers and then
+ // immediately unregisters. The offer would still be in the handler queue, but the
+ // provider would have been removed.
+ if (DBG) log("Received offer from an unregistered provider");
+ return;
+ }
final NetworkOfferInfo existingOffer = findNetworkOfferInfoByCallback(newOffer.callback);
if (null != existingOffer) {
handleUnregisterNetworkOffer(existingOffer);
@@ -8113,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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 5d1ca33..09f4c22 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -143,7 +143,6 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.os.SomeArgs;
-import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
@@ -4268,31 +4267,37 @@
}
}
+ @Override
+ public int getExternalStorageMountMode(int uid, String packageName) {
+ enforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE);
+ return mStorageManagerInternal.getExternalStorageMountMode(uid, packageName);
+ }
+
private int getMountModeInternal(int uid, String packageName) {
try {
// Get some easy cases out of the way first
if (Process.isIsolated(uid)) {
- return Zygote.MOUNT_EXTERNAL_NONE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
final String[] packagesForUid = mIPackageManager.getPackagesForUid(uid);
if (ArrayUtils.isEmpty(packagesForUid)) {
// It's possible the package got uninstalled already, so just ignore.
- return Zygote.MOUNT_EXTERNAL_NONE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
if (packageName == null) {
packageName = packagesForUid[0];
}
if (mPmInternal.isInstantApp(packageName, UserHandle.getUserId(uid))) {
- return Zygote.MOUNT_EXTERNAL_NONE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
if (mStorageManagerInternal.isExternalStorageService(uid)) {
// Determine if caller requires pass_through mount; note that we do this for
// all processes that share a UID with MediaProvider; but this is fine, since
// those processes anyway share the same rights as MediaProvider.
- return Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
+ return StorageManager.MOUNT_MODE_EXTERNAL_PASS_THROUGH;
}
if ((mDownloadsAuthorityAppId == UserHandle.getAppId(uid)
@@ -4300,7 +4305,7 @@
// DownloadManager can write in app-private directories on behalf of apps;
// give it write access to Android/
// ExternalStorageProvider can access Android/{data,obb} dirs in managed mode
- return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE;
}
final boolean hasMtp = mIPackageManager.checkUidPermission(ACCESS_MTP, uid) ==
@@ -4310,7 +4315,7 @@
0, UserHandle.getUserId(uid));
if (ai != null && ai.isSignedWithPlatformKey()) {
// Platform processes hosting the MTP server should be able to write in Android/
- return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE;
}
}
@@ -4335,13 +4340,13 @@
}
}
if ((hasInstall || hasInstallOp) && hasWrite) {
- return Zygote.MOUNT_EXTERNAL_INSTALLER;
+ return StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER;
}
- return Zygote.MOUNT_EXTERNAL_DEFAULT;
+ return StorageManager.MOUNT_MODE_EXTERNAL_DEFAULT;
} catch (RemoteException e) {
// Should not happen
}
- return Zygote.MOUNT_EXTERNAL_NONE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
private static class Callbacks extends Handler {
@@ -4711,7 +4716,8 @@
return true;
}
- return getExternalStorageMountMode(uid, packageName) != Zygote.MOUNT_EXTERNAL_NONE;
+ return getExternalStorageMountMode(uid, packageName)
+ != StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
private void killAppForOpChange(int code, int uid) {
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 d2fd8ff..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
@@ -1790,6 +1778,44 @@
}
if (!ignoreForeground) {
+ if (r.mStartForegroundCount == 0) {
+ /*
+ If the service was started with startService(), not
+ startForegroundService(), and if startForeground() isn't called within
+ mFgsStartForegroundTimeoutMs, then we check the state of the app
+ (who owns the service, which is the app that called startForeground())
+ again. If the app is in the foreground, or in any other cases where
+ FGS-starts are allowed, then we still allow the FGS to be started.
+ Otherwise, startForeground() would fail.
+
+ If the service was started with startForegroundService(), then the service
+ must call startForeground() within a timeout anyway, so we don't need this
+ check.
+ */
+ if (!r.fgRequired) {
+ 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, r.userId,false);
+ final String temp = "startForegroundDelayMs:" + delayMs;
+ if (r.mInfoAllowStartForeground != null) {
+ r.mInfoAllowStartForeground += "; " + temp;
+ } else {
+ r.mInfoAllowStartForeground = temp;
+ }
+ r.mLoggedInfoAllowStartForeground = false;
+ }
+ }
+ } else if (r.mStartForegroundCount >= 1) {
+ // The second or later time startForeground() is called after service is
+ // started. Check for app state again.
+ final long delayMs = SystemClock.elapsedRealtime() -
+ r.mLastSetFgsRestrictionTime;
+ if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
+ setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId,false);
+ }
+ }
logFgsBackgroundStart(r);
if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
final String msg = "Service.startForeground() not allowed due to "
@@ -1843,6 +1869,7 @@
active.mNumActive++;
}
r.isForeground = true;
+ r.mStartForegroundCount++;
if (!stopProcStatsOp) {
ServiceState stracker = r.getTracker();
if (stracker != null) {
@@ -1901,6 +1928,7 @@
decActiveForegroundAppLocked(smap, r);
}
r.isForeground = false;
+ resetFgsRestrictionLocked(r);
ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -2539,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;
@@ -3411,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) {
@@ -3459,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 "
@@ -3892,6 +3921,7 @@
r.foregroundId = 0;
r.foregroundNoti = null;
r.mAllowWhileInUsePermissionInFgs = false;
+ r.mAllowStartForeground = REASON_DENIED;
// Clear start entries.
r.clearDeliveredStartsLocked();
@@ -5428,8 +5458,9 @@
* @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.
if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
r.mAllowWhileInUsePermissionInFgs = true;
@@ -5445,11 +5476,20 @@
if (r.mAllowStartForeground == REASON_DENIED) {
r.mAllowStartForeground = shouldAllowFgsStartForegroundLocked(allowWhileInUse,
callingPackage, callingPid, callingUid, intent, r,
- allowBackgroundActivityStarts);
+ userId);
}
}
}
+ void resetFgsRestrictionLocked(ServiceRecord r) {
+ r.mAllowWhileInUsePermissionInFgs = false;
+ r.mAllowStartForeground = REASON_DENIED;
+ r.mInfoAllowStartForeground = null;
+ r.mInfoTempFgsAllowListReason = null;
+ r.mLoggedInfoAllowStartForeground = false;
+ r.mLastSetFgsRestrictionTime = 0;
+ }
+
boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
return true;
@@ -5579,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
@@ -5601,6 +5648,8 @@
+ ",callingUid:" + tempAllowListReason.mCallingUid))
+ ">"
+ "; targetSdkVersion:" + r.appInfo.targetSdkVersion
+ + "; callerTargetSdkVersion:" + callerTargetSdkVersion
+ + "; startForegroundCount:" + r.mStartForegroundCount
+ "]";
if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
r.mLoggedInfoAllowStartForeground = false;
@@ -5771,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 5859cea..e16f4c9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -96,6 +96,7 @@
static final String KEY_PROCESS_CRASH_COUNT_LIMIT = "process_crash_count_limit";
static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration";
static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration";
+ static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -135,7 +136,7 @@
private static final int DEFAULT_PROCESS_CRASH_COUNT_LIMIT = 12;
private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 10 * 1000;
private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000;
-
+ private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000;
// Flag stored in the DeviceConfig API.
/**
@@ -171,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.
*/
@@ -370,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;
@@ -396,6 +411,12 @@
*/
volatile long mFgToBgFgsGraceDuration = DEFAULT_FG_TO_BG_FGS_GRACE_DURATION;
+ /**
+ * When service started from background, before the timeout it can be promoted to FGS by calling
+ * Service.startForeground().
+ */
+ volatile long mFgsStartForegroundTimeoutMs = DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS;
+
private final ActivityManagerService mService;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -547,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;
@@ -586,6 +610,9 @@
case KEY_FG_TO_BG_FGS_GRACE_DURATION:
updateFgToBgFgsGraceDuration();
break;
+ case KEY_FGS_START_FOREGROUND_TIMEOUT:
+ updateFgsStartForegroundTimeout();
+ break;
default:
break;
}
@@ -819,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,
@@ -869,6 +903,13 @@
DEFAULT_FG_TO_BG_FGS_GRACE_DURATION);
}
+ private void updateFgsStartForegroundTimeout() {
+ mFgsStartForegroundTimeoutMs = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_FGS_START_FOREGROUND_TIMEOUT,
+ DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS);
+ }
+
private void updateImperceptibleKillExemptions() {
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear();
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
@@ -1071,6 +1112,16 @@
pw.println(mBootTimeTempAllowlistDuration);
pw.print(" "); pw.print(KEY_FG_TO_BG_FGS_GRACE_DURATION); pw.print("=");
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/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 9cd9902..54c3512 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -165,9 +165,16 @@
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
@PowerWhitelistManager.ReasonCode int mAllowStartForeground = REASON_DENIED;
+ // Debug info why mAllowStartForeground is allowed or denied.
String mInfoAllowStartForeground;
+ // Debug info if mAllowStartForeground is allowed because of a temp-allowlist.
FgsStartTempAllowList.TempFgsAllowListEntry mInfoTempFgsAllowListReason;
+ // Is the same mInfoAllowStartForeground string has been logged before? Used for dedup.
boolean mLoggedInfoAllowStartForeground;
+ // The number of times Service.startForeground() is called;
+ int mStartForegroundCount;
+ // Last time mAllowWhileInUsePermissionInFgs or mAllowStartForeground is set.
+ long mLastSetFgsRestrictionTime;
String stringName; // caching of toString
@@ -439,6 +446,8 @@
pw.println(mRecentCallingUid);
pw.print(prefix); pw.print("allowStartForeground=");
pw.println(mAllowStartForeground);
+ pw.print(prefix); pw.print("startForegroundCount=");
+ pw.println(mStartForegroundCount);
pw.print(prefix); pw.print("infoAllowStartForeground=");
pw.println(mInfoAllowStartForeground);
if (delayed) {
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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 804550b..2ce60d0 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1836,6 +1836,127 @@
}
}
+ /** @see AudioManager#isSurroundFormatEnabled(int) */
+ @Override
+ public boolean isSurroundFormatEnabled(int audioFormat) {
+ if (!isSurroundFormat(audioFormat)) {
+ Log.w(TAG, "audioFormat to enable is not a surround format.");
+ return false;
+ }
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing WRITE_SETTINGS permission");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSettingsLock) {
+ HashSet<Integer> enabledFormats = getEnabledFormats();
+ return enabledFormats.contains(audioFormat);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /** @see AudioManager#setSurroundFormatEnabled(int, boolean) */
+ @Override
+ public boolean setSurroundFormatEnabled(int audioFormat, boolean enabled) {
+ if (!isSurroundFormat(audioFormat)) {
+ Log.w(TAG, "audioFormat to enable is not a surround format.");
+ return false;
+ }
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing WRITE_SETTINGS permission");
+ }
+
+ HashSet<Integer> enabledFormats = getEnabledFormats();
+ if (enabled) {
+ enabledFormats.add(audioFormat);
+ } else {
+ enabledFormats.remove(audioFormat);
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSettingsLock) {
+ Settings.Global.putString(mContentResolver,
+ Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
+ TextUtils.join(",", enabledFormats));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return true;
+ }
+
+ /** @see AudioManager#setEncodedSurroundMode(int) */
+ @Override
+ public boolean setEncodedSurroundMode(int mode) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing WRITE_SETTINGS permission");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSettingsLock) {
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ENCODED_SURROUND_OUTPUT,
+ mode);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return true;
+ }
+
+ /** @see AudioManager#getEncodedSurroundMode() */
+ @Override
+ public int getEncodedSurroundMode() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing WRITE_SETTINGS permission");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSettingsLock) {
+ return Settings.Global.getInt(mContentResolver,
+ Settings.Global.ENCODED_SURROUND_OUTPUT,
+ AudioManager.ENCODED_SURROUND_OUTPUT_AUTO);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /** @return the formats that are enabled in global settings */
+ private HashSet<Integer> getEnabledFormats() {
+ HashSet<Integer> formats = new HashSet<>();
+ String enabledFormats = Settings.Global.getString(mContentResolver,
+ Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
+ if (enabledFormats != null) {
+ try {
+ Arrays.stream(TextUtils.split(enabledFormats, ","))
+ .mapToInt(Integer::parseInt)
+ .forEach(formats::add);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS misformatted.", e);
+ }
+ }
+ return formats;
+ }
+
+ private boolean isSurroundFormat(int audioFormat) {
+ for (int sf : AudioFormat.SURROUND_SOUND_ENCODING) {
+ if (sf == audioFormat) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void sendEnabledSurroundFormats(ContentResolver cr, boolean forceUpdate) {
if (mEncodedSurroundMode != Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL) {
// Manually enable surround formats only when the setting is in manual mode.
@@ -1860,14 +1981,7 @@
for (String format : surroundFormats) {
try {
int audioFormat = Integer.valueOf(format);
- boolean isSurroundFormat = false;
- for (int sf : AudioFormat.SURROUND_SOUND_ENCODING) {
- if (sf == audioFormat) {
- isSurroundFormat = true;
- break;
- }
- }
- if (isSurroundFormat && !formats.contains(audioFormat)) {
+ if (isSurroundFormat(audioFormat) && !formats.contains(audioFormat)) {
formats.add(audioFormat);
}
} catch (Exception e) {
@@ -7343,7 +7457,6 @@
Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.ENCODED_SURROUND_OUTPUT), false, this);
-
mEnabledSurroundFormats = Settings.Global.getString(
mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
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/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 41bc0b9..874e9a6 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -1054,27 +1054,16 @@
clipboard.mNotifiedUids.put(uid, true);
Binder.withCleanCallingIdentity(() -> {
- // Retrieve the app label of the source of the clip data
- CharSequence sourceAppLabel = null;
- if (clipboard.mPrimaryClipPackage != null) {
- try {
- sourceAppLabel = mPm.getApplicationLabel(mPm.getApplicationInfoAsUser(
- clipboard.mPrimaryClipPackage, 0, userId));
- } catch (PackageManager.NameNotFoundException e) {
- // leave label as null
- }
- }
-
try {
CharSequence callingAppLabel = mPm.getApplicationLabel(
mPm.getApplicationInfoAsUser(callingPackage, 0, userId));
String message;
- if (sourceAppLabel != null) {
+ if (isText(clipboard.primaryClip)) {
message = getContext().getString(
- R.string.pasted_from_app, callingAppLabel, sourceAppLabel);
+ R.string.pasted_text, callingAppLabel);
} else {
message = getContext().getString(
- R.string.pasted_from_clipboard, callingAppLabel);
+ R.string.pasted_content, callingAppLabel);
}
Slog.i(TAG, message);
Toast.makeText(
@@ -1085,4 +1074,21 @@
}
});
}
+
+ /**
+ * Returns true if the provided {@link ClipData} represents a single piece of text. That is, if
+ * there is only on {@link ClipData.Item}, and that item contains a non-empty piece of text and
+ * no URI or Intent. Note that HTML may be provided along with text so the presence of
+ * HtmlText in the clip does not prevent this method returning true.
+ */
+ private static boolean isText(@NonNull ClipData data) {
+ if (data.getItemCount() > 1) {
+ return false;
+ }
+ ClipData.Item item = data.getItemAt(0);
+
+ return !TextUtils.isEmpty(item.getText()) && item.getUri() == null
+ && item.getIntent() == null;
+ }
+
}
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/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 4f95d27..7518130 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -41,11 +41,11 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
-import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Supplier;
/**
* Manages set of updatable font files.
@@ -109,6 +109,7 @@
private final FsverityUtil mFsverityUtil;
private final File mConfigFile;
private final File mTmpConfigFile;
+ private final Supplier<Long> mCurrentTimeSupplier;
private long mLastModifiedMillis;
private int mConfigVersion = 1;
@@ -128,18 +129,20 @@
UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
FsverityUtil fsverityUtil) {
- this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE));
+ this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE),
+ () -> System.currentTimeMillis());
}
// For unit testing
UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
- FsverityUtil fsverityUtil, File configFile) {
+ FsverityUtil fsverityUtil, File configFile, Supplier<Long> currentTimeSupplier) {
mFilesDir = filesDir;
mPreinstalledFontDirs = preinstalledFontDirs;
mParser = parser;
mFsverityUtil = fsverityUtil;
mConfigFile = configFile;
mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp");
+ mCurrentTimeSupplier = currentTimeSupplier;
}
/* package */ void loadFontFileMap() {
@@ -209,7 +212,7 @@
FileUtils.deleteContents(mFilesDir);
mFontFamilyMap.clear();
- mLastModifiedMillis = System.currentTimeMillis();
+ mLastModifiedMillis = mCurrentTimeSupplier.get();
try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
} catch (Exception e) {
@@ -245,7 +248,7 @@
}
// Write config file.
- mLastModifiedMillis = Instant.now().getEpochSecond();
+ mLastModifiedMillis = mCurrentTimeSupplier.get();
try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
} catch (Exception e) {
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/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 39ed7e8..2e4d41c 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -96,9 +96,10 @@
/**
* Notifies that the specified {@link NetworkStatsProvider} has reached its quota
- * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)}.
+ * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or
+ * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}.
*
* @param tag the human readable identifier of the custom network stats provider.
*/
- public abstract void onStatsProviderLimitReached(@NonNull String tag);
+ public abstract void onStatsProviderWarningOrLimitReached(@NonNull String tag);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 16eac91..3859285 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -40,6 +40,14 @@
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
@@ -68,15 +76,7 @@
import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
-import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
-import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_DATA_SAVER;
import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_MASK;
-import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_APP_STANDBY;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_BATTERY_SAVER;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_DOZE;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
@@ -422,15 +422,15 @@
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;
private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
- private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20;
+ private static final int MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED = 20;
// TODO: Add similar docs for other messages.
/**
* Message to indicate that reasons for why an uid is blocked changed.
@@ -2034,39 +2034,45 @@
final boolean hasWarning = policy.warningBytes != LIMIT_DISABLED;
final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED;
- if (hasLimit || policy.metered) {
- final long quotaBytes;
- if (hasLimit && 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);
+ long limitBytes = Long.MAX_VALUE;
+ 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) {
- // snoozing past quota, but we still need to restrict apps,
- // so push really high quota.
- quotaBytes = Long.MAX_VALUE;
- } else {
- // 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.
- quotaBytes = Math.max(1, policy.limitBytes - totalBytes);
- }
- } else {
- // metered network, but no policy limit; we still need to
- // restrict apps, so push really high quota.
- quotaBytes = Long.MAX_VALUE;
+ // 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 (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 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, quotaBytes);
+ setInterfaceQuotasAsync(iface, warningBytes, limitBytes);
newMeteredIfaces.add(iface);
}
}
@@ -2089,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);
}
}
@@ -2101,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;
@@ -4970,7 +4976,7 @@
mListeners.finishBroadcast();
return true;
}
- case MSG_STATS_PROVIDER_LIMIT_REACHED: {
+ case MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED: {
mNetworkStats.forceUpdate();
synchronized (mNetworkPoliciesSecondLock) {
@@ -5041,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: {
@@ -5201,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) {
@@ -5217,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) {
@@ -5726,9 +5750,9 @@
}
@Override
- public void onStatsProviderLimitReached(@NonNull String tag) {
- Log.v(TAG, "onStatsProviderLimitReached: " + tag);
- mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget();
+ public void onStatsProviderWarningOrLimitReached(@NonNull String tag) {
+ Log.v(TAG, "onStatsProviderWarningOrLimitReached: " + tag);
+ mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget();
}
}
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 fe43c31..de5aae0 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -1674,9 +1674,14 @@
}
@Override
- public void setStatsProviderLimitAsync(@NonNull String iface, long quota) {
- if (LOGV) Slog.v(TAG, "setStatsProviderLimitAsync(" + iface + "," + quota + ")");
- invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetLimit(iface, quota));
+ 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,
+ warning, limit));
}
}
@@ -2071,10 +2076,10 @@
}
@Override
- public void notifyLimitReached() {
- Log.d(TAG, mTag + ": onLimitReached");
+ public void notifyWarningOrLimitReached() {
+ Log.d(TAG, mTag + ": notifyWarningOrLimitReached");
LocalServices.getService(NetworkPolicyManagerInternal.class)
- .onStatsProviderLimitReached(mTag);
+ .onStatsProviderWarningOrLimitReached(mTag);
}
@Override
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 3bb5c16..e63426f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -35,6 +35,7 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
@@ -1337,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());
@@ -1657,7 +1658,8 @@
| FLAG_PERMISSION_USER_FIXED
| FLAG_PERMISSION_REVOKED_COMPAT
| FLAG_PERMISSION_REVIEW_REQUIRED
- | FLAG_PERMISSION_ONE_TIME;
+ | FLAG_PERMISSION_ONE_TIME
+ | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
| FLAG_PERMISSION_POLICY_FIXED;
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/ActivityAssistInfo.java b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
new file mode 100644
index 0000000..054044b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
@@ -0,0 +1,52 @@
+/*
+ * 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.wm;
+
+import android.os.IBinder;
+
+/**
+ * Class needed to expose some {@link ActivityRecord} fields in order to provide
+ * {@link android.service.voice.VoiceInteractionSession#onHandleAssist(AssistState)}
+ *
+ * @hide
+ */
+public class ActivityAssistInfo {
+ private final IBinder mActivityToken;
+ private final IBinder mAssistToken;
+ private final int mTaskId;
+
+ public ActivityAssistInfo(ActivityRecord activityRecord) {
+ this.mActivityToken = activityRecord.appToken;
+ this.mAssistToken = activityRecord.assistToken;
+ this.mTaskId = activityRecord.getTask().mTaskId;
+ }
+
+ /** @hide */
+ public IBinder getActivityToken() {
+ return mActivityToken;
+ }
+
+ /** @hide */
+ public IBinder getAssistToken() {
+ return mAssistToken;
+ }
+
+ /** @hide */
+ public int getTaskId() {
+ return mTaskId;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 12c67bb..c09136e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -32,7 +32,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.service.voice.IVoiceInteractionSession;
-import android.util.Pair;
import android.util.proto.ProtoOutputStream;
import android.window.TaskSnapshot;
@@ -166,7 +165,7 @@
* Returns the top activity from each of the currently visible root tasks, and the related task
* id. The first entry will be the focused activity.
*/
- public abstract List<Pair<IBinder, Integer>> getTopVisibleActivities();
+ public abstract List<ActivityAssistInfo> getTopVisibleActivities();
/**
* Returns whether {@code uid} has any resumed activity.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 29c5cec..c8fa50c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -215,7 +215,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -5112,7 +5111,7 @@
}
@Override
- public List<Pair<IBinder, Integer>> getTopVisibleActivities() {
+ public List<ActivityAssistInfo> getTopVisibleActivities() {
synchronized (mGlobalLock) {
return mRootWindowContainer.getTopVisibleActivities();
}
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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 857217f..7261048 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1817,8 +1817,8 @@
* @return a list of pairs, containing activities and their task id which are the top ones in
* each visible root task. The first entry will be the focused activity.
*/
- List<Pair<IBinder, Integer>> getTopVisibleActivities() {
- final ArrayList<Pair<IBinder, Integer>> topVisibleActivities = new ArrayList<>();
+ List<ActivityAssistInfo> getTopVisibleActivities() {
+ final ArrayList<ActivityAssistInfo> topVisibleActivities = new ArrayList<>();
final Task topFocusedRootTask = getTopDisplayFocusedRootTask();
// Traverse all displays.
forAllRootTasks(rootTask -> {
@@ -1826,8 +1826,7 @@
if (rootTask.shouldBeVisible(null /* starting */)) {
final ActivityRecord top = rootTask.getTopNonFinishingActivity();
if (top != null) {
- Pair<IBinder, Integer> visibleActivity = new Pair<>(top.appToken,
- top.getTask().mTaskId);
+ ActivityAssistInfo visibleActivity = new ActivityAssistInfo(top);
if (rootTask == topFocusedRootTask) {
topVisibleActivities.add(0, visibleActivity);
} else {
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 09ae8fc..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);
@@ -10599,21 +10606,58 @@
});
}
+ /**
+ * Returns the apps that are non-exempt from some policies (such as suspension), and populates
+ * the given set with the apps that are exempt.
+ *
+ * @param packageNames apps to check
+ * @param outputExemptApps will be populate with subset of {@code packageNames} that is exempt
+ * from some policy restrictions
+ *
+ * @return subset of {@code packageNames} that is affected by some policy restrictions.
+ */
+ private String[] populateNonExemptAndExemptFromPolicyApps(String[] packageNames,
+ Set<String> outputExemptApps) {
+ Preconditions.checkArgument(outputExemptApps.isEmpty(), "outputExemptApps is not empty");
+ List<String> exemptApps = listPolicyExemptAppsUnchecked();
+ if (exemptApps.isEmpty()) {
+ return packageNames;
+ }
+ List<String> nonExemptApps = new ArrayList<>(packageNames.length);
+ for (int i = 0; i < packageNames.length; i++) {
+ String app = packageNames[i];
+ if (exemptApps.contains(app)) {
+ outputExemptApps.add(app);
+ } else {
+ nonExemptApps.add(app);
+ }
+ }
+ String[] result = new String[nonExemptApps.size()];
+ nonExemptApps.toArray(result);
+ return result;
+ }
+
@Override
public String[] setPackagesSuspended(ComponentName who, String callerPackage,
String[] packageNames, boolean suspended) {
+ Objects.requireNonNull(packageNames, "array of packages cannot be null");
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED);
- String[] result = null;
+ // Must remove the exempt apps from the input before calling PM, then add them back to
+ // the array returned to the caller
+ Set<String> exemptApps = new HashSet<>();
+ packageNames = populateNonExemptAndExemptFromPolicyApps(packageNames, exemptApps);
+
+ String[] nonSuspendedPackages = null;
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
try {
- result = mIPackageManager.setPackagesSuspendedAsUser(packageNames, suspended, null,
- null, null, PLATFORM_PACKAGE_NAME, caller.getUserId());
+ nonSuspendedPackages = mIPackageManager.setPackagesSuspendedAsUser(packageNames,
+ suspended, null, null, null, PLATFORM_PACKAGE_NAME, caller.getUserId());
} catch (RemoteException re) {
// Shouldn't happen.
Slog.e(LOG_TAG, "Failed talking to the package manager", re);
@@ -10627,10 +10671,35 @@
.setBoolean(/* isDelegate */ who == null)
.setStrings(packageNames)
.write();
- if (result != null) {
- return result;
+
+ if (nonSuspendedPackages == null) {
+ Slog.w(LOG_TAG, "PM failed to suspend packages (%s)", Arrays.toString(packageNames));
+ return packageNames;
}
- return packageNames;
+ if (exemptApps.isEmpty()) {
+ return nonSuspendedPackages;
+ }
+
+ String[] result = buildNonSuspendedPackagesUnionArray(nonSuspendedPackages, exemptApps);
+ if (VERBOSE_LOG) Slog.v(LOG_TAG, "Returning %s", Arrays.toString(result));
+ return result;
+ }
+
+ /**
+ * Returns an array containing the union of the given non-suspended packages and
+ * exempt apps. Assumes both parameters are non-null and non-empty.
+ */
+ private String[] buildNonSuspendedPackagesUnionArray(String[] nonSuspendedPackages,
+ Set<String> exemptApps) {
+ String[] result = new String[nonSuspendedPackages.length + exemptApps.size()];
+ int index = 0;
+ for (String app : nonSuspendedPackages) {
+ result[index++] = app;
+ }
+ for (String app : exemptApps) {
+ result[index++] = app;
+ }
+ return result;
}
@Override
@@ -10656,9 +10725,15 @@
@Override
public List<String> listPolicyExemptApps() {
+ CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) || isDeviceOwner(caller)
+ || isProfileOwner(caller));
+ return listPolicyExemptAppsUnchecked();
+ }
+
+ private List<String> listPolicyExemptAppsUnchecked() {
// TODO(b/181238156): decide whether it should only list the apps set by the resources,
// or also the "critical" apps defined by PersonalAppsSuspensionHelper (like SMS app).
// If it's the latter, refactor PersonalAppsSuspensionHelper so it (or a superclass) takes
@@ -16043,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));
@@ -16074,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));
}
/**
@@ -16094,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) {
@@ -16138,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) {
@@ -16148,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;
@@ -16168,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();
@@ -16200,7 +16278,7 @@
am.set(AlarmManager.RTC, alarmTime, pi);
}
- return deadlineState;
+ return notificationState;
}
private void suspendPersonalAppsInternal(int userId, boolean suspended) {
@@ -16242,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;
}
@@ -16263,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);
@@ -16354,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()) {
@@ -16364,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/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 60d9ea2..94f8e59 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -323,6 +323,14 @@
}
}
+void IncrementalService::IncFsMount::setReadLogsRequested(bool value) {
+ if (value) {
+ flags |= StorageFlags::ReadLogsRequested;
+ } else {
+ flags &= ~StorageFlags::ReadLogsRequested;
+ }
+}
+
IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir)
: mVold(sm.getVoldService()),
mDataLoaderManager(sm.getDataLoaderManager()),
@@ -804,32 +812,38 @@
return -EINVAL;
}
- std::unique_lock l(ifs->lock);
- if (!enableReadLogs) {
- return disableReadLogsLocked(*ifs);
- }
+ std::string packageName;
- if (!ifs->readLogsAllowed()) {
- LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId;
- return -EPERM;
- }
+ {
+ std::unique_lock l(ifs->lock);
+ if (!enableReadLogs) {
+ return disableReadLogsLocked(*ifs);
+ }
- if (!ifs->dataLoaderStub) {
- // This should never happen - only DL can call enableReadLogs.
- LOG(ERROR) << "enableReadLogs failed: invalid state";
- return -EPERM;
- }
+ if (!ifs->readLogsAllowed()) {
+ LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId;
+ return -EPERM;
+ }
- // Check installation time.
- const auto now = mClock->now();
- const auto startLoadingTs = ifs->startLoadingTs;
- if (startLoadingTs <= now && now - startLoadingTs > getReadLogsMaxInterval()) {
- LOG(ERROR) << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: "
- << storageId;
- return -EPERM;
- }
+ if (!ifs->dataLoaderStub) {
+ // This should never happen - only DL can call enableReadLogs.
+ LOG(ERROR) << "enableReadLogs failed: invalid state";
+ return -EPERM;
+ }
- const auto& packageName = ifs->dataLoaderStub->params().packageName;
+ // Check installation time.
+ const auto now = mClock->now();
+ const auto startLoadingTs = ifs->startLoadingTs;
+ if (startLoadingTs <= now && now - startLoadingTs > getReadLogsMaxInterval()) {
+ LOG(ERROR)
+ << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: "
+ << storageId;
+ return -EPERM;
+ }
+
+ packageName = ifs->dataLoaderStub->params().packageName;
+ ifs->setReadLogsRequested(true);
+ }
// Check loader usage stats permission and apop.
if (auto status =
@@ -849,8 +863,14 @@
return fromBinderStatus(status);
}
- if (auto status = applyStorageParamsLocked(*ifs, /*enableReadLogs=*/true); status != 0) {
- return status;
+ {
+ std::unique_lock l(ifs->lock);
+ if (!ifs->readLogsRequested()) {
+ return 0;
+ }
+ if (auto status = applyStorageParamsLocked(*ifs, /*enableReadLogs=*/true); status != 0) {
+ return status;
+ }
}
registerAppOpsCallback(packageName);
@@ -859,6 +879,7 @@
}
int IncrementalService::disableReadLogsLocked(IncFsMount& ifs) {
+ ifs.setReadLogsRequested(false);
return applyStorageParamsLocked(ifs, /*enableReadLogs=*/false);
}
@@ -1065,17 +1086,14 @@
return err;
}
if (params.size > 0) {
- // Only v2+ incfs supports automatically trimming file over-reserved sizes
- if (mIncFs->features() & incfs::Features::v2) {
- if (auto err = mIncFs->reserveSpace(ifs->control, normPath, params.size)) {
- if (err != -EOPNOTSUPP) {
- LOG(ERROR) << "Failed to reserve space for a new file: " << err;
- (void)mIncFs->unlink(ifs->control, normPath);
- return err;
- } else {
- LOG(WARNING) << "Reserving space for backing file isn't supported, "
- "may run out of disk later";
- }
+ if (auto err = mIncFs->reserveSpace(ifs->control, id, params.size)) {
+ if (err != -EOPNOTSUPP) {
+ LOG(ERROR) << "Failed to reserve space for a new file: " << err;
+ (void)mIncFs->unlink(ifs->control, normPath);
+ return err;
+ } else {
+ LOG(WARNING) << "Reserving space for backing file isn't supported, "
+ "may run out of disk later";
}
}
if (!data.empty()) {
@@ -1659,6 +1677,15 @@
}
}
+void IncrementalService::trimReservedSpaceV1(const IncFsMount& ifs) {
+ mIncFs->forEachFile(ifs.control, [this](auto&& control, auto&& fileId) {
+ if (mIncFs->isFileFullyLoaded(control, fileId) == incfs::LoadingState::Full) {
+ mIncFs->reserveSpace(control, fileId, -1);
+ }
+ return true;
+ });
+}
+
void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params,
DataLoaderStatusListener&& statusListener,
const StorageHealthCheckParams& healthCheckParams,
@@ -1678,6 +1705,22 @@
std::move(statusListener), healthCheckParams,
std::move(healthListener), path::join(ifs.root, constants().mount));
+ // pre-v2 IncFS doesn't do automatic reserved space trimming - need to run it manually
+ if (!(mIncFs->features() & incfs::Features::v2)) {
+ addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool {
+ if (!state.fullyLoaded) {
+ return true;
+ }
+
+ const auto ifs = getIfs(storageId);
+ if (!ifs) {
+ return false;
+ }
+ trimReservedSpaceV1(*ifs);
+ return false;
+ });
+ }
+
addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool {
if (!state.fullyLoaded || state.readLogsEnabled) {
return true;
@@ -2214,7 +2257,6 @@
affected.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
std::unique_lock ll(ifs->lock);
-
if (ifs->mountId == id && ifs->dataLoaderStub &&
ifs->dataLoaderStub->params().packageName == packageName) {
affected.push_back(ifs);
@@ -2222,7 +2264,8 @@
}
}
for (auto&& ifs : affected) {
- applyStorageParamsLocked(*ifs, /*enableReadLogs=*/false);
+ std::unique_lock ll(ifs->lock);
+ disableReadLogsLocked(*ifs);
}
}
@@ -2251,7 +2294,7 @@
mIfsStateCallbacks[storageId].emplace_back(std::move(callback));
}
if (wasEmpty) {
- addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval,
+ addTimedJob(*mTimedQueue, kAllStoragesId, Constants::progressUpdateInterval,
[this]() { processIfsStateCallbacks(); });
}
}
@@ -2267,29 +2310,36 @@
}
IfsStateCallbacks::iterator it;
if (storageId == kInvalidStorageId) {
- // First entry, initialize the it.
+ // First entry, initialize the |it|.
it = mIfsStateCallbacks.begin();
} else {
- // Subsequent entries, update the storageId, and shift to the new one.
- it = mIfsStateCallbacks.find(storageId);
+ // Subsequent entries, update the |storageId|, and shift to the new one (not that
+ // it guarantees much about updated items, but at least the loop will finish).
+ it = mIfsStateCallbacks.lower_bound(storageId);
if (it == mIfsStateCallbacks.end()) {
- // Was removed while processing, too bad.
+ // Nothing else left, too bad.
break;
}
-
- auto& callbacks = it->second;
- if (callbacks.empty()) {
- std::swap(callbacks, local);
+ if (it->first != storageId) {
+ local.clear(); // Was removed during processing, forget the old callbacks.
} else {
- callbacks.insert(callbacks.end(), local.begin(), local.end());
- }
- if (callbacks.empty()) {
- it = mIfsStateCallbacks.erase(it);
- if (mIfsStateCallbacks.empty()) {
- return;
+ // Put the 'surviving' callbacks back into the map and advance the position.
+ auto& callbacks = it->second;
+ if (callbacks.empty()) {
+ std::swap(callbacks, local);
+ } else {
+ callbacks.insert(callbacks.end(), std::move_iterator(local.begin()),
+ std::move_iterator(local.end()));
+ local.clear();
}
- } else {
- ++it;
+ if (callbacks.empty()) {
+ it = mIfsStateCallbacks.erase(it);
+ if (mIfsStateCallbacks.empty()) {
+ return;
+ }
+ } else {
+ ++it;
+ }
}
}
@@ -2309,7 +2359,7 @@
processIfsStateCallbacks(storageId, local);
}
- addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval,
+ addTimedJob(*mTimedQueue, kAllStoragesId, Constants::progressUpdateInterval,
[this]() { processIfsStateCallbacks(); });
}
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index a8f32de..fb6f56c 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -95,7 +95,8 @@
#pragma GCC diagnostic pop
static constexpr StorageId kInvalidStorageId = -1;
- static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max();
+ static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max() - 1;
+ static constexpr StorageId kAllStoragesId = kMaxStorageId + 1;
static constexpr BootClockTsUs kMaxBootClockTsUs = std::numeric_limits<BootClockTsUs>::max();
@@ -116,6 +117,7 @@
enum StorageFlags {
ReadLogsAllowed = 1 << 0,
ReadLogsEnabled = 1 << 1,
+ ReadLogsRequested = 1 << 2,
};
struct LoadingProgress {
@@ -365,6 +367,9 @@
void setReadLogsEnabled(bool value);
int32_t readLogsEnabled() const { return (flags & StorageFlags::ReadLogsEnabled); }
+ void setReadLogsRequested(bool value);
+ int32_t readLogsRequested() const { return (flags & StorageFlags::ReadLogsRequested); }
+
static void cleanupFilesystem(std::string_view root);
};
@@ -447,6 +452,8 @@
StorageLoadingProgressListener&& progressListener);
long getMillsSinceOldestPendingRead(StorageId storage);
+ void trimReservedSpaceV1(const IncFsMount& ifs);
+
private:
const std::unique_ptr<VoldServiceWrapper> mVold;
const std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager;
@@ -468,7 +475,7 @@
std::mutex mCallbacksLock;
std::unordered_map<std::string, sp<AppOpsListener>> mCallbackRegistered;
- using IfsStateCallbacks = std::unordered_map<StorageId, std::vector<IfsStateCallback>>;
+ using IfsStateCallbacks = std::map<StorageId, std::vector<IfsStateCallback>>;
std::mutex mIfsStateCallbacksLock;
IfsStateCallbacks mIfsStateCallbacks;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 3465499..8e416f3 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -212,6 +212,9 @@
std::string_view path) const final {
return incfs::isFullyLoaded(control, path);
}
+ incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const final {
+ return incfs::isFullyLoaded(control, id);
+ }
incfs::LoadingState isEverythingFullyLoaded(const Control& control) const final {
return incfs::isEverythingFullyLoaded(control);
}
@@ -227,9 +230,8 @@
ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final {
return incfs::writeBlocks({blocks.data(), size_t(blocks.size())});
}
- ErrorCode reserveSpace(const Control& control, std::string_view path,
- IncFsSize size) const final {
- return incfs::reserveSpace(control, path, size);
+ ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const final {
+ return incfs::reserveSpace(control, id, size);
}
WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final {
@@ -238,19 +240,26 @@
ErrorCode setUidReadTimeouts(const Control& control,
const std::vector<android::os::incremental::PerUidReadTimeouts>&
perUidReadTimeouts) const final {
- std::vector<incfs::UidReadTimeouts> timeouts;
- timeouts.resize(perUidReadTimeouts.size());
+ std::vector<incfs::UidReadTimeouts> timeouts(perUidReadTimeouts.size());
for (int i = 0, size = perUidReadTimeouts.size(); i < size; ++i) {
- auto&& timeout = timeouts[i];
+ auto& timeout = timeouts[i];
const auto& perUidTimeout = perUidReadTimeouts[i];
timeout.uid = perUidTimeout.uid;
timeout.minTimeUs = perUidTimeout.minTimeUs;
timeout.minPendingTimeUs = perUidTimeout.minPendingTimeUs;
timeout.maxPendingTimeUs = perUidTimeout.maxPendingTimeUs;
}
-
return incfs::setUidReadTimeouts(control, timeouts);
}
+ ErrorCode forEachFile(const Control& control, FileCallback cb) const final {
+ return incfs::forEachFile(control,
+ [&](auto& control, FileId id) { return cb(control, id); });
+ }
+ ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const final {
+ return incfs::forEachIncompleteFile(control, [&](auto& control, FileId id) {
+ return cb(control, id);
+ });
+ }
};
static JNIEnv* getOrAttachJniEnv(JavaVM* jvm);
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index a787db5..d4cdcbe 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -84,6 +84,8 @@
void(std::string_view root, std::string_view backingDir,
std::span<std::pair<std::string_view, std::string_view>> binds)>;
+ using FileCallback = android::base::function_ref<bool(const Control& control, FileId fileId)>;
+
static std::string toString(FileId fileId);
virtual ~IncFsWrapper() = default;
@@ -105,14 +107,14 @@
const Control& control, std::string_view path) const = 0;
virtual incfs::LoadingState isFileFullyLoaded(const Control& control,
std::string_view path) const = 0;
+ virtual incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const = 0;
virtual incfs::LoadingState isEverythingFullyLoaded(const Control& control) const = 0;
virtual ErrorCode link(const Control& control, std::string_view from,
std::string_view to) const = 0;
virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0;
virtual UniqueFd openForSpecialOps(const Control& control, FileId id) const = 0;
virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0;
- virtual ErrorCode reserveSpace(const Control& control, std::string_view path,
- IncFsSize size) const = 0;
+ virtual ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const = 0;
virtual WaitResult waitForPendingReads(
const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0;
@@ -120,6 +122,8 @@
const Control& control,
const std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts)
const = 0;
+ virtual ErrorCode forEachFile(const Control& control, FileCallback cb) const = 0;
+ virtual ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const = 0;
};
class AppOpsManagerWrapper {
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index e6d4872..ddb7784 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -379,6 +379,7 @@
std::string_view path));
MOCK_CONST_METHOD2(isFileFullyLoaded,
incfs::LoadingState(const Control& control, std::string_view path));
+ MOCK_CONST_METHOD2(isFileFullyLoaded, incfs::LoadingState(const Control& control, FileId id));
MOCK_CONST_METHOD1(isEverythingFullyLoaded, incfs::LoadingState(const Control& control));
MOCK_CONST_METHOD3(link,
ErrorCode(const Control& control, std::string_view from,
@@ -386,14 +387,15 @@
MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path));
MOCK_CONST_METHOD2(openForSpecialOps, UniqueFd(const Control& control, FileId id));
MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks));
- MOCK_CONST_METHOD3(reserveSpace,
- ErrorCode(const Control& control, std::string_view path, IncFsSize size));
+ MOCK_CONST_METHOD3(reserveSpace, ErrorCode(const Control& control, FileId id, IncFsSize size));
MOCK_CONST_METHOD3(waitForPendingReads,
WaitResult(const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer));
MOCK_CONST_METHOD2(setUidReadTimeouts,
ErrorCode(const Control& control,
const std::vector<PerUidReadTimeouts>& perUidReadTimeouts));
+ MOCK_CONST_METHOD2(forEachFile, ErrorCode(const Control& control, FileCallback cb));
+ MOCK_CONST_METHOD2(forEachIncompleteFile, ErrorCode(const Control& control, FileCallback cb));
MockIncFs() {
ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return());
@@ -1594,7 +1596,7 @@
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
IncrementalService::CreateOptions::CreateNew);
- EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+ EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>()))
.Times(1)
.WillOnce(Return(incfs::LoadingState::MissingBlocks));
ASSERT_GT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0);
@@ -1605,7 +1607,7 @@
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
IncrementalService::CreateOptions::CreateNew);
- EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+ EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>()))
.Times(1)
.WillOnce(Return(incfs::LoadingState(-1)));
ASSERT_LT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0);
@@ -1616,7 +1618,7 @@
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
IncrementalService::CreateOptions::CreateNew);
- EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+ EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>()))
.Times(1)
.WillOnce(Return(incfs::LoadingState::Full));
ASSERT_EQ(0, (int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
@@ -1735,10 +1737,10 @@
ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
// IfsState callback present.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
auto callback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Not loaded yet.
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
@@ -1751,10 +1753,10 @@
ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
// Still present.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
callback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Fully loaded.
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_)).WillOnce(Return(incfs::LoadingState::Full));
@@ -1797,10 +1799,10 @@
ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
// IfsState callback present.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
auto callback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Not loaded yet.
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
@@ -1813,10 +1815,10 @@
ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
// Still present.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
callback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Fully loaded.
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
@@ -1832,10 +1834,10 @@
ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
// Still present.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
callback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Disable readlogs and expect the unbind.
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
@@ -2007,10 +2009,10 @@
{
// Timed callback present -> 0 progress.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Call it again.
timedCallback();
@@ -2018,10 +2020,10 @@
{
// Still present -> some progress.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Fully loaded but readlogs collection enabled.
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
@@ -2032,10 +2034,10 @@
{
// Still present -> fully loaded + readlogs.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Now disable readlogs.
ASSERT_GE(mDataLoader->setStorageParams(false), 0);
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/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 22b2f7e..d220444 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -1015,7 +1015,7 @@
assertNotNull(info);
if (timestamp != null) {
- final long tolerance = 1000; // ms
+ final long tolerance = 10000; // ms
assertTrue(timestamp - tolerance <= info.getTimestamp());
assertTrue(timestamp + tolerance >= info.getTimestamp());
}
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/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/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index dbb415c..e7ffea0 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -54,6 +54,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
@Presubmit
@@ -117,10 +118,13 @@
}
}
+ private static final long CURRENT_TIME = 1234567890L;
+
private File mCacheDir;
private File mUpdatableFontFilesDir;
private File mConfigFile;
private List<File> mPreinstalledFontDirs;
+ private Supplier<Long> mCurrentTimeSupplier = () -> CURRENT_TIME;
@SuppressWarnings("ResultOfMethodCallIgnored")
@Before
@@ -147,7 +151,7 @@
@Test
public void construct() throws Exception {
- long expectedModifiedDate = 1234567890;
+ long expectedModifiedDate = CURRENT_TIME / 2;
FakeFontFileParser parser = new FakeFontFileParser();
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
@@ -155,7 +159,7 @@
writeConfig(config, mConfigFile);
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dirForPreparation.loadFontFileMap();
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
.isEqualTo(expectedModifiedDate);
@@ -168,6 +172,9 @@
+ " <font>foo.ttf</font>"
+ " <font>bar.ttf</font>"
+ "</family>")));
+ // Verifies that getLastModifiedTimeMillis() returns the value of currentTimeMillis.
+ assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
+ .isEqualTo(CURRENT_TIME);
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
@@ -175,7 +182,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3);
@@ -199,7 +206,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
assertThat(dir.getFontFamilyMap()).isEmpty();
@@ -211,7 +218,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dirForPreparation.loadFontFileMap();
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE),
@@ -229,7 +236,7 @@
dirForPreparation.getFontFileMap().get("foo.ttf").getAbsolutePath());
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
@@ -243,7 +250,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dirForPreparation.loadFontFileMap();
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE),
@@ -262,7 +269,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
@@ -276,7 +283,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dirForPreparation.loadFontFileMap();
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE),
@@ -296,7 +303,7 @@
FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2");
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
// For foo.ttf, preinstalled font (revision 5) should be used.
assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf");
@@ -317,7 +324,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- new File("/dev/null"));
+ new File("/dev/null"), mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
assertThat(dir.getFontFamilyMap()).isEmpty();
@@ -329,7 +336,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dirForPreparation.loadFontFileMap();
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE),
@@ -351,7 +358,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
// The state should be rolled back as a whole if one of the update requests fail.
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
@@ -369,7 +376,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE)));
@@ -387,7 +394,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE)));
@@ -406,7 +413,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2", GOOD_SIGNATURE)));
@@ -428,7 +435,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE)));
@@ -445,7 +452,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Arrays.asList(
@@ -463,7 +470,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -485,7 +492,7 @@
FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test.ttf,1");
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -517,7 +524,7 @@
try {
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- readonlyFile);
+ readonlyFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -551,7 +558,7 @@
public long getRevision(File file) throws IOException {
return 0;
}
- }, fakeFsverityUtil, mConfigFile);
+ }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -580,7 +587,7 @@
public long getRevision(File file) throws IOException {
return 0;
}
- }, fakeFsverityUtil, mConfigFile);
+ }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -617,7 +624,7 @@
FakeFontFileParser parser = new FakeFontFileParser();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -637,7 +644,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE)));
@@ -660,7 +667,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Arrays.asList(
@@ -682,7 +689,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -703,7 +710,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -723,7 +730,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
// We assume we have monospace.
assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace");
@@ -755,7 +762,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
deleted file mode 100644
index 70718f7..0000000
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.hdmi;
-
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertEquals;
-
-import android.content.Context;
-import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.HdmiPortInfo;
-import android.hardware.hdmi.IHdmiControlCallback;
-import android.os.Looper;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Tests for {@link HdmiControlServiceBinderAPITest} class.
- */
-@SmallTest
-@RunWith(JUnit4.class)
-public class HdmiControlServiceBinderAPITest {
-
- private Context mContext;
-
- private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice {
-
- private boolean mCanGoToStandby;
- private boolean mIsStandby;
- private boolean mIsDisabled;
-
- protected HdmiCecLocalDeviceMyDevice(HdmiControlService service, int deviceType) {
- super(service, deviceType);
- }
-
- @Override
- protected void onAddressAllocated(int logicalAddress, int reason) {
- }
-
- @Override
- protected int getPreferredAddress() {
- return 0;
- }
-
- @Override
- protected void setPreferredAddress(int addr) {
- }
-
- @Override
- protected boolean canGoToStandby() {
- return mCanGoToStandby;
- }
-
- @Override
- protected void disableDevice(
- boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
- mIsDisabled = true;
- originalCallback.onCleared(this);
- }
-
- @Override
- protected void onStandby(boolean initiatedByCec, int standbyAction) {
- mIsStandby = true;
- }
-
- protected boolean isStandby() {
- return mIsStandby;
- }
-
- protected boolean isDisabled() {
- return mIsDisabled;
- }
-
- protected void setCanGoToStandby(boolean canGoToStandby) {
- mCanGoToStandby = canGoToStandby;
- }
-
- @Override
- protected int getRcProfile() {
- return 0;
- }
-
- @Override
- protected List<Integer> getRcFeatures() {
- return Collections.emptyList();
- }
-
- @Override
- protected List<Integer> getDeviceFeatures() {
- return Collections.emptyList();
- }
- }
-
- private static final String TAG = "HdmiControlServiceBinderAPITest";
- private HdmiControlService mHdmiControlService;
- private HdmiCecController mHdmiCecController;
- private HdmiCecLocalDevicePlayback mPlaybackDevice;
- private FakeNativeWrapper mNativeWrapper;
- private Looper mMyLooper;
- private TestLooper mTestLooper = new TestLooper();
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
- private HdmiPortInfo[] mHdmiPortInfo;
- private int mResult;
- private int mPowerStatus;
-
- @Before
- public void SetUp() {
- mContext = InstrumentationRegistry.getTargetContext();
- // Some tests expect no logical addresses being allocated at the beginning of the test.
- setHdmiControlEnabled(false);
-
- HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContext);
-
- mHdmiControlService =
- new HdmiControlService(mContext) {
- @Override
- void sendCecCommand(HdmiCecMessage command) {
- switch (command.getOpcode()) {
- case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
- HdmiCecMessage message =
- HdmiCecMessageBuilder.buildReportPowerStatus(
- Constants.ADDR_TV,
- Constants.ADDR_PLAYBACK_1,
- HdmiControlManager.POWER_STATUS_ON);
- handleCecCommand(message);
- break;
- default:
- return;
- }
- }
-
- @Override
- boolean isPowerStandby() {
- return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
- }
-
- @Override
- protected HdmiCecConfig getHdmiCecConfig() {
- return hdmiCecConfig;
- }
- };
- mMyLooper = mTestLooper.getLooper();
-
- mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
- @Override
- protected void wakeUpIfActiveSource() {}
-
- @Override
- protected void setPreferredAddress(int addr) {}
-
- @Override
- protected int getPreferredAddress() {
- return Constants.ADDR_PLAYBACK_1;
- }
- };
- mPlaybackDevice.init();
-
- mHdmiControlService.setIoLooper(mMyLooper);
-
- mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController = HdmiCecController.createWithNativeWrapper(
- mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
- mHdmiControlService.setCecController(mHdmiCecController);
- mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
-
- mLocalDevices.add(mPlaybackDevice);
- mHdmiPortInfo = new HdmiPortInfo[1];
- mHdmiPortInfo[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
- mNativeWrapper.setPortInfo(mHdmiPortInfo);
- mHdmiControlService.initService();
- mResult = -1;
- mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
-
- mTestLooper.dispatchAll();
- }
-
- @Test
- public void oneTouchPlay_addressNotAllocated() {
- assertThat(mHdmiControlService.isAddressAllocated()).isFalse();
- mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
- @Override
- public void onComplete(int result) {
- mResult = result;
- }
- });
- assertEquals(mResult, -1);
- assertThat(mPlaybackDevice.isActiveSource()).isFalse();
-
- setHdmiControlEnabled(true);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
- mTestLooper.dispatchAll();
- assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
- assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS);
- assertThat(mPlaybackDevice.isActiveSource()).isTrue();
- }
-
- @Test
- public void oneTouchPlay_addressAllocated() {
- setHdmiControlEnabled(true);
-
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
- mTestLooper.dispatchAll();
- assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
- mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
- @Override
- public void onComplete(int result) {
- mResult = result;
- }
- });
- assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS);
- assertThat(mPlaybackDevice.isActiveSource()).isTrue();
- }
-
- private void setHdmiControlEnabled(boolean enabled) {
- int value = enabled ? 1 : 0;
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDMI_CONTROL_ENABLED,
- value);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index c61635c..d74bff2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -37,13 +37,13 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.test.TestLooper;
+import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -79,12 +79,18 @@
@Mock
private IThermalService mIThermalServiceMock;
- @Before
- public void setUp() throws Exception {
+ /**
+ * Manually called before tests, because some tests require HDMI control to be disabled.
+ * @param hdmiControlEnabled whether to enable the global setting hdmi_control.
+ * @throws Exception
+ */
+ public void setUp(boolean hdmiControlEnabled) throws Exception {
MockitoAnnotations.initMocks(this);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ setHdmiControlEnabled(hdmiControlEnabled);
+
PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
@@ -146,7 +152,9 @@
}
@Test
- public void succeedWithUnknownTvDevice() {
+ public void succeedWithUnknownTvDevice() throws Exception {
+ setUp(true);
+
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
playbackDevice.init();
@@ -185,7 +193,9 @@
}
@Test
- public void succeedAfterGettingPowerStatusOn_Cec14b() {
+ public void succeedAfterGettingPowerStatusOn_Cec14b() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -225,7 +235,9 @@
}
@Test
- public void succeedAfterGettingTransientPowerStatus_Cec14b() {
+ public void succeedAfterGettingTransientPowerStatus_Cec14b() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -275,7 +287,9 @@
}
@Test
- public void timeOut_Cec14b() {
+ public void timeOut_Cec14b() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -316,7 +330,9 @@
}
@Test
- public void succeedIfPowerStatusOn_Cec20() {
+ public void succeedIfPowerStatusOn_Cec20() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -348,7 +364,9 @@
}
@Test
- public void succeedIfPowerStatusUnknown_Cec20() {
+ public void succeedIfPowerStatusUnknown_Cec20() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -390,7 +408,9 @@
}
@Test
- public void succeedIfPowerStatusStandby_Cec20() {
+ public void succeedIfPowerStatusStandby_Cec20() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -431,6 +451,73 @@
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
+ @Test
+ public void succeedWithAddressNotAllocated_Cec14b() throws Exception {
+ setUp(false);
+
+ assertThat(mHdmiControlService.isAddressAllocated()).isFalse();
+
+ HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
+ mHdmiControlService);
+ playbackDevice.init();
+ mLocalDevices.add(playbackDevice);
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiControlService.oneTouchPlay(callback);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.hasResult()).isFalse();
+ assertThat(playbackDevice.isActiveSource()).isFalse();
+
+ setHdmiControlEnabled(true);
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_TV,
+ playbackDevice.mAddress,
+ HdmiControlManager.POWER_STATUS_ON
+ );
+ mNativeWrapper.onCecMessage(reportPowerStatusMessage);
+
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ assertThat(playbackDevice.isActiveSource()).isTrue();
+ }
+
+ @Test
+ public void succeedWithAddressAllocated_Cec14b() throws Exception {
+ setUp(true);
+
+ HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
+ mHdmiControlService);
+ playbackDevice.init();
+ mLocalDevices.add(playbackDevice);
+
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
+
+ TestCallback callback = new TestCallback();
+ mHdmiControlService.oneTouchPlay(callback);
+
+ HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_TV,
+ playbackDevice.mAddress,
+ HdmiControlManager.POWER_STATUS_ON
+ );
+ mNativeWrapper.onCecMessage(reportPowerStatusMessage);
+
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ assertThat(playbackDevice.isActiveSource()).isTrue();
+ }
+
private static class TestActionTimer implements ActionTimer {
private int mState;
@@ -456,9 +543,19 @@
mCallbackResult.add(result);
}
+ private boolean hasResult() {
+ return mCallbackResult.size() != 0;
+ }
+
private int getResult() {
assertThat(mCallbackResult.size()).isEqualTo(1);
return mCallbackResult.get(0);
}
}
+
+ private void setHdmiControlEnabled(boolean enabled) {
+ int value = enabled ? 1 : 0;
+ Settings.Global.putInt(mContextSpy.getContentResolver(),
+ Settings.Global.HDMI_CONTROL_ENABLED, value);
+ }
}
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 fb01ff6..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.onStatsProviderLimitReached("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/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index 748e5df..22a6445 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -7,6 +7,14 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsAssistTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
}
]
}
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 c9b0a3e..0742cb4 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -54,7 +54,6 @@
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
import android.system.OsConstants;
-import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.view.IWindowManager;
@@ -64,6 +63,7 @@
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import com.android.server.LocalServices;
+import com.android.server.wm.ActivityAssistInfo;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens;
@@ -187,24 +187,23 @@
mSessionComponentName, mUser, mContext, this,
mInfo.getServiceInfo().applicationInfo.uid, mHandler);
}
- List<Pair<IBinder, Integer>> allVisibleActivities =
+ List<ActivityAssistInfo> allVisibleActivities =
LocalServices.getService(ActivityTaskManagerInternal.class)
.getTopVisibleActivities();
- List<Pair<IBinder, Integer>> visibleActivities = null;
+ List<ActivityAssistInfo> visibleActivities = null;
if (activityToken != null) {
visibleActivities = new ArrayList();
int activitiesCount = allVisibleActivities.size();
for (int i = 0; i < activitiesCount; i++) {
- if (allVisibleActivities.get(i).first == activityToken) {
- visibleActivities.add(
- new Pair<>(activityToken, allVisibleActivities.get(i).second));
+ ActivityAssistInfo info = allVisibleActivities.get(i);
+ if (info.getActivityToken() == activityToken) {
+ visibleActivities.add(info);
break;
}
}
} else {
- visibleActivities = LocalServices.getService(ActivityTaskManagerInternal.class)
- .getTopVisibleActivities();
+ visibleActivities = allVisibleActivities;
}
return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
visibleActivities);
@@ -402,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");
@@ -427,7 +426,7 @@
mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
options, sharedMemory);
} else {
- mHotwordDetectionConnection.setConfigLocked(options, sharedMemory);
+ mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 428d342..cc021a9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -56,7 +56,6 @@
import android.service.voice.IVoiceInteractionSessionService;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionSession;
-import android.util.Pair;
import android.util.Slog;
import android.view.IWindowManager;
@@ -68,6 +67,7 @@
import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.wm.ActivityAssistInfo;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -102,6 +102,7 @@
IVoiceInteractionSession mSession;
IVoiceInteractor mInteractor;
ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
+ private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>();
AssistDataRequester mAssistDataRequester;
IVoiceInteractionSessionShowCallback mShowCallback =
@@ -192,7 +193,7 @@
public boolean showLocked(Bundle args, int flags, int disabledContext,
IVoiceInteractionSessionShowCallback showCallback,
- List<Pair<IBinder, Integer>> topActivities) {
+ List<ActivityAssistInfo> topActivities) {
if (mBound) {
if (!mFullyBound) {
mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
@@ -216,7 +217,7 @@
int topActivitiesCount = topActivities.size();
final ArrayList<IBinder> topActivitiesToken = new ArrayList<>(topActivitiesCount);
for (int i = 0; i < topActivitiesCount; i++) {
- topActivitiesToken.add(topActivities.get(i).first);
+ topActivitiesToken.add(topActivities.get(i).getActivityToken());
}
mAssistDataRequester.requestAssistData(topActivitiesToken,
fetchData,
@@ -243,8 +244,16 @@
} else {
doHandleAssistWithoutData(topActivities);
}
- } else if (showCallback != null) {
- mPendingShowCallbacks.add(showCallback);
+ } else {
+ if (showCallback != null) {
+ mPendingShowCallbacks.add(showCallback);
+ }
+ if (!assistDataRequestNeeded) {
+ // If no data are required we are not passing trough mAssistDataRequester. As
+ // a consequence, when a new session is delivered it is needed to process those
+ // requests manually.
+ mPendingHandleAssistWithoutData = topActivities;
+ }
}
mCallback.onSessionShown(this);
return true;
@@ -258,17 +267,17 @@
return false;
}
- private void doHandleAssistWithoutData(List<Pair<IBinder, Integer>> topActivities) {
+ private void doHandleAssistWithoutData(List<ActivityAssistInfo> topActivities) {
final int activityCount = topActivities.size();
for (int i = 0; i < activityCount; i++) {
- final Pair<IBinder, Integer> topActivity = topActivities.get(i);
- final IBinder activityId = topActivity.first;
- final int taskId = topActivity.second;
+ final ActivityAssistInfo topActivity = topActivities.get(i);
+ final IBinder assistToken = topActivity.getAssistToken();
+ final int taskId = topActivity.getTaskId();
final int activityIndex = i;
try {
mSession.handleAssist(
taskId,
- activityId,
+ assistToken,
/* assistData = */ null,
/* assistStructure = */ null,
/* assistContent = */ null,
@@ -468,6 +477,10 @@
} catch (RemoteException e) {
}
mAssistDataRequester.processPendingAssistData();
+ if (!mPendingHandleAssistWithoutData.isEmpty()) {
+ doHandleAssistWithoutData(mPendingHandleAssistWithoutData);
+ mPendingHandleAssistWithoutData.clear();
+ }
}
return true;
}
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/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
index f49d4fc..4259a86 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
@@ -32,7 +32,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -98,8 +98,8 @@
};
@DataClass.Generated(
- time = 1604522375155L,
- codegenVersion = "1.0.20",
+ time = 1616541542813L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java",
inputSignatures = "private int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
index e8cce23..677094b 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
@@ -46,7 +46,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -120,8 +120,8 @@
};
@DataClass.Generated(
- time = 1604522376059L,
- codegenVersion = "1.0.20",
+ time = 1616541543730L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java",
inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
index 9de6552..eb260ab 100644
--- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
@@ -54,7 +54,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -412,8 +412,8 @@
}
@DataClass.Generated(
- time = 1604522374190L,
- codegenVersion = "1.0.20",
+ time = 1616541541942L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java",
inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 5a3e273..158e065 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -23,8 +23,11 @@
import android.annotation.StringDef;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
+import android.companion.ICompanionDeviceManager;
import android.content.pm.PackageManager;
import android.net.LinkAddress;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -282,6 +285,16 @@
/**
+ * Binder types are also supported
+ */
+ private @NonNull IBinder mToken = new Binder();
+ /**
+ * AIDL interface types are also supported
+ */
+ private @Nullable ICompanionDeviceManager mIPCInterface = null;
+
+
+ /**
* Manually declaring any method that would otherwise be generated suppresses its generation,
* allowing for fine-grained overrides of the generated behavior.
*/
@@ -344,7 +357,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -492,6 +505,10 @@
*
* Validation annotations following {@link Each} annotation, will be applied for each
* array/collection element instead.
+ * @param token
+ * Binder types are also supported
+ * @param iPCInterface
+ * AIDL interface types are also supported
*/
@DataClass.Generated.Member
public SampleDataClass(
@@ -514,7 +531,9 @@
@Nullable LinkAddress[] linkAddresses5,
@StringRes int stringRes,
@android.annotation.IntRange(from = 0, to = 6) int dayOfWeek,
- @Size(2) @NonNull @FloatRange(from = 0f) float[] coords) {
+ @Size(2) @NonNull @FloatRange(from = 0f) float[] coords,
+ @NonNull IBinder token,
+ @Nullable ICompanionDeviceManager iPCInterface) {
this.mNum = num;
this.mNum2 = num2;
this.mNum4 = num4;
@@ -597,6 +616,10 @@
"from", 0f);
}
+ this.mToken = token;
+ AnnotationValidations.validate(
+ NonNull.class, null, mToken);
+ this.mIPCInterface = iPCInterface;
onConstructed();
}
@@ -797,6 +820,22 @@
}
/**
+ * Binder types are also supported
+ */
+ @DataClass.Generated.Member
+ public @NonNull IBinder getToken() {
+ return mToken;
+ }
+
+ /**
+ * AIDL interface types are also supported
+ */
+ @DataClass.Generated.Member
+ public @Nullable ICompanionDeviceManager getIPCInterface() {
+ return mIPCInterface;
+ }
+
+ /**
* When using transient fields for caching it's often also a good idea to initialize them
* lazily.
*
@@ -1089,6 +1128,26 @@
return this;
}
+ /**
+ * Binder types are also supported
+ */
+ @DataClass.Generated.Member
+ public @NonNull SampleDataClass setToken(@NonNull IBinder value) {
+ mToken = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mToken);
+ return this;
+ }
+
+ /**
+ * AIDL interface types are also supported
+ */
+ @DataClass.Generated.Member
+ public @NonNull SampleDataClass setIPCInterface(@NonNull ICompanionDeviceManager value) {
+ mIPCInterface = value;
+ return this;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -1115,7 +1174,9 @@
"linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " +
"stringRes = " + mStringRes + ", " +
"dayOfWeek = " + mDayOfWeek + ", " +
- "coords = " + java.util.Arrays.toString(mCoords) +
+ "coords = " + java.util.Arrays.toString(mCoords) + ", " +
+ "token = " + mToken + ", " +
+ "iPCInterface = " + mIPCInterface +
" }";
}
@@ -1151,7 +1212,9 @@
&& java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5)
&& mStringRes == that.mStringRes
&& mDayOfWeek == that.mDayOfWeek
- && java.util.Arrays.equals(mCoords, that.mCoords);
+ && java.util.Arrays.equals(mCoords, that.mCoords)
+ && Objects.equals(mToken, that.mToken)
+ && Objects.equals(mIPCInterface, that.mIPCInterface);
}
@Override
@@ -1181,6 +1244,8 @@
_hash = 31 * _hash + mStringRes;
_hash = 31 * _hash + mDayOfWeek;
_hash = 31 * _hash + java.util.Arrays.hashCode(mCoords);
+ _hash = 31 * _hash + Objects.hashCode(mToken);
+ _hash = 31 * _hash + Objects.hashCode(mIPCInterface);
return _hash;
}
@@ -1208,6 +1273,8 @@
actionInt.acceptInt(this, "stringRes", mStringRes);
actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek);
actionObject.acceptObject(this, "coords", mCoords);
+ actionObject.acceptObject(this, "token", mToken);
+ actionObject.acceptObject(this, "iPCInterface", mIPCInterface);
}
/** @deprecated May cause boxing allocations - use with caution! */
@@ -1234,6 +1301,8 @@
action.acceptObject(this, "stringRes", mStringRes);
action.acceptObject(this, "dayOfWeek", mDayOfWeek);
action.acceptObject(this, "coords", mCoords);
+ action.acceptObject(this, "token", mToken);
+ action.acceptObject(this, "iPCInterface", mIPCInterface);
}
@DataClass.Generated.Member
@@ -1269,6 +1338,7 @@
if (mOtherParcelable != null) flg |= 0x40;
if (mLinkAddresses4 != null) flg |= 0x800;
if (mLinkAddresses5 != null) flg |= 0x10000;
+ if (mIPCInterface != null) flg |= 0x200000;
dest.writeLong(flg);
dest.writeInt(mNum);
dest.writeInt(mNum2);
@@ -1290,6 +1360,8 @@
dest.writeInt(mStringRes);
dest.writeInt(mDayOfWeek);
dest.writeFloatArray(mCoords);
+ dest.writeStrongBinder(mToken);
+ if (mIPCInterface != null) dest.writeStrongInterface(mIPCInterface);
}
@Override
@@ -1326,6 +1398,8 @@
int stringRes = in.readInt();
int dayOfWeek = in.readInt();
float[] coords = in.createFloatArray();
+ IBinder token = (IBinder) in.readStrongBinder();
+ ICompanionDeviceManager iPCInterface = (flg & 0x200000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder());
this.mNum = num;
this.mNum2 = num2;
@@ -1409,6 +1483,10 @@
"from", 0f);
}
+ this.mToken = token;
+ AnnotationValidations.validate(
+ NonNull.class, null, mToken);
+ this.mIPCInterface = iPCInterface;
onConstructed();
}
@@ -1454,6 +1532,8 @@
private @StringRes int mStringRes;
private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek;
private @Size(2) @NonNull @FloatRange(from = 0f) float[] mCoords;
+ private @NonNull IBinder mToken;
+ private @Nullable ICompanionDeviceManager mIPCInterface;
private long mBuilderFieldsSet = 0L;
@@ -1794,10 +1874,32 @@
return this;
}
+ /**
+ * Binder types are also supported
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setToken(@NonNull IBinder value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x100000;
+ mToken = value;
+ return this;
+ }
+
+ /**
+ * AIDL interface types are also supported
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setIPCInterface(@NonNull ICompanionDeviceManager value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x200000;
+ mIPCInterface = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull SampleDataClass build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x100000; // Mark builder used
+ mBuilderFieldsSet |= 0x400000; // Mark builder used
if ((mBuilderFieldsSet & 0x10) == 0) {
mName2 = "Bob";
@@ -1841,6 +1943,12 @@
if ((mBuilderFieldsSet & 0x80000) == 0) {
mCoords = new float[] { 0f, 0f };
}
+ if ((mBuilderFieldsSet & 0x100000) == 0) {
+ mToken = new Binder();
+ }
+ if ((mBuilderFieldsSet & 0x200000) == 0) {
+ mIPCInterface = null;
+ }
SampleDataClass o = new SampleDataClass(
mNum,
mNum2,
@@ -1861,12 +1969,14 @@
mLinkAddresses5,
mStringRes,
mDayOfWeek,
- mCoords);
+ mCoords,
+ mToken,
+ mIPCInterface);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x100000) != 0) {
+ if ((mBuilderFieldsSet & 0x400000) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -1874,10 +1984,10 @@
}
@DataClass.Generated(
- time = 1604522372172L,
- codegenVersion = "1.0.20",
+ time = 1616541539978L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
- inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
+ inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
@Deprecated
private void __metadata() {}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
index 3ab3445..a535e22 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -85,7 +85,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -253,8 +253,8 @@
}
@DataClass.Generated(
- time = 1604522373190L,
- codegenVersion = "1.0.20",
+ time = 1616541540898L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
index 8901cac..d409624 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
@@ -36,7 +36,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -135,8 +135,8 @@
};
@DataClass.Generated(
- time = 1604522377998L,
- codegenVersion = "1.0.20",
+ time = 1616541545539L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull java.lang.String mBar\nclass NestedDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
@@ -160,7 +160,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -259,8 +259,8 @@
};
@DataClass.Generated(
- time = 1604522378007L,
- codegenVersion = "1.0.20",
+ time = 1616541545548L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull long mBaz2\nclass NestedDataClass3 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
@@ -274,7 +274,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -373,8 +373,8 @@
};
@DataClass.Generated(
- time = 1604522378015L,
- codegenVersion = "1.0.20",
+ time = 1616541545552L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull java.lang.String mBaz\nclass NestedDataClass2 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
index ac776f3..3583b95 100644
--- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
@@ -64,7 +64,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -89,8 +89,8 @@
}
@DataClass.Generated(
- time = 1604522377011L,
- codegenVersion = "1.0.20",
+ time = 1616541544639L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java",
inputSignatures = "private @android.annotation.Nullable java.util.List<java.util.Set<?>> mUsesWildcards\npublic @android.annotation.NonNull java.lang.String someMethod(int)\nprivate @android.annotation.IntRange void annotatedWithConstArg()\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)")
@Deprecated
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 2e57467..c679d04 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -25,17 +25,24 @@
name: "StagedInstallInternalTestApp",
manifest: "app/AndroidManifest.xml",
srcs: ["app/src/**/*.java"],
- static_libs: ["androidx.test.rules", "cts-install-lib"],
+ static_libs: [
+ "androidx.test.rules",
+ "cts-install-lib",
+ ],
test_suites: ["general-tests"],
java_resources: [
":com.android.apex.apkrollback.test_v2",
+ ":StagedInstallTestApexV2_WrongSha",
],
}
java_test_host {
name: "StagedInstallInternalTest",
srcs: ["src/**/*.java"],
- libs: ["tradefed", "cts-shim-host-lib"],
+ libs: [
+ "tradefed",
+ "cts-shim-host-lib",
+ ],
static_libs: [
"testng",
"compatibility-tradefed",
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index ad8aac1..e633c87 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -17,6 +17,7 @@
package com.android.tests.stagedinstallinternal;
import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -50,6 +51,9 @@
private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");
+ private static final TestApp APEX_WRONG_SHA_V2 = new TestApp(
+ "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
+ "com.android.apex.cts.shim.v2_wrong_sha.apex");
private File mTestStateFile = new File(
InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
@@ -128,6 +132,26 @@
}
@Test
+ public void testStagedSessionShouldCleanUpOnVerificationFailure() throws Exception {
+ InstallUtils.commitExpectingFailure(AssertionError.class, "apexd verification failed",
+ Install.single(APEX_WRONG_SHA_V2).setStaged());
+ }
+
+ @Test
+ public void testStagedSessionShouldCleanUpOnOnSuccess_Commit() throws Exception {
+ int sessionId = Install.single(TestApp.A1).setStaged().commit();
+ storeSessionId(sessionId);
+ }
+
+ @Test
+ public void testStagedSessionShouldCleanUpOnOnSuccess_Verify() throws Exception {
+ int sessionId = retrieveLastSessionId();
+ PackageInstaller.SessionInfo info = InstallUtils.getStagedSessionInfo(sessionId);
+ assertThat(info).isNotNull();
+ assertThat(info.isStagedSessionApplied()).isTrue();
+ }
+
+ @Test
public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception {
InstallUtils.commitExpectingFailure(AssertionError.class, "INSTALL_FAILED_INVALID_APK",
Install.single(TestApp.AIncompleteSplit).setStaged());
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 8dc53ac..9fd190c 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -44,7 +44,6 @@
import org.junit.runner.RunWith;
import java.io.File;
-import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -252,6 +251,28 @@
}
@Test
+ public void testStagedSessionShouldCleanUpOnVerificationFailure() throws Exception {
+ assumeTrue("Device does not support updating APEX",
+ mHostUtils.isApexUpdateSupported());
+ List<String> before = getStagingDirectories();
+ runPhase("testStagedSessionShouldCleanUpOnVerificationFailure");
+ List<String> after = getStagingDirectories();
+ assertThat(after).isEqualTo(before);
+ }
+
+ @Test
+ @LargeTest
+ public void testStagedSessionShouldCleanUpOnOnSuccess() throws Exception {
+ List<String> before = getStagingDirectories();
+ runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Commit");
+ assertThat(getStagingDirectories()).isNotEqualTo(before);
+ getDevice().reboot();
+ runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Verify");
+ List<String> after = getStagingDirectories();
+ assertThat(after).isEqualTo(before);
+ }
+
+ @Test
public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception {
List<String> before = getStagingDirectories();
runPhase("testStagedInstallationShouldCleanUpOnValidationFailure");
@@ -273,12 +294,14 @@
//create random directories in /data/app-staging folder
getDevice().enableAdbRoot();
getDevice().executeShellCommand("mkdir /data/app-staging/session_123");
- getDevice().executeShellCommand("mkdir /data/app-staging/random_name");
+ getDevice().executeShellCommand("mkdir /data/app-staging/session_456");
getDevice().disableAdbRoot();
- assertThat(getStagingDirectories()).isNotEmpty();
+ assertThat(getStagingDirectories()).contains("session_123");
+ assertThat(getStagingDirectories()).contains("session_456");
getDevice().reboot();
- assertThat(getStagingDirectories()).isEmpty();
+ assertThat(getStagingDirectories()).doesNotContain("session_123");
+ assertThat(getStagingDirectories()).doesNotContain("session_456");
}
@Test
@@ -304,9 +327,6 @@
.stream().filter(entry -> entry.getName().matches("session_\\d+"))
.map(entry -> entry.getName())
.collect(Collectors.toList());
- } catch (Exception e) {
- // Return an empty list if any error
- return Collections.EMPTY_LIST;
} finally {
getDevice().disableAdbRoot();
}
diff --git a/tests/net/common/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java
index 11d44b8..cd9da8e 100644
--- a/tests/net/common/java/android/net/NetworkTest.java
+++ b/tests/net/common/java/android/net/NetworkTest.java
@@ -22,11 +22,16 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.os.Build;
import android.platform.test.annotations.AppModeFull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,6 +48,9 @@
public class NetworkTest {
final Network mNetwork = new Network(99);
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
@Test
public void testBindSocketOfInvalidFdThrows() throws Exception {
@@ -151,6 +159,23 @@
}
@Test
+ public void testFromNetworkHandle() {
+ final Network network = new Network(1234);
+ assertEquals(network.getNetId(),
+ Network.fromNetworkHandle(network.getNetworkHandle()).getNetId());
+ }
+
+ // Parsing private DNS bypassing handle was not supported until S
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testFromNetworkHandle_S() {
+ final Network network = new Network(1234, true);
+
+ final Network recreatedNetwork = Network.fromNetworkHandle(network.getNetworkHandle());
+ assertEquals(network.netId, recreatedNetwork.netId);
+ assertEquals(network.getNetIdForResolv(), recreatedNetwork.getNetIdForResolv());
+ }
+
+ @Test
public void testGetPrivateDnsBypassingCopy() {
final Network copy = mNetwork.getPrivateDnsBypassingCopy();
assertEquals(mNetwork.netId, copy.netId);
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 d82565a..44298d4 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -30,6 +30,10 @@
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
@@ -90,10 +94,6 @@
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
-import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_DATA_SAVER;
-import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_BATTERY_SAVER;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
@@ -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/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index 7a0be97..bcd6ed7 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -61,7 +61,8 @@
"android.view.PendingInsetsControllerTest",
"android.window.WindowContextTest",
"android.window.WindowMetricsHelperTest",
- "android.app.activity.ActivityThreadTest"
+ "android.app.activity.ActivityThreadTest",
+ "android.window.WindowContextControllerTest"
};
public FrameworksTestsFilter(Bundle testArgs) {
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();
diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt
index 02ebaef..74392dd 100644
--- a/tools/codegen/src/com/android/codegen/FieldInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -220,11 +220,12 @@
isBinder(FieldInnerType!!) -> "BinderList"
else -> "ParcelableList"
}
+ isStrongBinder(Type) -> "StrongBinder"
isIInterface(Type) -> "StrongInterface"
- isBinder(Type) -> "StrongBinder"
else -> "TypedObject"
}.capitalize()
+ private fun isStrongBinder(type: String) = type == "Binder" || type == "IBinder"
private fun isBinder(type: String) = type == "Binder" || type == "IBinder" || isIInterface(type)
private fun isIInterface(type: String) = type.length >= 2 && type[0] == 'I' && type[1].isUpperCase()
}
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index d9ad649..4da4019 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.22"
+const val CODEGEN_VERSION = "1.0.23"
const val CANONICAL_BUILDER_CLASS = "Builder"
const val BASE_BUILDER_CLASS = "BaseBuilder"