diff options
454 files changed, 15977 insertions, 7182 deletions
diff --git a/Android.bp b/Android.bp index 6a47db1e22a4..8b15f02b06ed 100644 --- a/Android.bp +++ b/Android.bp @@ -465,6 +465,7 @@ java_library { static_libs: [ "android.net.ipsec.ike.stubs.module_lib", "framework-appsearch.stubs.module_lib", + "framework-connectivity.stubs.module_lib", "framework-graphics.stubs.module_lib", "framework-media.stubs.module_lib", "framework-mediaprovider.stubs.module_lib", @@ -487,6 +488,7 @@ java_library { "android.net.ipsec.ike.impl", "framework-minus-apex", "framework-appsearch.impl", + "framework-connectivity.impl", "framework-graphics.impl", "framework-mediaprovider.impl", "framework-permission.impl", @@ -643,14 +645,17 @@ java_defaults { defaults: ["framework-aidl-export-defaults"], srcs: [ ":framework-non-updatable-sources", - ":framework-connectivity-sources", "core/java/**/*.logtags", ], // See comment on framework-atb-backward-compatibility module below exclude_srcs: ["core/java/android/content/pm/AndroidTestBaseUpdater.java"], aidl: { generate_get_transaction_name: true, - local_include_dirs: ["media/aidl"], + local_include_dirs: [ + "media/aidl", + // TODO: move to include_dirs when migrated to packages/modules + "packages/Connectivity/framework/aidl-export", + ], include_dirs: ["frameworks/av/aidl"], }, dxflags: [ @@ -703,8 +708,6 @@ java_library { apex_available: ["//apex_available:platform"], visibility: [ "//frameworks/base", - // TODO: remove when framework-connectivity can build against API - "//frameworks/base/packages/Connectivity/framework", // TODO(b/147128803) remove the below lines "//frameworks/base/apex/appsearch/framework", "//frameworks/base/apex/blobstore/framework", @@ -743,6 +746,7 @@ java_library { static_libs: [ "app-compat-annotations", "framework-minus-apex", + "framework-connectivity.impl", // TODO(b/182859030): should be removed "framework-appsearch.impl", // TODO(b/146218515): should be removed "framework-updatable-stubs-module_libs_api", ], @@ -1461,7 +1465,7 @@ java_library { ], 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 a3e05dc7cd85..5acfe6ab6430 100644 --- a/apex/appsearch/framework/api/current.txt +++ b/apex/appsearch/framework/api/current.txt @@ -26,10 +26,8 @@ package android.app.appsearch { } 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 @@ package android.app.appsearch { 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 @@ package android.app.appsearch { 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 @@ package android.app.appsearch { 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 @@ package android.app.appsearch { 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 @@ package android.app.appsearch { } 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 @@ package android.app.appsearch { } 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 @@ package android.app.appsearch { } 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 @@ package android.app.appsearch { 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 @@ package android.app.appsearch { 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 @@ package android.app.appsearch { 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 0c6b86b68636..977682771ca3 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 @@ public class AppSearchManager { /** 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 @@ public class AppSearchManager { 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 000000000000..e585d9147895 --- /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 ce4aad1f0c1e..0f6468a62794 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 @@ package android.app.appsearch; 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.ArrayMap; 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 @@ public final class AppSearchSession implements Closeable { 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 @@ public final class AppSearchSession implements Closeable { * 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 @@ public final class AppSearchSession implements Closeable { // 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 @@ public final class AppSearchSession implements Closeable { } 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 @@ public final class AppSearchSession implements Closeable { mIsMutated = true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (Throwable t) { + callback.accept(AppSearchResult.throwableToFailedResult(t)); } } @@ -332,8 +383,7 @@ public final class AppSearchSession implements Closeable { // 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 @@ public final class AppSearchSession implements Closeable { // 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 @@ public final class AppSearchSession implements Closeable { } } } + + /** + * 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 d436488f34f2..48c397f324ec 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.AppSearchBatchResult; 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 @@ interface IAppSearchManager { * 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 @@ interface IAppSearchManager { 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 79b7b7552196..a8048dc5a4c4 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 @@ public final class AppSearchSchema { 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 @@ public final class AppSearchSchema { } /** - * @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 4ce95ea358f4..8c9d950abe25 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 @@ import java.util.Set; 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 @@ public class GenericDocument { * * <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 @@ public class GenericDocument { } /** - * 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 6881a27d6846..1719e14b01e3 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 @@ public final class GetByUriRequest { * <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 3e693670f7bc..1f56ef3588b1 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 android.util.ArraySet; 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 @@ public class GetSchemaResponse extends HashSet<AppSearchSchema> { /** * 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 455cf3a26b50..8da68c0b4898 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 @@ public final class RemoveByUriRequest { * <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 2cd08c631006..646e73c24bd9 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 @@ public final class ReportUsageRequest { /** 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 e66056f0380b..55a228d94c10 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 @@ public final class SearchResult { 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 @@ public final class SearchResult { * <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 @@ public final class SearchResult { 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 @@ public final class SearchResult { 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 e840ffcc69b1..1324451f47f9 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 @@ public final class SetSchemaRequest { } /** - * 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 @@ public final class SetSchemaRequest { } /** - * 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 32d7e043e954..c9473bdeff32 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.annotation.NonNull; 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 @@ import java.util.Set; * @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 6e3fb82ba213..91ed6cd4a638 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.NonNull; 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.IAppSearchResultCallback; 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.LocalServices; 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 @@ public class AppSearchManagerService extends SystemService { schemasPackageAccessible.put(entry.getKey(), packageIdentifiers); } AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId); - impl.setSchema( + SetSchemaResponse setSchemaResponse = impl.setSchema( packageName, databaseName, schemas, @@ -133,8 +141,8 @@ public class AppSearchManagerService extends SystemService { 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 @@ public class AppSearchManagerService extends SystemService { } @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 8c953d12fd98..cacf8801a8bb 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.Context; 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 @@ public final class ImplInstanceManager { 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 78474295078a..1ed26d670f36 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 @@ public class VisibilityStore { 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 @@ public class VisibilityStore { 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 @@ public class VisibilityStore { 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 bc3064155d34..f0de4962ad3c 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 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { @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/Android.bp b/apex/jobscheduler/framework/Android.bp index b51c2aae0bb8..f766fcffb58c 100644 --- a/apex/jobscheduler/framework/Android.bp +++ b/apex/jobscheduler/framework/Android.bp @@ -32,6 +32,7 @@ java_library { }, libs: [ "app-compat-annotations", + "framework-connectivity.stubs.module_lib", "framework-minus-apex", "unsupportedappusage", ], diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 78c5b156bc45..3ea1922547d5 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -18,6 +18,7 @@ package android.app; 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.Context; 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 com.android.i18n.timezone.ZoneInfoDb; 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 class AlarmManager { 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 @@ public class AlarmManager { 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 class AlarmManager { public void doAlarm(IAlarmCompleteListener alarmManager) { mCompletion = alarmManager; - mHandler.post(this); + mExecutor.execute(this); } @Override @@ -368,7 +381,7 @@ public class AlarmManager { */ 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 class AlarmManager { 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 class AlarmManager { 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 @@ public class AlarmManager { } /** + * 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 @@ public class AlarmManager { */ @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 @@ public class AlarmManager { @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 class AlarmManager { 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 @@ public class AlarmManager { 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 @@ public class AlarmManager { 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 class AlarmManager { 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 class AlarmManager { 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 class AlarmManager { 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 58fc87476f2a..3c9496f557a1 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; 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.Slog; 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { @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 @@ public class AlarmManagerService extends SystemService { // 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 class AlarmManagerService extends SystemService { */ 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { // 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 80698f7cedc9..bebf0190191b 100644 --- a/apex/media/framework/api/current.txt +++ b/apex/media/framework/api/current.txt @@ -69,10 +69,12 @@ package android.media { 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 8bdca766e0dd..cff422d0aafe 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.NonNull; 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.HashMap; 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 @@ public final class MediaParser { 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 @@ public final class MediaParser { MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH)); nativeSubmitMetrics( + // TODO: mLogSessionId, mParserName, mCreatedByName, String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool), @@ -1341,6 +1345,15 @@ public final class MediaParser { 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 @@ public final class MediaParser { // 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 af0a739bffaa..935cf7067043 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1009,7 +1009,7 @@ package android { 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 @@ package android.app.admin { 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 @@ package android.app.admin { } 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 @@ package android.app.admin { 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 @@ package android.content.pm { 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 @@ package android.content.pm { 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 @@ package android.content.res { 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 @@ package android.hardware.camera2 { 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 @@ package android.hardware.camera2 { 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 @@ package android.hardware.camera2 { 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 @@ package android.hardware.camera2 { 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 @@ package android.hardware.camera2 { 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 @@ package android.hardware.camera2 { 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 @@ package android.hardware.camera2 { 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 @@ package android.hardware.camera2 { 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 @@ package android.hardware.camera2.params { 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 @@ package android.hardware.camera2.params { 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 @@ package android.location { 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 @@ package android.location { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media.metrics { } 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 @@ package android.media.metrics { 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 @@ package android.provider { 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 @@ package android.service.autofill { 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 @@ package android.service.autofill { 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 @@ package android.telephony { } 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 { @@ -48294,6 +48364,7 @@ package android.view { method protected int computeVerticalScrollRange(); method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(); method public void createContextMenu(android.view.ContextMenu); + method @Nullable public android.view.translation.ViewTranslationRequest createTranslationRequest(@NonNull int[]); method @Deprecated public void destroyDrawingCache(); method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets); method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent); @@ -48514,6 +48585,7 @@ package android.view { method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarThumbDrawable(); method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarTrackDrawable(); method public int getVerticalScrollbarWidth(); + method @Nullable public android.view.translation.ViewTranslationCallback getViewTranslationCallback(); method public android.view.ViewTreeObserver getViewTreeObserver(); method public int getVisibility(); method public final int getWidth(); @@ -48657,6 +48729,7 @@ package android.view { method public void onStartTemporaryDetach(); method public boolean onTouchEvent(android.view.MotionEvent); method public boolean onTrackballEvent(android.view.MotionEvent); + method public void onTranslationResponse(@NonNull android.view.translation.ViewTranslationResponse); method @CallSuper public void onVisibilityAggregated(boolean); method protected void onVisibilityChanged(@NonNull android.view.View, int); method public void onWindowFocusChanged(boolean); @@ -48865,6 +48938,7 @@ package android.view { method public void setVerticalScrollbarPosition(int); method public void setVerticalScrollbarThumbDrawable(@Nullable android.graphics.drawable.Drawable); method public void setVerticalScrollbarTrackDrawable(@Nullable android.graphics.drawable.Drawable); + method public void setViewTranslationCallback(@NonNull android.view.translation.ViewTranslationCallback); method public void setVisibility(int); method @Deprecated public void setWillNotCacheDrawing(boolean); method public void setWillNotDraw(boolean); @@ -52687,6 +52761,12 @@ package android.view.translation { method public void onStarted(@NonNull String, @NonNull String); } + @UiThread public interface ViewTranslationCallback { + method public boolean onClearTranslation(@NonNull android.view.View); + method public boolean onHideTranslation(@NonNull android.view.View); + method public boolean onShowTranslation(@NonNull android.view.View); + } + public final class ViewTranslationRequest implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.view.autofill.AutofillId getAutofillId(); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 8dc5c15d48ce..5925b7263cf5 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -201,14 +201,6 @@ package android.net { 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 d54526d1c52b..761774b373b7 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3,6 +3,7 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android.app { 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 @@ package android.app.admin { 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 @@ package android.bluetooth { 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.bluetooth.le { 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 @@ package android.content.pm { 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 @@ package android.content.pm.verify.domain { } 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 @@ package android.location { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.net.netstats.provider { 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 @@ package android.os.storage { 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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.service.voice { 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 @@ package android.service.voice { } 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 @@ package android.telephony.data { 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.util { 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 3926e39fd29a..b50b8dd3fdb4 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -48,6 +48,14 @@ package android.app.prediction { } +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 4d0c76526b35..97ad48c96829 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -233,6 +233,8 @@ package android.app { 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.audiopolicy { package android.media.metrics { public final class LogSessionId { - method @NonNull public String getStringId(); + ctor public LogSessionId(@NonNull String); } } @@ -1931,6 +1933,17 @@ package android.os.vibrator { 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 @@ package android.permission { 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 a6aa28effe00..7e4af1ad7952 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1561,12 +1561,14 @@ public class AppOpsManager { * * @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 47de04078b45..5964f71d28db 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -135,4 +135,20 @@ public final class GameManager { 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 c8e1478a9930..4bf8a3f77bca 100644 --- a/core/java/android/app/IGameManagerService.aidl +++ b/core/java/android/app/IGameManagerService.aidl @@ -22,4 +22,5 @@ package android.app; 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 747a2de80db0..da64dcd5b5f5 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -524,6 +524,16 @@ public class DeviceAdminReceiver extends BroadcastReceiver { "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 @@ public class DeviceAdminReceiver extends BroadcastReceiver { 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 @@ public class DeviceAdminReceiver extends BroadcastReceiver { 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 117df02a2d7c..26e67411f174 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -872,8 +872,7 @@ public class DevicePolicyManager { * * 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 @@ public class DevicePolicyManager { "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 @@ public class DevicePolicyManager { } /** + * 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 0ad92b70168e..5e49a985c781 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -495,6 +495,10 @@ interface IDevicePolicyManager { 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 4fb557780d04..632572dea3c6 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -567,6 +567,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @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 86bd8a2b7a4e..2ce715630c93 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -425,6 +425,32 @@ public final class CompanionDeviceManager { 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 95d3515abb80..83db35872f74 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -51,4 +51,6 @@ interface ICompanionDeviceManager { 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 d0d406a0c9e6..01b554a56720 100644 --- a/core/java/android/content/OWNERS +++ b/core/java/android/content/OWNERS @@ -8,3 +8,5 @@ per-file Intent.java = patb@google.com 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 feb58a30e519..0952b3e1233c 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -550,9 +550,18 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { 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 66295eb513d8..9283e5fd3cbd 100644 --- a/core/java/android/content/pm/AppSearchPerson.java +++ b/core/java/android/content/pm/AppSearchPerson.java @@ -107,7 +107,7 @@ public class AppSearchPerson extends GenericDocument { 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 c04d3bef3af3..b2478ca12191 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -217,9 +217,8 @@ public class AppSearchShortcutInfo extends GenericDocument { @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 @@ public class AppSearchShortcutInfo extends GenericDocument { @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 @@ public class AppSearchShortcutInfo extends GenericDocument { /** * @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 0da453d81be6..5f3ec36438a3 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1363,7 +1363,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * 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 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * 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 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { 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 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { 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 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { zygotePreloadName = orig.zygotePreloadName; gwpAsanMode = orig.gwpAsanMode; memtagMode = orig.memtagMode; - nativeHeapZeroInit = orig.nativeHeapZeroInit; + nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized; requestOptimizedExternalStorageAccess = orig.requestOptimizedExternalStorageAccess; } @@ -1891,7 +1917,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { 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 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { zygotePreloadName = source.readString8(); gwpAsanMode = source.readInt(); memtagMode = source.readInt(); - nativeHeapZeroInit = sForBoolean.unparcel(source); + nativeHeapZeroInitialized = source.readInt(); requestOptimizedExternalStorageAccess = sForBoolean.unparcel(source); } @@ -2382,7 +2408,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** {@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 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** {@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 3dd5ee102090..632c0f54375c 100644 --- a/core/java/android/content/pm/ProcessInfo.java +++ b/core/java/android/content/pm/ProcessInfo.java @@ -62,8 +62,7 @@ public class ProcessInfo implements Parcelable { /** * 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 @@ public class ProcessInfo implements Parcelable { this.deniedPermissions = orig.deniedPermissions; this.gwpAsanMode = orig.gwpAsanMode; this.memtagMode = orig.memtagMode; - this.nativeHeapZeroInit = orig.nativeHeapZeroInit; + this.nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized; } @@ -101,7 +100,7 @@ public class ProcessInfo implements Parcelable { * @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 @@ public class ProcessInfo implements Parcelable { @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 @@ public class ProcessInfo implements Parcelable { 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 @@ public class ProcessInfo implements Parcelable { 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 @@ public class ProcessInfo implements Parcelable { 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 @@ public class ProcessInfo implements Parcelable { 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 @@ public class ProcessInfo implements Parcelable { }; @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 60f321883e98..23945eebf546 100644 --- a/core/java/android/content/pm/SuspendDialogInfo.java +++ b/core/java/android/content/pm/SuspendDialogInfo.java @@ -65,16 +65,20 @@ public final class SuspendDialogInfo implements Parcelable { 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 @@ public final class SuspendDialogInfo implements Parcelable { } /** + * @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 @@ public final class SuspendDialogInfo implements Parcelable { } /** - * @return the text to be shown + * @return the text to be shown on the neutral button * @hide */ @StringRes @@ -157,6 +171,16 @@ public final class SuspendDialogInfo implements Parcelable { } /** + * @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 @@ public final class SuspendDialogInfo implements Parcelable { } 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 @@ public final class SuspendDialogInfo implements Parcelable { } 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 @@ public final class SuspendDialogInfo implements Parcelable { 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 @@ public final class SuspendDialogInfo implements Parcelable { } 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 final class SuspendDialogInfo implements Parcelable { 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 @@ public final class SuspendDialogInfo implements Parcelable { 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 @@ public final class SuspendDialogInfo implements Parcelable { 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 final class SuspendDialogInfo implements Parcelable { 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 @@ public final class SuspendDialogInfo implements Parcelable { 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 @@ public final class SuspendDialogInfo implements Parcelable { } /** + * 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 @@ public final class SuspendDialogInfo implements Parcelable { } /** + * 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 4dc9ce8be750..1c65e0014635 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.CallSuper; 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 @@ public interface ParsingPackage extends ParsingPackageRead { 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 065ed2e95eca..60aac76fa8a5 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -382,12 +382,14 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { 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 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { 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 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { 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 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { 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 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { 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 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { } @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 47dfa9d13c7c..cfd828ec7d3e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageRead.java +++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java @@ -887,20 +887,22 @@ public interface ParsingPackageRead extends Parcelable { * @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 0e1574c8da92..9f69d0c0afd3 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -2008,9 +2008,11 @@ public class ParsingPackageUtils { 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 d99c4109e5ad..ff6aaad09d09 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 @@ public class ParsedActivityUtils { | 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 89fef9d8e0dd..54a60d349331 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 @@ public class ParsedProcess implements Parcelable { @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 class ParsedProcess implements Parcelable { 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 @@ public class ParsedProcess implements Parcelable { 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 @@ public class ParsedProcess implements Parcelable { } @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 @@ public class ParsedProcess implements Parcelable { // 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 @@ public class ParsedProcess implements Parcelable { // 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 @@ public class ParsedProcess implements Parcelable { 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 @@ public class ParsedProcess implements Parcelable { }; @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 257977467312..e417e7407fbb 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 @@ public class ParsedProcessUtils { 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 d2d14410ff3c..33920c676170 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 @@ public final class DomainVerificationManager { */ @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 aed0823bd52f..ac4b7b7dc158 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -360,7 +360,7 @@ public class Resources { 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 @@ public class Resources { * 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 @@ public class Resources { } 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 @@ public class Resources { * 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 @@ public class Resources { * @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 @@ public class Resources { */ @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 class Resources { 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 class Resources { 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 @@ public class Resources { @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 @@ public class Resources { * <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 @@ public class Resources { * @hide */ public int[] getAllAttributes() { - return mThemeImpl.getAllAttributes(); + synchronized (mLock) { + return mThemeImpl.getAllAttributes(); + } } /** @@ -1738,7 +1768,9 @@ public class Resources { * @see ActivityInfo */ public @Config int getChangingConfigurations() { - return mThemeImpl.getChangingConfigurations(); + synchronized (mLock) { + return mThemeImpl.getChangingConfigurations(); + } } /** @@ -1749,23 +1781,31 @@ public class Resources { * @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 @@ public class Resources { */ @ViewDebug.ExportedProperty(category = "theme", hasAdjacentMapping = true) public String[] getTheme() { - return mThemeImpl.getTheme(); + synchronized (mLock) { + return mThemeImpl.getTheme(); + } } /** @hide */ @@ -1800,7 +1842,9 @@ public class Resources { * {@link #applyStyle(int, boolean)}. */ public void rebase() { - mThemeImpl.rebase(); + synchronized (mLock) { + mThemeImpl.rebase(); + } } /** @@ -1862,12 +1906,14 @@ public class Resources { @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 cbcdb3756a2c..553e11b46da5 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -1314,22 +1314,16 @@ public class ResourcesImpl { } 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 @@ public class ResourcesImpl { @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { - synchronized (mKey) { - 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; - } + 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; } @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 @@ public class ResourcesImpl { } @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 @@ public class ResourcesImpl { * {@link #applyStyle(int, boolean)}. */ void rebase() { - synchronized (mKey) { - 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); - } + 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); } } @@ -1455,10 +1435,8 @@ public class ResourcesImpl { @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 5eaa7662b250..2a349e9a4600 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -46,7 +46,7 @@ import java.util.Arrays; * 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 @@ public class TypedArray { } /** + * 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 07ebbaff67ea..6654c2c71049 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1210,6 +1210,25 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri 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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri 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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * 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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * 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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri 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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri 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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri 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 final class CameraCharacteristics extends CameraMetadata<CameraCharacteri 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 b7b1a147c822..726bca4d1cd2 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -507,6 +507,18 @@ public final class CameraManager { 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 @@ public final class CameraManager { 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 924dcee31a33..d4da3b9526c3 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -586,7 +586,7 @@ public abstract class CameraMetadata<TKey> { * 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 abstract class CameraMetadata<TKey> { */ 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 abstract class CameraMetadata<TKey> { 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 9ac2ff57ad7b..906256d70197 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -1391,6 +1391,13 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * 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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * 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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * 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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * 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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @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 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> 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 e7457e714f43..6ff68c11a974 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -812,6 +812,13 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * 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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * 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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * 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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * 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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @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 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { 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 0a4298162027..2920e670f15b 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.CameraCaptureSession; 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 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl 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 4defd23231b8..b578bf80c231 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -20,6 +20,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainRunna 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.ArrayList; 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 @@ public class CameraDeviceImpl extends CameraDevice 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 class CameraDeviceImpl extends CameraDevice }; 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 @@ public class CameraDeviceImpl extends CameraDevice mDeviceCallback = callback; mDeviceExecutor = executor; mCharacteristics = characteristics; + mPhysicalIdsToChars = physicalIdsToChars; mAppTargetSdkVersion = appTargetSdkVersion; mContext = ctx; @@ -1357,11 +1364,71 @@ public class CameraDeviceImpl extends CameraDevice } } + 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 @@ public class CameraDeviceImpl extends CameraDevice 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 @@ public class CameraDeviceImpl extends CameraDevice 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 0cdf744ecd68..aa84b024f69d 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -327,6 +327,10 @@ public class CameraMetadataNative implements Parcelable { 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 @@ public class CameraMetadataNative implements Parcelable { 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 @@ public class CameraMetadataNative implements Parcelable { }); 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 @@ public class CameraMetadataNative implements Parcelable { 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 @@ public class CameraMetadataNative implements Parcelable { 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 @@ public class CameraMetadataNative implements Parcelable { } 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 @@ public class CameraMetadataNative implements Parcelable { 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 @@ public class CameraMetadataNative implements Parcelable { 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 8a0172ee8018..34116aae8648 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -267,8 +267,8 @@ public final class MandatoryStreamCombination { 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 @@ public final class MandatoryStreamCombination { "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 @@ public final class MandatoryStreamCombination { 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 @@ public final class MandatoryStreamCombination { * @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 @@ public final class MandatoryStreamCombination { } /** + * 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 @@ public final class MandatoryStreamCombination { 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 @@ public final class MandatoryStreamCombination { combTemplate.mDescription); return null; } - streamsInfo.add(streamInfo); } @@ -1220,6 +1326,14 @@ public final class MandatoryStreamCombination { } /** + * 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 e31bd601fc03..84736dc73ad6 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -19,6 +19,7 @@ package android.hardware.camera2.params; 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.graphics.ImageFormat; 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.hardware.camera2.utils.SurfaceUtils; 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 final class OutputConfiguration implements Parcelable { */ 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 @@ public final class OutputConfiguration implements Parcelable { mIsShared = false; mPhysicalCameraId = null; mIsMultiResolution = false; + mSensorPixelModesUsed = new ArrayList<Integer>(); } /** @@ -399,6 +412,7 @@ public final class OutputConfiguration implements Parcelable { mIsShared = false; mPhysicalCameraId = null; mIsMultiResolution = false; + mSensorPixelModesUsed = new ArrayList<Integer>(); } /** @@ -485,6 +499,81 @@ public final class OutputConfiguration implements Parcelable { } /** + * 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 @@ public final class OutputConfiguration implements Parcelable { this.mIsShared = other.mIsShared; this.mPhysicalCameraId = other.mPhysicalCameraId; this.mIsMultiResolution = other.mIsMultiResolution; + this.mSensorPixelModesUsed = other.mSensorPixelModesUsed; } /** @@ -642,7 +732,8 @@ public final class OutputConfiguration implements Parcelable { 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 @@ public final class OutputConfiguration implements Parcelable { } mPhysicalCameraId = physicalCameraId; mIsMultiResolution = isMultiResolutionOutput; + mSensorPixelModesUsed = sensorPixelModesUsed; } /** @@ -766,6 +858,7 @@ public final class OutputConfiguration implements Parcelable { dest.writeTypedList(mSurfaces); dest.writeString(mPhysicalCameraId); dest.writeInt(mIsMultiResolution ? 1 : 0); + dest.writeList(mSensorPixelModesUsed); } /** @@ -798,7 +891,14 @@ public final class OutputConfiguration implements Parcelable { !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 @@ public final class OutputConfiguration implements Parcelable { 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 @@ public final class OutputConfiguration implements Parcelable { 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 @@ public final class OutputConfiguration implements Parcelable { // 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 6dd67447c321..2430c09d93f4 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -19,6 +19,7 @@ package android.hardware.display; 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 @@ public final class DisplayManager { 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 fd0431c5bc3f..06efc4fc263e 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -690,6 +690,37 @@ public final class DisplayManagerGlobal { } } + + /** + * 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 dee91445c224..3538ff17a5a2 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -119,6 +119,12 @@ interface IDisplayManager { // 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 01fd3964df1d..c83ccfa81f95 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -223,6 +223,14 @@ public final class InputManager { 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 96e7d3bc75c0..799ea4a15bc3 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -18,6 +18,7 @@ package android.net; 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 class NetworkPolicyManager { }) 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 @@ public class NetworkPolicyManager { /** @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 @@ public class NetworkPolicyManager { * {@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 @@ public class NetworkPolicyManager { */ @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 @@ public class NetworkPolicyManager { * @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 @@ public class NetworkPolicyManager { } @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 @@ public class NetworkPolicyManager { } 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 4078b249218c..74c3ba44b69e 100644 --- a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl +++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl @@ -23,6 +23,6 @@ package android.net.netstats.provider; */ 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 bd336dd348fe..7eaa01e262fe 100644 --- a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl +++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl @@ -26,6 +26,6 @@ import android.net.NetworkStats; 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 7639d2244cfe..23fc06927ef9 100644 --- a/core/java/android/net/netstats/provider/NetworkStatsProvider.java +++ b/core/java/android/net/netstats/provider/NetworkStatsProvider.java @@ -29,7 +29,8 @@ import android.os.RemoteException; @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 @@ public abstract class NetworkStatsProvider { } @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 @@ public abstract class NetworkStatsProvider { } /** - * 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 class NetworkStatsProvider { 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 4674aa2d120e..c47fc576ebe9 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1004,6 +1004,24 @@ public abstract class BatteryStats implements Parcelable { 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 class BatteryStats implements Parcelable { 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 9518bf197fa0..85861bc1d2aa 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -60,14 +60,18 @@ public final class BatteryUsageStatsQuery implements Parcelable { */ 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 @@ public final class BatteryUsageStatsQuery implements Parcelable { 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 @@ public final class BatteryUsageStatsQuery implements Parcelable { dest.writeInt(mFlags); dest.writeInt(mUserIds.length); dest.writeIntArray(mUserIds); + dest.writeLong(mMaxStatsAgeMs); } @Override @@ -132,6 +146,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { 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 @@ public final class BatteryUsageStatsQuery implements Parcelable { 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 81c38f84849e..9385402c3d72 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -202,4 +202,5 @@ interface IStorageManager { 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 cae20ed90aa1..81071682ab7e 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2124,6 +2124,52 @@ public class StorageManager { } } + + /** @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 @@ public class StorageManager { } /** + * 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 c94c0ffd4652..440d6f269646 100644 --- a/core/java/android/permission/PermGroupUsage.java +++ b/core/java/android/permission/PermGroupUsage.java @@ -18,6 +18,7 @@ package android.permission; 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 @@ import android.annotation.Nullable; * * @hide */ +@TestApi public final class PermGroupUsage { private final String mPackageName; @@ -36,7 +38,19 @@ public final class PermGroupUsage { 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 @@ public final class PermGroupUsage { 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 final class PermGroupUsage { 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 177e422e7851..baa25f07f514 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -26,6 +26,7 @@ import android.annotation.Nullable; 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 @@ public final class PermissionManager { 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 @@ public final class PermissionManager { * 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 @@ public final class PermissionManager { } /** @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 @@ public final class PermissionManager { @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 @@ public final class PermissionManager { * * @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 921911bbf479..2d6fa3c77966 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -71,13 +71,10 @@ public class PermissionUsageHelper { 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 @@ public class PermissionUsageHelper { 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 31cf63c93c3f..03b5a2e2568a 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -257,6 +257,14 @@ public final class DeviceConfig { 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 0fea48442941..620fa6517ca5 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -236,10 +236,21 @@ public final class DocumentsContract { 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 719c38392fb1..591f05f27b4e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -470,8 +470,8 @@ public final class Settings { * 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 @@ public final class Settings { "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 final class Settings { 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 04a4ca47d305..13274c6f27ee 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.AutofillId; 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 abstract class AutofillService extends Service { */ 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 @@ public abstract class AutofillService extends Service { 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 @@ public abstract class AutofillService extends Service { @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 23a1a3fee47c..d88e0945bdca 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -31,4 +31,5 @@ oneway interface IAutoFillService { 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 000000000000..6a4d2b834cef --- /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 000000000000..a47105a23816 --- /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 000000000000..b8a8cde12d3e --- /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 aeeaa97e2287..d9a310f7a6a3 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -618,7 +618,7 @@ public abstract class AugmentedAutofillService extends Service { 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 1ea40bea8e57..94ca68ff6af8 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -44,6 +44,7 @@ import android.os.Build; 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 @@ public class AlwaysOnHotwordDetector { // 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 @@ public class AlwaysOnHotwordDetector { 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 @@ public class AlwaysOnHotwordDetector { mTargetSdkVersion = targetSdkVersion; mSupportHotwordDetectionService = supportHotwordDetectionService; if (mSupportHotwordDetectionService) { - setHotwordDetectionServiceConfig(options, sharedMemory); + updateState(options, sharedMemory); } try { Identity identity = new Identity(); @@ -524,30 +573,28 @@ public class AlwaysOnHotwordDetector { /** * 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 686268c3d854..db984c246b2f 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -82,10 +82,10 @@ public abstract class HotwordDetectionService extends Service { } @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 @@ public abstract class HotwordDetectionService extends Service { /** * 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 8d01dd1bf340..0791f1ca49eb 100644 --- a/core/java/android/service/voice/IHotwordDetectionService.aidl +++ b/core/java/android/service/voice/IHotwordDetectionService.aidl @@ -34,5 +34,5 @@ oneway interface IHotwordDetectionService { 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 d47ae2783336..1da7dc403d39 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -343,11 +343,14 @@ public class PhoneStateListener { /** * 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}). - * - * @see #onDisplayInfoChanged + * 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 * @deprecated Use {@link TelephonyCallback.DisplayInfoListener} instead. */ @Deprecated @@ -981,8 +984,12 @@ public class PhoneStateListener { * <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 d0000005c237..18949cdbeeab 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -1057,7 +1057,6 @@ public class TelephonyCallback { * * @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 8c771baaea37..3d603737c48c 100644 --- a/core/java/android/uwb/AngleMeasurement.java +++ b/core/java/android/uwb/AngleMeasurement.java @@ -48,7 +48,10 @@ public final class AngleMeasurement implements Parcelable { * @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 98b7dbfa670f..8db6456c0380 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -64,8 +64,14 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { } @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 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { 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 6801c27851a9..dea32cd96756 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 static java.lang.annotation.RetentionPolicy.SOURCE; 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 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @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 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @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 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @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 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @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 f177451783dc..bd86a47f3918 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 class SoundEffectConstants { 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 @@ public class SoundEffectConstants { /** * 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 @@ public class SoundEffectConstants { * @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 f1eef9fad8a1..82106b09ca5c 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1389,6 +1389,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // 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 7455b8bc3cf3..a03e9e352c60 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -151,6 +151,8 @@ import android.view.inputmethod.InputConnection; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumEntry; import android.view.inspector.InspectableProperty.FlagEntry; +import android.view.translation.TranslationSpec.DataFormat; +import android.view.translation.ViewTranslationCallback; import android.view.translation.ViewTranslationRequest; import android.view.translation.ViewTranslationResponse; import android.widget.Checkable; @@ -5253,6 +5255,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Nullable private String[] mOnReceiveContentMimeTypes; + @Nullable + private ViewTranslationCallback mViewTranslationCallback; + /** * Simple constructor to use when creating a view from code. * @@ -26136,9 +26141,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>The sound effect will only be played if sound effects are enabled by the user, and * {@link #isSoundEffectsEnabled()} is true. * - * @param soundConstant One of the constants defined in {@link SoundEffectConstants} + * @param soundConstant One of the constants defined in {@link SoundEffectConstants}. */ - public void playSoundEffect(int soundConstant) { + public void playSoundEffect(@SoundEffectConstants.SoundEffect int soundConstant) { if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) { return; } @@ -30717,71 +30722,62 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + //TODO(b/1960696): update javadoc when dispatchRequestTranslation is ready. /** - * Returns a {@link ViewTranslationRequest} to the {@link onStartUiTranslation} which represents - * the content to be translated. - * - * <p>The default implementation does nothing and return null.</p> + * Returns a {@link ViewTranslationRequest} which represents the content to be translated. * - * @hide + * <p>The default implementation does nothing and returns null.</p> * - * @return the {@link ViewTranslationRequest} which contains the information to be translated. + * @param supportedFormats the supported translation formats. For now, the only possible value + * is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}. + * @return the {@link ViewTranslationRequest} which contains the information to be translated or + * {@code null} if this View doesn't support translation. + * The {@link AutofillId} must be set on the returned value. */ @Nullable - //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed. - public ViewTranslationRequest onCreateTranslationRequest() { + public ViewTranslationRequest createTranslationRequest( + @NonNull @DataFormat int[] supportedFormats) { return null; } /** - * Called when the user wants to show the original text instead of the translated text. - * - * @hide - * - * <p> The default implementation does nothing. - */ - //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed. - public void onPauseUiTranslation() { - // no-op - } - - /** - * User can switch back to show the original text, this method called when the user wants to - * re-show the translated text again. + * Returns a {@link ViewTranslationCallback} that is used to display/hide the translated + * information. If the View supports displaying translated content, it should implement + * {@link ViewTranslationCallback}. * - * @hide + * <p>The default implementation returns null if developers don't set the customized + * {@link ViewTranslationCallback} by {@link #setViewTranslationCallback} </p> * - * <p> The default implementation does nothing.</p> + * @return a {@link ViewTranslationCallback} that is used to control how to display the + * translated information or {@code null} if this View doesn't support translation. */ - //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed. - public void onRestoreUiTranslation() { - // no-op + @Nullable + public ViewTranslationCallback getViewTranslationCallback() { + return mViewTranslationCallback; } /** - * Called when the user finish the Ui translation and no longer to show the translated text. + * Sets a {@link ViewTranslationCallback} that is used to display/hide the translated + * information. Developers can provide the customized implementation for show/hide translated + * information. * - * @hide - * - * <p> The default implementation does nothing.</p> + * @param callback a {@link ViewTranslationCallback} that is used to control how to display the + * translated information */ - //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed. - public void onFinishUiTranslation() { - // no-op + public void setViewTranslationCallback(@NonNull ViewTranslationCallback callback) { + mViewTranslationCallback = callback; } /** - * Called when the request from {@link onStartUiTranslation} is completed by the translation - * service so that the translation result can be shown. - * - * @hide + * Called when the content from {@link #createTranslationRequest} had been translated by the + * TranslationService. * * <p> The default implementation does nothing.</p> * - * @param response the translated information which can be shown in the view. + * @param response a {@link ViewTranslationResponse} that contains the translated information + * which can be shown in the view. */ - //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed. - public void onTranslationComplete(@NonNull ViewTranslationResponse response) { + public void onTranslationResponse(@NonNull ViewTranslationResponse response) { // no-op } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index dbccf10822cd..0a246a68c56d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4293,7 +4293,7 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, * {@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 2b12230510bf..ce014693c4c4 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -143,6 +143,9 @@ public final class ContentCaptureEvent implements Parcelable { 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 @@ public final class ContentCaptureEvent implements Parcelable { /** @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 @@ public final class ContentCaptureEvent implements Parcelable { 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 @@ public final class ContentCaptureEvent implements Parcelable { 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 5ca793e3c394..f196f75861ec 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -43,12 +43,15 @@ import android.os.Handler; 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.lang.ref.WeakReference; 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 @@ public final class MainContentCaptureSession extends ContentCaptureSession { 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 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // 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); - - // 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())); + 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. + + 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 @@ public final class MainContentCaptureSession extends ContentCaptureSession { 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 @@ public final class MainContentCaptureSession extends ContentCaptureSession { ? Collections.emptyList() : mEvents; mEvents = null; + mLastComposingSpan.clear(); return new ParceledListSlice<>(events); } @@ -677,9 +714,16 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) { + // Since the same CharSequence instance may be reused in the TextView, we need to make + // a copy of its content so that its value will not be changed by subsequent updates + // in the TextView. + final String eventText = text == null ? null : text.toString(); + final boolean textHasComposingSpan = + text instanceof Spannable && BaseInputConnection.getComposingSpanStart( + (Spannable) text) >= 0; mHandler.post(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED) - .setAutofillId(id).setText(text))); + .setAutofillId(id).setText(eventText, textHasComposingSpan))); } /** Public because is also used by ViewRootImpl */ diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index d79ecca1426e..15d01ae6a8fc 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -46,7 +46,7 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; +import java.util.function.BiConsumer; /** * A controller to manage the ui translation requests for the {@link Activity}. @@ -77,6 +77,7 @@ public class UiTranslationController { private final HandlerThread mWorkerThread; @NonNull private final Handler mWorkerHandler; + private int mCurrentState; public UiTranslationController(Activity activity, Context context) { mActivity = activity; @@ -101,6 +102,9 @@ public class UiTranslationController { } Log.i(TAG, "updateUiTranslationState state: " + stateToString(state) + (DEBUG ? ", views: " + views : "")); + synchronized (mLock) { + mCurrentState = state; + } switch (state) { case STATE_UI_TRANSLATION_STARTED: final Pair<TranslationSpec, TranslationSpec> specs = @@ -114,14 +118,14 @@ public class UiTranslationController { } break; case STATE_UI_TRANSLATION_PAUSED: - runForEachView(View::onPauseUiTranslation); + runForEachView((view, callback) -> callback.onHideTranslation(view)); break; case STATE_UI_TRANSLATION_RESUMED: - runForEachView(View::onRestoreUiTranslation); + runForEachView((view, callback) -> callback.onShowTranslation(view)); break; case STATE_UI_TRANSLATION_FINISHED: destroyTranslators(); - runForEachView(View::onFinishUiTranslation); + runForEachView((view, callback) -> callback.onClearTranslation(view)); synchronized (mLock) { mViews.clear(); } @@ -232,11 +236,16 @@ public class UiTranslationController { } final SparseArray<ViewTranslationResponse> translatedResult = response.getViewTranslationResponses(); + // TODO(b/177960696): handle virtual views. Check the result if the AutofillId is virtual + // AutofillId? onTranslationCompleted(translatedResult); } private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) { if (!mActivity.isResumed()) { + if (DEBUG) { + Log.v(TAG, "onTranslationCompleted: Activity is not resumed."); + } return; } final int resultCount = translatedResult.size(); @@ -244,6 +253,11 @@ public class UiTranslationController { Log.v(TAG, "onTranslationCompleted: receive " + resultCount + " responses."); } synchronized (mLock) { + if (mCurrentState == STATE_UI_TRANSLATION_FINISHED) { + Log.w(TAG, "onTranslationCompleted: the translation state is finished now. " + + "Skip to show the translated text."); + return; + } for (int i = 0; i < resultCount; i++) { final ViewTranslationResponse response = translatedResult.get(i); final AutofillId autofillId = response.getAutofillId(); @@ -256,18 +270,28 @@ public class UiTranslationController { + " may be gone."); continue; } - mActivity.runOnUiThread(() -> view.onTranslationComplete(response)); + mActivity.runOnUiThread(() -> { + if (view.getViewTranslationCallback() == null) { + if (DEBUG) { + Log.d(TAG, view + " doesn't support showing translation because of " + + "null ViewTranslationCallback."); + } + return; + } + view.onTranslationResponse(response); + view.getViewTranslationCallback().onShowTranslation(view); + }); } } } /** - * Called when there is an ui translation request comes to request view translation. + * Creates a Translator for the given source and target translation specs and start the ui + * translation when the Translator is created successfully. */ @WorkerThread private void createTranslatorAndStart(TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> views) { - // Create Translator final Translator translator = createTranslatorIfNeeded(sourceSpec, destSpec); if (translator == null) { Log.w(TAG, "Can not create Translator for sourceSpec:" + sourceSpec + " destSpec:" @@ -295,31 +319,51 @@ public class UiTranslationController { */ private void onUiTranslationStarted(Translator translator, List<AutofillId> views) { synchronized (mLock) { - // Find Views collect the translation data - final ArrayList<ViewTranslationRequest> requests = new ArrayList<>(); + // TODO(b/177960696): handle virtual views. Need to check the requested view list is + // virtual AutofillId or not + findViewsAndCollectViewTranslationRequest(translator, views); + } + } + + /** + * If the translation requested views are not virtual view, we need to traverse the tree to + * find the views and get the View's ViewTranslationRequest. + */ + private void findViewsAndCollectViewTranslationRequest(Translator translator, + List<AutofillId> views) { + // Find Views collect the translation data + final ArrayList<ViewTranslationRequest> requests = new ArrayList<>(); final ArrayList<View> foundViews = new ArrayList<>(); - findViewsTraversalByAutofillIds(views, foundViews); - for (int i = 0; i < foundViews.size(); i++) { - final View view = foundViews.get(i); - final int currentCount = i; - mActivity.runOnUiThread(() -> { - final ViewTranslationRequest request = view.onCreateTranslationRequest(); - if (request != null - && request.getKeys().size() > 0) { - requests.add(request); - } - if (currentCount == (foundViews.size() - 1)) { - Log.v(TAG, "onUiTranslationStarted: collect " + requests.size() - + " requests."); - mWorkerHandler.sendMessage(PooledLambda.obtainMessage( - UiTranslationController::sendTranslationRequest, - UiTranslationController.this, translator, requests)); - } - }); - } + findViewsTraversalByAutofillIds(views, foundViews); + final int[] supportedFormats = getSupportedFormatsLocked(); + for (int i = 0; i < foundViews.size(); i++) { + final View view = foundViews.get(i); + final int currentCount = i; + mActivity.runOnUiThread(() -> { + final ViewTranslationRequest request = + view.createTranslationRequest(supportedFormats); + // TODO(b/177960696): handle null case, the developers may want to handle the + // translation, call dispatchRequestTranslation() instead. + if (request != null + && request.getKeys().size() > 0) { + requests.add(request); + } + if (currentCount == (foundViews.size() - 1)) { + Log.v(TAG, "onUiTranslationStarted: collect " + requests.size() + + " requests."); + mWorkerHandler.sendMessage(PooledLambda.obtainMessage( + UiTranslationController::sendTranslationRequest, + UiTranslationController.this, translator, requests)); + } + }); } } + private int[] getSupportedFormatsLocked() { + // We only support text now + return new int[] {TranslationSpec.DATA_FORMAT_TEXT}; + } + private void findViewsTraversalByAutofillIds(List<AutofillId> sourceViewIds, ArrayList<View> foundViews) { final ArrayList<ViewRootImpl> roots = @@ -356,20 +400,21 @@ public class UiTranslationController { } } - private void runForEachView(Consumer<View> action) { + private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) { synchronized (mLock) { final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews); mActivity.runOnUiThread(() -> { final int viewCounts = views.size(); for (int i = 0; i < viewCounts; i++) { final View view = views.valueAt(i).get(); - if (view == null) { + if (view == null || view.getViewTranslationCallback() == null) { if (DEBUG) { - Log.d(TAG, "View was gone for autofillid = " + views.keyAt(i)); + Log.d(TAG, "View was gone or ViewTranslationCallback for autofillid " + + "= " + views.keyAt(i)); } continue; } - action.accept(view); + action.accept(view, view.getViewTranslationCallback()); } }); } diff --git a/core/java/android/view/translation/ViewTranslationCallback.java b/core/java/android/view/translation/ViewTranslationCallback.java new file mode 100644 index 000000000000..c8959847d64b --- /dev/null +++ b/core/java/android/view/translation/ViewTranslationCallback.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.translation; + +import android.annotation.NonNull; +import android.annotation.UiThread; +import android.view.View; + +/** + * Callback for handling the translated information show or hide in the {@link View}. See {@link + * View#onTranslationResponse} for how to get the translated information. + */ +@UiThread +public interface ViewTranslationCallback { + /** + * Called when the translated text is ready to show or if the user has requested to reshow the + * translated content after hiding it. This may be called before the translation results are + * returned through the {@link View#onTranslationResponse}. + * + * @return {@code true} if the View handles showing the translation. + */ + boolean onShowTranslation(@NonNull View view); + /** + * Called when the user wants to show the original text instead of the translated text. This + * may be called before the translation results are returned through the + * {@link View#onTranslationResponse}. + * + * @return {@code true} if the View handles hiding the translation. + */ + boolean onHideTranslation(@NonNull View view); + /** + * Called when the user finish the Ui translation and no longer to show the translated text. + * + * @return {@code true} if the View handles clearing the translation. + */ + boolean onClearTranslation(@NonNull View view); +} diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 940a3c9cccdf..6733c0d3a8e1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -195,7 +195,9 @@ import android.view.textclassifier.TextLinks; import android.view.textservice.SpellCheckerSubtype; import android.view.textservice.TextServicesManager; import android.view.translation.TranslationRequestValue; +import android.view.translation.TranslationSpec; import android.view.translation.UiTranslationController; +import android.view.translation.ViewTranslationCallback; import android.view.translation.ViewTranslationRequest; import android.view.translation.ViewTranslationResponse; import android.widget.RemoteViews.RemoteView; @@ -203,6 +205,7 @@ import android.widget.RemoteViews.RemoteView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastMath; import com.android.internal.util.Preconditions; import com.android.internal.widget.EditableInputConnection; @@ -737,7 +740,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private MovementMethod mMovement; private TransformationMethod mTransformation; - private TranslationTransformationMethod mTranslationTransformation; + private TextViewTranslationCallback mDefaultTranslationCallback; @UnsupportedAppUsage private boolean mAllowTransformationLengthChange; @UnsupportedAppUsage @@ -13857,136 +13860,104 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Provides a {@link ViewTranslationRequest} that represents the content to be translated via - * translation service. + * Returns a {@link ViewTranslationRequest} which represents the content to be translated. * - * <p>NOTE: When overriding the method, it should not translate the password. We also suggest - * that not translating the text is selectable or editable. We use the transformation method to - * implement showing the translated text. The TextView does not support the transformation - * method text length change. If the text is selectable or editable, it will crash while - * selecting the text. To support it, it needs broader changes to text APIs, we only allow to - * translate non selectable and editable text now. + * <p>NOTE: When overriding the method, it should not translate the password. If the subclass + * uses {@link TransformationMethod} to display the translated result, it's also not recommend + * to translate text is selectable or editable. * - * @hide + * @param supportedFormats the supported translation format. The value could be {@link + * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}. + * @return the {@link ViewTranslationRequest} which contains the information to be translated. */ @Nullable @Override - public ViewTranslationRequest onCreateTranslationRequest() { - if (mText == null || mText.length() == 0) { + public ViewTranslationRequest createTranslationRequest(@NonNull int[] supportedFormats) { + if (supportedFormats == null || supportedFormats.length == 0) { // TODO(b/182433547): remove before S release if (UiTranslationController.DEBUG) { - Log.w(LOG_TAG, "Cannot create translation request for the empty text."); + Log.w(LOG_TAG, "Do not provide the support translation formats."); } return null; } - // Not translate password, editable text and not important for translation - // TODO(b/177214256): support selectable text translation. It needs to broader changes to - // text selection apis, not support in S. - boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); - if (isTextEditable() || isPassword || isTextSelectable()) { - // TODO(b/182433547): remove before S release - if (UiTranslationController.DEBUG) { - Log.w(LOG_TAG, "Cannot create translation request. editable = " + isTextEditable() - + ", isPassword = " + isPassword + ", selectable = " + isTextSelectable()); + ViewTranslationRequest.Builder requestBuilder = + new ViewTranslationRequest.Builder(getAutofillId()); + // Support Text translation + if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) { + if (mText == null || mText.length() == 0) { + // TODO(b/182433547): remove before S release + if (UiTranslationController.DEBUG) { + Log.w(LOG_TAG, "Cannot create translation request for the empty text."); + } + return null; } - return null; - } - // TODO(b/176488462): apply the view's important for translation property - // TODO(b/174283799): remove the spans from the mText and save the spans information - // TODO: use fixed ids for request texts. - ViewTranslationRequest request = - new ViewTranslationRequest.Builder(getAutofillId()) - .setValue(ViewTranslationRequest.ID_TEXT, - TranslationRequestValue.forText(mText)) - .build(); - return request; - } - - /** - * Provides the implementation that pauses the ongoing Ui translation, it will show the original - * text instead of the translated text and restore the original transformation method. - * - * <p>NOTE: If this method is overridden, other translation related methods such as - * {@link onRestoreUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete} - * should also be overridden. - * - * @hide - */ - @Override - public void onPauseUiTranslation() { - // Restore to original text content. - if (mTranslationTransformation != null) { - setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod()); - } else { - // TODO(b/182433547): remove before S release - Log.w(LOG_TAG, "onPauseUiTranslation(): no translated text."); + boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); + // TODO(b/177214256): support selectable text translation. + // We use the TransformationMethod to implement showing the translated text. The + // TextView does not support the text length change for TransformationMethod. If the + // text is selectable or editable, it will crash while selecting the text. To support + // it, it needs broader changes to text APIs, we only allow to translate non selectable + // and editable text in S. + if (isTextEditable() || isPassword || isTextSelectable()) { + // TODO(b/182433547): remove before S release + if (UiTranslationController.DEBUG) { + Log.w(LOG_TAG, "Cannot create translation request. editable = " + + isTextEditable() + ", isPassword = " + isPassword + ", selectable = " + + isTextSelectable()); + } + return null; + } + // TODO(b/176488462): apply the view's important for translation + requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, + TranslationRequestValue.forText(mText)); } + return requestBuilder.build(); } /** - * Provides the implementation that restoes the paused Ui translation, it will show the - * translated text again if the text had been translated. This method will replace the current - * tansformation method with {@link TranslationTransformationMethod}. - * - * <p>NOTE: If this method is overridden, other translation related methods such as - * {@link onPauseUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete} - * should also be overridden. + * Returns a {@link ViewTranslationCallback} that is used to display the translated information. + * The default implementation will use a {@link TransformationMethod} that allow to replace the + * current {@link TransformationMethod} to transform the original text to the translated text + * display. * - * @hide + * @return a {@link ViewTranslationCallback} that is used to control how to display the + * translated information or {@code null} if this View doesn't support translation. */ + @Nullable @Override - public void onRestoreUiTranslation() { - if (mTranslationTransformation != null) { - setTransformationMethod(mTranslationTransformation); - } else { - // TODO(b/182433547): remove before S release - Log.w(LOG_TAG, "onRestoreUiTranslation(): no translated text."); - } + public ViewTranslationCallback getViewTranslationCallback() { + return getDefaultViewTranslationCallback(); } - /** - * Provides the implementation that finishes the current Ui translation and it's no longer to - * show the translated text. This method restores the original transformation method and resets - * the saved {@link TranslationTransformationMethod}. - * - * <p>NOTE: If this method is overridden, other translation related methods such as - * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onTranslationComplete} - * should also be overridden. - * - * @hide - */ - @Override - public void onFinishUiTranslation() { - // Restore to original text content and clear TranslationTransformation - if (mTranslationTransformation != null) { - setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod()); - mTranslationTransformation = null; - } else { - // TODO(b/182433547): remove before S release - Log.w(LOG_TAG, "onFinishUiTranslation(): no translated text."); + private ViewTranslationCallback getDefaultViewTranslationCallback() { + if (mDefaultTranslationCallback == null) { + mDefaultTranslationCallback = new TextViewTranslationCallback(); } + return mDefaultTranslationCallback; } /** - * Default {@link TextView} implementation after the translation request is done by the - * translation service, it's ok to show the translated text. This method will save the original - * transformation method and replace the current transformation method with - * {@link TranslationTransformationMethod}. * - * <p>NOTE: If this method is overridden, other translation related methods such as - * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onFinishUiTranslation} - * should also be overridden. + * Called when the content from {@link #createTranslationRequest} had been translated by the + * TranslationService. The default implementation will replace the current + * {@link TransformationMethod} to transform the original text to the translated text display. * - * @hide + * @param response a {@link ViewTranslationResponse} that contains the translated information + * which can be shown in the view. */ @Override - public void onTranslationComplete(@NonNull ViewTranslationResponse response) { - // Show the translated text. - TransformationMethod originalTranslationMethod = mTranslationTransformation != null - ? mTranslationTransformation.getOriginalTransformationMethod() : mTransformation; - mTranslationTransformation = + public void onTranslationResponse(@NonNull ViewTranslationResponse response) { + // TODO(b/183467275): Use the overridden ViewTranslationCallback instead of our default + // implementation if the view has overridden getViewTranslationCallback. + TextViewTranslationCallback callback = + (TextViewTranslationCallback) getDefaultViewTranslationCallback(); + TranslationTransformationMethod oldTranslationMethod = + callback.getTranslationTransformation(); + TransformationMethod originalTranslationMethod = oldTranslationMethod != null + ? oldTranslationMethod.getOriginalTransformationMethod() : mTransformation; + TranslationTransformationMethod newTranslationMethod = new TranslationTransformationMethod(response, originalTranslationMethod); // TODO(b/178353965): well-handle setTransformationMethod. - setTransformationMethod(mTranslationTransformation); + callback.setTranslationTransformation(newTranslationMethod); } } diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java new file mode 100644 index 000000000000..296d93c88554 --- /dev/null +++ b/core/java/android/widget/TextViewTranslationCallback.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.annotation.NonNull; +import android.text.method.TranslationTransformationMethod; +import android.util.Log; +import android.view.View; +import android.view.translation.UiTranslationManager; +import android.view.translation.ViewTranslationCallback; +import android.view.translation.ViewTranslationResponse; + +/** + * Default implementation for {@link ViewTranslationCallback} for {@link TextView} components. + * This class handles how to display the translated information for {@link TextView}. + * + * @hide + */ +public class TextViewTranslationCallback implements ViewTranslationCallback { + + private static final String TAG = "TextViewTranslationCallback"; + + private static final boolean DEBUG = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG); + + private TranslationTransformationMethod mTranslationTransformation; + + /** + * Invoked by the platform when receiving the successful {@link ViewTranslationResponse} for the + * view that provides the translatable information by {@link View#createTranslationRequest} and + * sent by the platform. + */ + void setTranslationTransformation(TranslationTransformationMethod method) { + if (method == null) { + if (DEBUG) { + Log.w(TAG, "setTranslationTransformation: should not set null " + + "TranslationTransformationMethod"); + } + return; + } + mTranslationTransformation = method; + } + + TranslationTransformationMethod getTranslationTransformation() { + return mTranslationTransformation; + } + + private void clearTranslationTransformation() { + if (DEBUG) { + Log.v(TAG, "clearTranslationTransformation: " + mTranslationTransformation); + } + mTranslationTransformation = null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onShowTranslation(@NonNull View view) { + if (mTranslationTransformation != null) { + ((TextView) view).setTransformationMethod(mTranslationTransformation); + } else { + if (DEBUG) { + // TODO(b/182433547): remove before S release + Log.w(TAG, "onShowTranslation(): no translated text."); + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onHideTranslation(@NonNull View view) { + // Restore to original text content. + if (mTranslationTransformation != null) { + ((TextView) view).setTransformationMethod( + mTranslationTransformation.getOriginalTransformationMethod()); + } else { + if (DEBUG) { + // TODO(b/182433547): remove before S release + Log.w(TAG, "onHideTranslation(): no translated text."); + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onClearTranslation(@NonNull View view) { + // Restore to original text content and clear TranslationTransformation + if (mTranslationTransformation != null) { + ((TextView) view).setTransformationMethod( + mTranslationTransformation.getOriginalTransformationMethod()); + clearTranslationTransformation(); + } else { + if (DEBUG) { + // TODO(b/182433547): remove before S release + Log.w(TAG, "onClearTranslation(): no translated text."); + } + } + return true; + } +} diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java index bff2252e3b6d..375f4cf63c3b 100644 --- a/core/java/android/window/WindowContext.java +++ b/core/java/android/window/WindowContext.java @@ -27,10 +27,7 @@ import android.content.ContextWrapper; 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 @@ import java.lang.ref.Reference; @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 @@ public class WindowContext extends ContextWrapper { 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 @@ public class WindowContext extends ContextWrapper { * 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 @@ public class WindowContext extends ContextWrapper { /** 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 000000000000..61434145290f --- /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 dd837fc2194c..3b6a877907f2 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -194,12 +194,12 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd 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 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd 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 2a022e6e6736..e2732867d195 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -229,15 +229,14 @@ interface IVoiceInteractionManagerService { /** * 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 2464fc737a1f..622f1668d052 100644 --- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java @@ -205,7 +205,7 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA 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 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA 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 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA 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 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA 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 762297d15e6d..86f29a8f07ef 100644 --- a/core/java/com/android/internal/app/SuspendedAppActivity.java +++ b/core/java/com/android/internal/app/SuspendedAppActivity.java @@ -106,13 +106,17 @@ public class SuspendedAppActivity extends AlertActivity } 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 @@ public class SuspendedAppActivity extends AlertActivity 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 fae58622d91e..6776c27fc16e 100644 --- a/core/java/com/android/internal/display/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java @@ -20,6 +20,7 @@ package com.android.internal.display; 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.PowerManager; 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 @@ public class BrightnessSynchronizer { // 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 @@ public class BrightnessSynchronizer { * 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 @@ public class BrightnessSynchronizer { } 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 @@ public class BrightnessSynchronizer { /** * 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 @@ public class BrightnessSynchronizer { } } - 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 @@ public class BrightnessSynchronizer { 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 @@ public class BrightnessSynchronizer { 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 ec0a8d8ede9d..b0920b0100da 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -170,7 +170,7 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { @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 @@ public class BatteryStatsImpl extends BatteryStats { } @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 @@ public class BatteryStatsImpl extends BatteryStats { } @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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { * 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { mTmpRailStats.resetCellularTotalEnergyUsed(); } } - long radioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( + long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000); mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs); @@ -11958,12 +12012,21 @@ public class BatteryStatsImpl extends BatteryStats { // 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 @@ public class BatteryStatsImpl extends BatteryStats { } } - 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 @@ public class BatteryStatsImpl extends BatteryStats { // '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 @@ public class BatteryStatsImpl extends BatteryStats { } /** + * 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { // 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 4f99c94e0dc5..f8ae0c1858f8 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.hardware.SensorManager; 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 @@ public class BatteryUsageStatsProvider { } /** - * Returns snapshots of battery attribution data, one per supplied query. + * Returns true if the last update was too long ago for the tolerances specified + * by the supplied queries. */ - 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++) { + 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); - for (int userId : query.getUserIds()) { - UserHandle userHandle = UserHandle.of(userId); - if (!users.contains(userHandle)) { - users.add(userHandle); - } - } + allowableStatsAge = Math.min(allowableStatsAge, query.getMaxStatsAge()); } - batteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, users); + return mStats.mClocks.elapsedRealtime() - lastUpdateTimeStampMs > allowableStatsAge; + } + + /** + * Returns snapshots of battery attribution data, one per supplied query. + */ + public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { 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 df25cdaee17f..97c4fd8b7b7a 100644 --- a/core/java/com/android/internal/os/GnssPowerCalculator.java +++ b/core/java/com/android/internal/os/GnssPowerCalculator.java @@ -61,7 +61,17 @@ public class GnssPowerCalculator extends PowerCalculator { 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 @@ public class GnssPowerCalculator extends PowerCalculator { 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 22001d4ffbb7..498e1f2931a0 100644 --- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java +++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java @@ -96,10 +96,12 @@ public class MobileRadioPowerCalculator extends PowerCalculator { 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 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } 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 @@ public class MobileRadioPowerCalculator extends PowerCalculator { 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 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } 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 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } 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 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } 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 @@ public class MobileRadioPowerCalculator extends PowerCalculator { 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 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } /** + * 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 39bde742e828..611fe29f4fcf 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.FLAG_TRANSLUCENT_STATUS; 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 845b3e501c08..00a0627d068c 100644 --- a/core/java/com/android/internal/power/MeasuredEnergyStats.java +++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java @@ -54,7 +54,9 @@ public class MeasuredEnergyStats { 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 @@ public class MeasuredEnergyStats { POWER_BUCKET_CPU, POWER_BUCKET_WIFI, POWER_BUCKET_BLUETOOTH, + POWER_BUCKET_GNSS, + POWER_BUCKET_MOBILE_RADIO, }) @Retention(RetentionPolicy.SOURCE) public @interface StandardPowerBucket { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index f1fa5dbcbbdc..20d257e98278 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -221,7 +221,6 @@ cc_library_shared { static_libs: [ "libasync_safe", - "libconnectivityframeworkutils", "libbinderthreadstateutils", "libdmabufinfo", "libgif", @@ -238,7 +237,6 @@ cc_library_shared { "android.hardware.camera.device@3.2", "media_permission-aidl-cpp", "libandroidicu", - "libandroid_net", "libbpf_android", "libnetdbpf", "libnetdutils", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 94ac183517a2..0c3f265b84cc 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -156,7 +156,6 @@ extern int register_android_os_SharedMemory(JNIEnv* env); extern int register_android_service_DataLoaderService(JNIEnv* env); extern int register_android_os_incremental_IncrementalManager(JNIEnv* env); extern int register_android_net_LocalSocketImpl(JNIEnv* env); -extern int register_android_net_NetworkUtils(JNIEnv* env); extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_Hyphenator(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); @@ -1548,7 +1547,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_Trace), REG_JNI(register_android_os_UEventObserver), REG_JNI(register_android_net_LocalSocketImpl), - REG_JNI(register_android_net_NetworkUtils), REG_JNI(register_android_os_MemoryFile), REG_JNI(register_android_os_SharedMemory), REG_JNI(register_android_os_incremental_IncrementalManager), diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index f54ffc50095b..419dc6e53744 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1658,10 +1658,12 @@ public: 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 dca6002d23f8..530cb44d3aae 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -123,6 +123,8 @@ message SecureSettingsProto { 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 @@ message SecureSettingsProto { 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 0d23946ebbb8..acb7429df5c3 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -58,6 +58,9 @@ message PowerManagerServiceDumpProto { 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 @@ message PowerManagerServiceDumpProto { // 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 @@ message PowerManagerServiceDumpProto { // 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 8a471350bb36..b3b66c48fe6c 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 38e8f83ff767..986bb820eb2c 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 8df32217cc99..b6c22bb68086 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 3259cafb4f7e..bc49818638bc 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 0b3c4052e204..dd647508338f 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. [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 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 personal apps don't support the specific content. [CHAR LIMIT=NONE] --> + <string name="resolver_no_personal_apps_available">No personal 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> + <!-- 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 @@ ul.</string> <!-- 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 a704936c5ed4..b924ecd3f301 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 7ef1d5e426cc..56c685afa026 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 class AppSearchSessionUnitTest { 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 @@ public class AppSearchSessionUnitTest { 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 c61a4b538a44..0b945895ad7f 100644 --- a/core/tests/coretests/src/android/content/OWNERS +++ b/core/tests/coretests/src/android/content/OWNERS @@ -2,3 +2,5 @@ per-file AssetTest.java = file:/core/java/android/content/res/OWNERS 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 ffe93bc99cf5..21eb44ad113a 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 @@ public class AppSearchShortcutInfoTest { 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 f3a6f9e9de17..22c71b527d48 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.Point; 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 @@ import org.mockito.MockitoAnnotations; * 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 cc229e11dcf2..dc43204c35d6 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.Point; 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.concurrent.TimeUnit; 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 41cd4c562bd8..2833ea3f9ac0 100644 --- a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java +++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java @@ -49,7 +49,6 @@ import java.util.function.Consumer; */ @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 67614bb22e4e..e6a25d00ff10 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 @@ public class ContentCaptureEventTest { @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 @@ public class ContentCaptureEventTest { @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 000000000000..e4fc19c0e58d --- /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 4c58ad3d7f8d..1633d2897fcb 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 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { 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 7dc5a8b58f91..97652a970420 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 @@ public class ResolverActivityTest { 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 @@ public class ResolverActivityTest { 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 @@ public class ResolverActivityTest { 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 @@ public class ResolverActivityTest { 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 0f591433a84e..d36f06ab683a 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 @@ public class BatteryUsageStatsProviderTest { 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 bd7f1e2c3410..eed61cb0afe7 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 @@ package com.android.internal.os; 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 @@ public class GnssPowerCalculatorTest { 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 @@ public class GnssPowerCalculatorTest { 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 @@ public class GnssPowerCalculatorTest { 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 66a8379177c2..ae59a546642d 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 static org.mockito.Mockito.when; 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 @@ public class MobileRadioPowerCalculatorTest { .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,14 +92,15 @@ public class MobileRadioPowerCalculatorTest { 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(calculator); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(), + calculator); SystemBatteryConsumer consumer = mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO); @@ -106,4 +111,57 @@ public class MobileRadioPowerCalculatorTest { 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); + + MobileRadioPowerCalculator calculator = + new MobileRadioPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + 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(2.77777); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) + .isWithin(PRECISION).of(1.53934); + } } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index cafda0966fc4..c48fd8b8b6ae 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -34,9 +34,11 @@ import android.graphics.fonts.FontFamily; 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.system.OsConstants; 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 @@ public class Typeface { 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 24d73efebe54..3616a4d66399 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -276,7 +276,11 @@ public class GradientDrawable extends Drawable { */ @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 5dd250c8239e..98b9584f7b4a 100644 --- a/graphics/java/android/graphics/drawable/RippleShader.java +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -60,17 +60,13 @@ final class RippleShader extends RuntimeShader { + " 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 @@ final class RippleShader extends RuntimeShader { + " 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 @@ final class RippleShader extends RuntimeShader { } 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 35b1c169f283..72cea0cacd12 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -139,4 +139,18 @@ public class AndroidKeyStoreMaintenance { 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 000000000000..a1a7aa85519f --- /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/media/java/android/media/metrics/PlaybackComponent.java b/keystore/java/android/security/GenerateRkpKeyException.java index 1cadf3be38ee..a2d65e4e7119 100644 --- a/media/java/android/media/metrics/PlaybackComponent.java +++ b/keystore/java/android/security/GenerateRkpKeyException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,18 @@ * limitations under the License. */ -package android.media.metrics; - -import android.annotation.NonNull; +package android.security; /** - * Interface for playback related components used by playback metrics. + * Thrown on problems in attempting to attest to a key using a remotely provisioned key. + * + * @hide */ -public interface PlaybackComponent { - - /** - * Sets the playback ID of the component. - */ - void setPlaybackId(@NonNull String playbackId); +public class GenerateRkpKeyException extends Exception { /** - * Gets playback ID. + * Constructs a new {@code GenerateRkpKeyException}. */ - @NonNull String getPlaybackId(); + public GenerateRkpKeyException() { + } } diff --git a/keystore/java/android/security/IGenerateRkpKeyService.aidl b/keystore/java/android/security/IGenerateRkpKeyService.aidl new file mode 100644 index 000000000000..5f1d6693c23a --- /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 a08f390c9fd3..b05149ef75bc 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -1204,6 +1204,7 @@ public class KeyStore { * 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 6ac3821d0f9c..75e248e06b2b 100644 --- a/keystore/java/android/security/KeyStore2.java +++ b/keystore/java/android/security/KeyStore2.java @@ -18,8 +18,7 @@ package android.security; 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 @@ public class KeyStore2 { * 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 e401add9ece7..2d8901a37c05 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.KeyPurpose; 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 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato @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 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato 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 ade63e5b832c..5d9fad5b676e 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -45,7 +45,8 @@ sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { 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 @@ ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChu mTargetSize = swapWidthHeight() ? SkISize { mDecodeSize.height(), mDecodeSize.width() } : mDecodeSize; this->rewind(); + mOptions.fZeroInitialized = zeroInit; } ImageDecoder::~ImageDecoder() = default; @@ -446,10 +448,17 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { 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 cbfffd5e9291..cef2233fc371 100644 --- a/libs/hwui/hwui/ImageDecoder.h +++ b/libs/hwui/hwui/ImageDecoder.h @@ -34,8 +34,8 @@ public: 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 ad7741b61e9f..f7b8c014be6e 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -141,7 +141,8 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream, } 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 5e2e5595ba9c..5f8d795cd654 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -1246,20 +1246,42 @@ public class Location implements Parcelable { * 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 f957a73144c8..343d04fd3214 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -568,6 +568,42 @@ public class AudioManager { 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 @@ public class AudioManager { @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 @@ public class AudioManager { /** * 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 @@ public class AudioManager { /** * 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 @@ public class AudioManager { /** * 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 @@ public class AudioManager { } /** + * 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 @@ public class AudioManager { } /** - * @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 @@ public class AudioManager { * @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 0d613992f300..3399377be999 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -32,6 +32,7 @@ import android.content.Context; 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 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, /** * 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 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, } /** - * 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 bccefdfd7b96..7a2b022b514a 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -26,6 +26,7 @@ import android.annotation.RequiresPermission; 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.ByteBuffer; 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 @@ public class AudioTrack extends PlayerBase /** * 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 @@ public class AudioTrack extends PlayerBase } /** - * 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 4f87fe6c5f8c..ee945d5a715f 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -158,6 +158,14 @@ interface IAudioService { 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 a7e2b65ebb8f..b51777c3135a 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -25,7 +25,6 @@ import android.graphics.Rect; 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.Arrays; 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 @@ import java.util.concurrent.locks.ReentrantLock; </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 @@ final public class MediaCodec implements PlaybackComponent { 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 @@ final public class MediaCodec implements PlaybackComponent { 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 06d0eb0b5423..a9e8c26ecb77 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -3434,11 +3434,14 @@ public final class MediaCodecInfo { 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 ae64c026fb51..10b99dce53b0 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -27,7 +27,7 @@ import android.compat.annotation.UnsupportedAppUsage; 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.HashMap; 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 final class MediaDrm implements AutoCloseable { 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 @@ public final class MediaDrm implements AutoCloseable { /** * 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 @@ public final class MediaDrm implements AutoCloseable { 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 8f603300dc11..283f1f1d69a2 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; 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.Arrays; 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 @@ import java.util.stream.Collectors; * <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 @@ final public class MediaExtractor { 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 @@ final public class MediaExtractor { } 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 dd08d8adf20b..f960ff2b1a7e 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -29,6 +29,7 @@ import android.app.ActivityThread; 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 @@ public class MediaRecorder implements AudioRouting, private int mChannelCount; + @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE; + /** * Default constructor. * @@ -165,6 +168,27 @@ public class MediaRecorder implements AudioRouting, } /** + * 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 8daa30389012..1f6855a4ae8e 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -194,6 +194,14 @@ public final class MediaRouter2 { /** * 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 @@ public final class MediaRouter2 { /** * 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 @@ public final class MediaRouter2 { } /** - * 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 6fefbe15abae..758a8130a62e 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -148,6 +148,14 @@ public final class MediaRouter2Manager { /** * 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 @@ public final class MediaRouter2Manager { /** * 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 2f952474b7f0..37fee8466859 100644 --- a/media/java/android/media/RouteDiscoveryPreference.java +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -18,6 +18,7 @@ package android.media; 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 @@ public final class RouteDiscoveryPreference implements Parcelable { 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 f68ef4b68de1..41f30937489b 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 final class LogSessionId { 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/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index ded2e1b4e939..f6944823feb5 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 @@ enum { 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 @@ static struct { } gByteBufferInfo; static struct { + jclass clazz; + jmethodID ctorId; jmethodID sizeId; jmethodID getId; jmethodID addId; @@ -154,6 +167,13 @@ static struct { 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 JMediaCodec::selectAudioPresentation(const int32_t presentationId, const in (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 @@ static void android_media_MediaCodec_setAudioPresentation( 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 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { 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 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { 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 @@ static const JNINativeMethod gMethods[] = { { "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 5fd6bfd6f8cd..ee456c9ba82d 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -164,6 +164,14 @@ struct JMediaCodec : public AHandler { 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 6d7badb08905..35249f615e6b 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -258,6 +258,9 @@ LIBANDROID { 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 e8cf63f64572..195fd5e60295 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -446,6 +446,44 @@ void ASurfaceTransaction_setGeometry(ASurfaceTransaction* aSurfaceTransaction, 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 3553c1f05310..74cecdd351b3 100644 --- a/packages/Connectivity/framework/Android.bp +++ b/packages/Connectivity/framework/Android.bp @@ -26,6 +26,7 @@ package { java_library { name: "framework-connectivity-protos", sdk_version: "module_current", + min_sdk_version: "30", proto: { type: "nano", }, @@ -34,7 +35,6 @@ java_library { ":framework-javastream-protos", ], apex_available: [ - "//apex_available:platform", "com.android.tethering", ], jarjar_rules: "jarjar-rules-proto.txt", @@ -81,11 +81,13 @@ filegroup { 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 +99,33 @@ java_sdk_library { "frameworks/native/aidl/binder", // For PersistableBundle.aidl ], }, + impl_only_libs: [ + // TODO (b/183097033) remove once module_current includes core_platform + "stable.core.platform.api.stubs", + "framework-tethering.stubs.module_lib", + "framework-wifi.stubs.module_lib", + "net-utils-device-common", + ], libs: [ "unsupportedappusage", ], + static_libs: [ + "framework-connectivity-protos", + ], + jarjar_rules: "jarjar-rules.txt", permitted_packages: ["android.net"], + impl_library_visibility: [ + "//packages/modules/Connectivity/Tethering/apex", + // In preparation for future move + "//packages/modules/Connectivity/apex", + "//packages/modules/Connectivity/service", + "//frameworks/base/packages/Connectivity/service", + "//frameworks/base", + "//packages/modules/Connectivity/Tethering/tests/unit", + ], + apex_available: [ + "com.android.tethering", + ], } cc_defaults { @@ -109,13 +134,15 @@ cc_defaults { "-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 +164,15 @@ cc_library_static { 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 46d0a3f5062c..0cc8c239cb94 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -20,6 +20,7 @@ package android.net { method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean); + method public static void setPrivateDnsMode(@NonNull android.content.Context, @NonNull String); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); @@ -28,6 +29,14 @@ package android.net { 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 9dcc391bd31b..593698e097cd 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -218,6 +218,8 @@ package android.net { 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 @@ package android.net { 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 @@ package android.net { 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 fd4d9db23d29..e8bb42df6acc 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 jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jobj } 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 @@ static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* 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 @@ static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint 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 @@ static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint 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 @@ static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, job 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 jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, job 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 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "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 8c9367641949..a9f8b8dda340 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -829,6 +829,94 @@ public class ConnectivityManager { }) 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 @@ public class ConnectivityManager { * 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 @@ -5338,4 +5427,23 @@ public class ConnectivityManager { if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC; return mode; } + + /** + * Set private DNS mode to settings. + * + * @param context The {@link Context} to set the private DNS mode. + * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static void setPrivateDnsMode(@NonNull Context context, + @NonNull @PrivateDnsMode String mode) { + if (!(mode == PRIVATE_DNS_MODE_OFF + || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC + || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { + throw new IllegalArgumentException("Invalid private dns mode"); + } + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE, mode); + } } diff --git a/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl index 1f66e18717d8..f9d399459ebd 100644 --- a/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl +++ b/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl @@ -46,4 +46,6 @@ oneway interface INetworkAgent { 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 c5464d32412b..cbd6193744b9 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.NetworkInfo; 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 @@ oneway interface INetworkAgentRegistry { 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 67d2d405dbed..a6de173fa33e 100644 --- a/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl +++ b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl @@ -22,15 +22,16 @@ import android.net.NetworkRequest; * 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 @@ import android.net.NetworkRequest; * 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 91c75759f85c..c9735419f7dd 100644 --- a/packages/Connectivity/framework/src/android/net/IQosCallback.aidl +++ b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl @@ -19,6 +19,7 @@ package android.net; 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 @@ oneway interface IQosCallback { 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 41fad6317a20..1f490337de93 100644 --- a/packages/Connectivity/framework/src/android/net/Network.java +++ b/packages/Connectivity/framework/src/android/net/Network.java @@ -92,6 +92,7 @@ public class Network implements Parcelable { // 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 class Network implements Parcelable { */ 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 @@ public class Network implements Parcelable { 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 @@ public class Network implements Parcelable { 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 b3d9616cead7..6b55bb771c30 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.Looper; 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 abstract class NetworkAgent { */ 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 @@ public abstract class NetworkAgent { msg.arg1 /* QoS callback id */); break; } + case CMD_NETWORK_CREATED: { + onNetworkCreated(); + break; + } + case CMD_NETWORK_DISCONNECTED: { + onNetworkDisconnected(); + break; + } } } } @@ -700,6 +725,16 @@ public abstract class NetworkAgent { 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 @@ public abstract class NetworkAgent { } /** + * 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 @@ public abstract class NetworkAgent { /** - * 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 d5b5c9b603ec..d859022552c1 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkProvider.java +++ b/packages/Connectivity/framework/src/android/net/NetworkProvider.java @@ -170,10 +170,11 @@ public class NetworkProvider { /** @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 @@ public class NetworkProvider { } @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 @@ public class NetworkProvider { } /** - * 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 f52485933c49..2679b6218c19 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java +++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java @@ -42,6 +42,9 @@ import java.util.TreeSet; * {@hide} */ public class NetworkUtils { + static { + System.loadLibrary("framework-connectivity-jni"); + } private static final String TAG = "NetworkUtils"; @@ -92,12 +95,16 @@ public class NetworkUtils { @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 +115,22 @@ public class NetworkUtils { 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 +139,11 @@ public class NetworkUtils { * {@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 bdb4ad68cd7b..de0fc243b43b 100644 --- a/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java +++ b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java @@ -19,6 +19,7 @@ package android.net; 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 @@ class QosCallbackConnection extends android.net.IQosCallback.Stub { } /** + * 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 4f3bb77c5877..93f2ff2bf724 100644 --- a/packages/Connectivity/framework/src/android/net/QosSession.java +++ b/packages/Connectivity/framework/src/android/net/QosSession.java @@ -36,6 +36,11 @@ public final class QosSession implements Parcelable { */ 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 @@ public final class QosSession implements Parcelable { */ @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 37dd9ff84b59..b44128b9ae2e 100644 --- a/packages/Connectivity/service/Android.bp +++ b/packages/Connectivity/service/Android.bp @@ -25,7 +25,7 @@ package { 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 @@ cc_library_shared { "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 @@ cc_library_shared { 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 @@ java_library { "service-connectivity-protos", ], apex_available: [ - "//apex_available:platform", "com.android.tethering", ], } @@ -90,6 +90,7 @@ java_library { java_library { name: "service-connectivity-protos", sdk_version: "system_current", + min_sdk_version: "30", proto: { type: "nano", }, @@ -98,7 +99,6 @@ java_library { ], libs: ["libprotobuf-java-nano"], apex_available: [ - "//apex_available:platform", "com.android.tethering", ], } @@ -106,13 +106,14 @@ java_library { 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 fa4501ac7f29..d78373811194 100644 --- a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp +++ b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp @@ -22,6 +22,7 @@ package { 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 36a6fde36191..e7a40e5ea66b 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 @@ namespace android { //------------------------------------------------------------------------------ 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 d6c66b5663af..5e69a4ee395c 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -17,8 +17,8 @@ android_library { // 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 @@ android_library { resource_dirs: ["res"], - srcs: ["src/**/*.java", "src/**/*.kt"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], min_sdk_version: "21", @@ -68,6 +71,7 @@ java_defaults { "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 8c208e35a371..0390e86a0cf6 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 231babea97c2..dd9fc2c7c142 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp @@ -23,5 +23,6 @@ android_library { 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 000000000000..24d53ab84653 --- /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 e376930645ce..c799b9962828 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 c799b9962828..000000000000 --- 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 e1a64d446ea2..1157a346d6ec 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 ad94cd0318a7..957bac742703 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.view.ViewGroup; 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 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { 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 000000000000..c4c74ffc719b --- /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 5496a0178094..7a550aed94ac 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 8d9a562fede7..ae9261c48419 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 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec 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 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec 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 000000000000..55677908b684 --- /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 4a1b089970c7..000000000000 --- 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 000000000000..87977bdcd6b3 --- /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 000000000000..3a289a7ceb00 --- /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 000000000000..acf06c4ffc29 --- /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 000000000000..17b68050a3cc --- /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 000000000000..a3bb1da71c9a --- /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 94856552d6e2..009ae6bc46a9 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 a6623b01fede..f24e0083db27 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 6ca8f5775f42..17596ac2903a 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 000000000000..36ca684a14a3 --- /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 000000000000..c12f6f748cba --- /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 000000000000..11660a5fe444 --- /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 index 4f402565787c..0630ca85b621 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 000000000000..f99fda05c3c9 --- /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 9130662021d5..17f257d3758a 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 @@ public class TwoTargetPreference extends Preference { 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 @@ public class TwoTargetPreference extends Preference { 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 7186ec5be805..927e9db89be8 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 898796828131..a5da8b6bd15e 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.Environment; 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 @@ public class AppUtils { } 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 dfde3c7a2512..c61f8a9c3b53 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 @@ public class ConnectivitySubsystemsRecoveryManager { * @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 @@ public class ConnectivitySubsystemsRecoveryManager { } 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 1474f184775d..f62ca3294665 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 @@ public abstract class AbstractEnableAdbPreferenceController extends } } + @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 db9b83e04a47..53920f04cc7a 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 @@ public class SecureSettingsValidators { 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 7288371899ce..4119dc9f2206 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 @@ class SettingsProtoDumpUtil { 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 @@ class SettingsProtoDumpUtil { 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 beee03b52579..95c2d2efcd89 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 @@ package com.android.systemui.plugins.qs; 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 @@ public interface DetailAdapter { } 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 000000000000..e49fc15fbd7f --- /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 000000000000..bce193a564a0 --- /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 fe76b639f15a..000000000000 --- 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 b0e0ed5079e6..a845e73eb3a4 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 4f6cb0140d09..0dec981bd4ae 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 f2341b55488f..9990244ba6c5 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 914ee3c089d4..f0ab187b1a05 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 fda59b50104a..1d5bca92ddb0 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 730f24ff9dd2..d82151d30b9e 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 95ff59bdf1df..8c905739b991 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 be49e1f8c71e..886f98e55d67 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 af6df32a02b0..6e61148f4ac8 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 2393b749b64e..d5c6398dd719 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 94bf86ab07da..3ca885a2c6b7 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 4a661dcecce8..ecc1a5c831b8 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 d2bff180ef8a..b2bf6da65163 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 3f0e3eb84424..ab219f36bab3 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -131,7 +131,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie 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 bb1d9729ccc3..cfef6cb399ed 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -160,11 +160,18 @@ public class CarrierTextManager { * * @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 @@ public class CarrierTextManager { 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 class CarrierTextManager { 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 72dd72eb1676..02a8958ef657 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -487,5 +487,9 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> 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 561ea4075291..568bea0e2d24 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java @@ -70,7 +70,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp 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 4887767b9922..708b2d55b75a 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.MathUtils; import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; -import android.view.OrientationEventListener; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; @@ -107,7 +106,6 @@ public class KeyguardSecurityContainer extends FrameLayout { 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 @@ public class KeyguardSecurityContainer extends FrameLayout { 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 @@ public class KeyguardSecurityContainer extends FrameLayout { updateBiometricRetry(securityMode, faceAuthEnabled); updateLayoutForSecurityMode(securityMode); - mOrientationEventListener.enable(); } void updateLayoutForSecurityMode(SecurityMode securityMode) { @@ -385,7 +375,6 @@ public class KeyguardSecurityContainer extends FrameLayout { mAlertDialog = null; } mSecurityViewFlipper.setWindowInsetsAnimationCallback(null); - mOrientationEventListener.disable(); } @Override @@ -663,6 +652,15 @@ public class KeyguardSecurityContainer extends FrameLayout { 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 ccba1d59c8d0..760eaecae247 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -29,6 +29,7 @@ import static com.android.systemui.DejankUtils.whitelistIpcs; 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 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard 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 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create( mKeyguardSecurityCallback); mConfigurationController = configurationController; + mLastOrientation = getResources().getConfiguration().orientation; } @Override @@ -498,6 +502,19 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard 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 378907c400d7..e2748437c0c6 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 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { 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 2569f7c107cb..f7beaf123b7c 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.os.Message; import android.util.Log; import android.util.Slog; import android.view.Gravity; -import android.view.View; import android.view.WindowManager; /** @@ -98,8 +97,8 @@ public class WirelessChargingAnimation { 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 @@ public class WirelessChargingAnimation { 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 e8407f01516b..ce0b51489490 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.AnimatorSet; 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.FrameLayout; 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 @@ import java.text.NumberFormat; * @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 @@ public class WirelessChargingLayout extends FrameLayout { 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 @@ public class WirelessChargingLayout extends FrameLayout { 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 7e7cdce058bd..6812f77eb159 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.Queue; 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 @@ public class BrightLineFalsingManager implements FalsingManager { } }; - 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 @@ public class BrightLineFalsingManager implements FalsingManager { } }); + 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 @@ public class BrightLineFalsingManager implements FalsingManager { }; private Collection<FalsingClassifier.Result> mPriorResults; + private @Classifier.InteractionType int mPriorInteractionType = Classifier.GENERIC; @Inject public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider, @@ -154,7 +177,7 @@ public class BrightLineFalsingManager implements FalsingManager { mTestHarness = testHarness; mDataProvider.addSessionListener(mSessionListener); - mDataProvider.addGestureCompleteListener(mGestureCompleteListener); + mDataProvider.addGestureCompleteListener(mGestureFinalizedListener); mHistoryTracker.addBeliefListener(mBeliefListener); } @@ -165,50 +188,33 @@ public class BrightLineFalsingManager implements FalsingManager { @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); - - 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(); - } - } + logDebug("False Gesture: " + booleanResult); - return result; + return booleanResult; } @Override @@ -354,7 +360,7 @@ public class BrightLineFalsingManager implements FalsingManager { @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 f665a7439ebe..1aaa139eb7ce 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 @@ public class FalsingDataProvider { 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 @@ public class FalsingDataProvider { 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 @@ public class FalsingDataProvider { 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 @@ public class FalsingDataProvider { 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 @@ public class FalsingDataProvider { } /** 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 52c9f164a16e..b7f6e2bb965a 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 @@ public class DozeSensors { 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 dd3d02a86672..31e49390b9b8 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 @@ public class HomeSoundEffectController extends SystemUI { 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 9d43e0c13320..c8dfde1d717b 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 @@ public class NavigationBar implements View.OnAttachStateChangeListener, 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 @@ public class NavigationBar implements View.OnAttachStateChangeListener, // Send the assistant availability upon connection if (isConnected) { - sendAssistantAvailability(mAssistantAvailable); + updateAssistantEntrypoints(); } } @@ -404,12 +406,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, 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 @@ public class NavigationBar implements View.OnAttachStateChangeListener, 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 @@ public class NavigationBar implements View.OnAttachStateChangeListener, || 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 @@ public class NavigationBar implements View.OnAttachStateChangeListener, 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 @@ public class NavigationBar implements View.OnAttachStateChangeListener, 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 @@ public class NavigationBar implements View.OnAttachStateChangeListener, | (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 5bc128062adc..440c5efab008 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 @@ public class PeopleSpaceUtils { /** 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 @@ public class PeopleSpaceUtils { 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 @@ public class PeopleSpaceUtils { 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 @@ public class PeopleSpaceUtils { 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 bc196bf68b66..8d1b712e0807 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 @@ class PeopleTileViewHelper { 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 776e8a246bf6..5be2d4a05f54 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.ComponentName; 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.GuardedBy; 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 @@ public class PeopleSpaceWidgetManager { 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 @@ public class PeopleSpaceWidgetManager { mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); mPeopleManager = mContext.getSystemService(PeopleManager.class); mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); + mPackageManager = mContext.getPackageManager(); } /** @@ -108,12 +128,15 @@ public class PeopleSpaceWidgetManager { 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 class PeopleSpaceWidgetManager { 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 @@ public class PeopleSpaceWidgetManager { 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 @@ public class PeopleSpaceWidgetManager { 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 @@ public class PeopleSpaceWidgetManager { 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 @@ public class PeopleSpaceWidgetManager { // 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 @@ public class PeopleSpaceWidgetManager { } 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 9967936ac1bd..d54d3f21f0b6 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.annotation.Nullable; 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 @@ public class QSDetail extends LinearLayout { } 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 @@ public class QSDetail extends LinearLayout { 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 5e13fd345b81..2c5dbcd51f21 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 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen 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 3e3451e64b49..0a9c12fa4a13 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 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D 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 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D && 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 32b41ec0ab66..d72f8e9ca1c0 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 @@ public class TileQueryHelper { 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 fea521f15b84..bdb392608455 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 class BrightnessController implements ToggleSlider.Listener { 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 @@ public class BrightnessController implements ToggleSlider.Listener { 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 @@ public class BrightnessController implements ToggleSlider.Listener { 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 6f80317e8f56..05af08e0287a 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.Paint 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 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context 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 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context 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 5547c1e78cc4..d400205af50b 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 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) { 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 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) { 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 b567ad4ccfc2..2900462c6924 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 @@ package com.android.systemui.statusbar.charging 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.BatteryController 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 @@ class WiredChargingRippleController @Inject constructor( 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 @@ class WiredChargingRippleController @Inject constructor( 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 @@ class WiredChargingRippleController @Inject constructor( 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 c83b60d07aa6..d581c4b6f9b7 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.AutoHideUiElement; 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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); - } - - @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); - } + 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 461587760f56..fd37d89a24ec 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 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie } @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 0da441d1ad35..83558cbf089f 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 @@ public class UserSwitcherController implements Dumpable { } @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 3bc26322e6cd..95216c559420 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 java.util.List; 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 4e1accacfb7f..3111930b2e60 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 @@ public class TelephonyListenerManager { 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 5dc7006406ee..044d8285b079 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 @@ public class VolumeDialogImpl implements VolumeDialog, 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 @@ public class VolumeDialogImpl implements VolumeDialog, 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 @@ public class VolumeDialogImpl implements VolumeDialog, 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 @@ public class VolumeDialogImpl implements VolumeDialog, 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 ddfa63a33149..709ccd4c2384 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.bubbles.Bubbles; 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 @@ import dagger.Provides; * 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 000000000000..61f50b5aae30 --- /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 084c0b496c16..99b3f6f19f81 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.ArgumentMatchers.eq; 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 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { private ConfigurationController mConfigurationController; @Mock private EmergencyButtonController mEmergencyButtonController; + @Mock + private Resources mResources; + private Configuration mConfiguration; private KeyguardSecurityContainerController mKeyguardSecurityContainerController; private KeyguardPasswordViewController mKeyguardPasswordViewController; @@ -103,6 +108,11 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @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 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { 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 67c1d075d03e..1f165bba2bf7 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 androidx.test.filters.SmallTest; 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 @@ public class BrightLineClassifierTest extends SysuiTestCase { 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 @@ public class BrightLineClassifierTest extends SysuiTestCase { 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 @@ public class BrightLineClassifierTest extends SysuiTestCase { @Test public void testHistory() { - mGestureCompleteListener.onGestureComplete(1000); + mGestureFinalizedListener.onGestureFinalized(1000); verify(mHistoryTracker).addResults(anyCollection(), eq(1000L)); } @@ -220,7 +220,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { 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 class BrightLineClassifierTest extends SysuiTestCase { 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 class BrightLineClassifierTest extends SysuiTestCase { 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 3a77f7eec7f9..33a30e0bcc27 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.doReturn; 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.ArgumentCaptor; 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 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { 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 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { 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 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { } /** + * 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 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { 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 c2e0d6b49a76..8db0f33ba7e1 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 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { 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 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { 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 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { 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 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 2 messages, show count. + // 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 7090e781a316..d91625eb7fab 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.Context; 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.Captor; 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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private LauncherApps mLauncherApps; @Mock private NotificationEntryManager mNotificationEntryManager; + @Mock + private PackageManager mPackageManager; @Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor; @@ -167,7 +187,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { * 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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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,9 +1057,14 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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) @@ -797,6 +1079,26 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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") + .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; + } + + private StatusBarNotification createNotification(String shortcutId, boolean isMessagingStyle, boolean isMissedCall) { Notification notification = createMessagingStyleNotification( @@ -804,6 +1106,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { return new SbnBuilder() .setNotification(notification) .setPkg(TEST_PACKAGE_A) + .setUid(0) + .setUser(new UserHandle(0)) .build(); } @@ -824,11 +1128,16 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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 3701b9127116..9ce72414d751 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 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { 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 463b33602c15..ac15903e9806 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 androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,7 +35,7 @@ import org.junit.runner.RunWith; 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 443a9bc33b90..f971a0916837 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 f6bb157312e0..311e87356d78 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -153,7 +153,7 @@ stubs_defaults { " --hide DeprecationMismatch" + " --hide HiddenTypedefConstant", visibility: ["//visibility:private"], - filter_packages: ["com.android."] + filter_packages: ["com.android."], } droidstubs { @@ -168,7 +168,7 @@ droidstubs { 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 @@ droidstubs { }, 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 @@ droidstubs { }, 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 d2c1bc10abb0..1fdd9083b7cc 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 @@ public class WindowMagnificationPromptController { 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 @@ public class WindowMagnificationPromptController { .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 @@ public class WindowMagnificationPromptController { 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 b160d78a0b64..483f67a91c19 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 @@ public class CompanionDeviceManagerService extends SystemService implements Bind })); } + @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 4c6e06f70277..8bb9ce940cc3 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.DETECTI 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.REDACT_FOR_NETWORK_SETTINGS; 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 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsManager.removeNetwork(nai.network); } mNetIdManager.releaseNetId(nai.network.getNetId()); + nai.onNetworkDisconnected(); } private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) { @@ -6467,6 +6468,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + 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 @@ public class ConnectivityService extends IConnectivityManager.Stub */ 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 5d1ca3336fa7..09f4c221f3fe 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.AppFuseMount; 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 @@ class StorageManagerService extends IStorageManager.Stub } } + @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 @@ class StorageManagerService extends IStorageManager.Stub // 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 @@ class StorageManagerService extends IStorageManager.Stub 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 @@ class StorageManagerService extends IStorageManager.Stub } } 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 @@ class StorageManagerService extends IStorageManager.Stub 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 411fc6778984..3ea44588e2da 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -366,11 +366,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { || 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { && 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - 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 cfa110e82e89..cd2d6d4e4585 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -29,7 +29,10 @@ import static java.util.Objects.requireNonNull; 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 @@ public class VcnManagementService extends IVcnManagementService.Stub { @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 @@ public class VcnManagementService extends IVcnManagementService.Stub { 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 d2fd8ff3890e..c6405e01dbff 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 @@ package com.android.server.am; import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND; -import static android.Manifest.permission.SYSTEM_ALERT_WINDOW; import static android.app.ActivityManager.PROCESS_STATE_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.REASON_UID_VISIBLE; 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 @@ public final class ActiveServices { } 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 @@ public final class ActiveServices { + 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { } 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 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; + r.mStartForegroundCount++; if (!stopProcStatsOp) { ServiceState stracker = r.getTracker(); if (stracker != null) { @@ -1901,6 +1928,7 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { // 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 @@ public final class ActiveServices { r.foregroundId = 0; r.foregroundNoti = null; r.mAllowWhileInUsePermissionInFgs = false; + r.mAllowStartForeground = REASON_DENIED; // Clear start entries. r.clearDeliveredStartsLocked(); @@ -5428,8 +5458,9 @@ public final class ActiveServices { * @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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { */ 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 @@ public final class ActiveServices { + ",callingUid:" + tempAllowListReason.mCallingUid)) + ">" + "; targetSdkVersion:" + r.appInfo.targetSdkVersion + + "; callerTargetSdkVersion:" + callerTargetSdkVersion + + "; startForegroundCount:" + r.mStartForegroundCount + "]"; if (!debugInfo.equals(r.mInfoAllowStartForeground)) { r.mLoggedInfoAllowStartForeground = false; @@ -5771,7 +5820,12 @@ public final class ActiveServices { 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 5859cea2d284..e16f4c9905e8 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { "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 @@ final class ActivityManagerConstants extends ContentObserver { // 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 @@ final class ActivityManagerConstants extends ContentObserver { */ 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_FG_TO_BG_FGS_GRACE_DURATION: updateFgToBgFgsGraceDuration(); break; + case KEY_FGS_START_FOREGROUND_TIMEOUT: + updateFgsStartForegroundTimeout(); + break; default: break; } @@ -819,6 +846,13 @@ final class ActivityManagerConstants extends ContentObserver { /*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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 321e3b14896c..2bceb20fa86f 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 @@ public class ActivityManagerService extends IActivityManager.Stub } @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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessRecord app; if (!isolated) { app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName, - info.uid, true); + info.uid); } else { app = null; } @@ -11933,7 +11932,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub ApplicationInfo targetInfo) { ProcessRecord pr; synchronized (this) { - pr = getProcessRecordLocked(targetInfo.processName, targetInfo.uid, true); + pr = getProcessRecordLocked(targetInfo.processName, targetInfo.uid); } try { @@ -15433,8 +15432,7 @@ public class ActivityManagerService extends IActivityManager.Stub @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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 e74c936af02d..60e8d54676ec 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 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { 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 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { 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 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } 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 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { 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 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { 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 c3f97adbd9c3..5937a18673d9 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 final class BatteryStatsService extends IBatteryStats.Stub 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 @@ public final class BatteryStatsService extends IBatteryStats.Stub 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 e79f09665153..9ecae42ce2c9 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 @@ public final class BroadcastQueue { } 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 @@ public final class BroadcastQueue { 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 2c8794d75795..ee4526b99d1c 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 @@ public class ContentProviderHelper { // 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 @@ public class ContentProviderHelper { 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 d789bbb04d7a..f9296a0e64fc 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 @@ public class MeasuredEnergySnapshot { /** 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 @@ public class MeasuredEnergySnapshot { 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 51bcde869189..2cde423bd0a3 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_LRU; 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_LRU; 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 @@ public final class ProcessList { } @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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { 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 42e7ff4c5724..cb23b898a467 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 @@ class ProcessRecord implements WindowProcessListener { 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 9cd9902f4995..54c35123fbb9 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN 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 143a1cf0e094..31183cac50e1 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 @@ class UserController implements Handler.Callback { * 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 @@ class UserController implements Handler.Callback { 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 2ee41acb4b9c..57de70858179 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.ActivityManager; 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 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 @@ public final class GameManagerService extends IGameManagerService.Stub { 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,7 +176,183 @@ public final class GameManagerService extends IGameManagerService.Stub { } 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() + "]"; } } @@ -163,6 +372,8 @@ public final class GameManagerService extends IGameManagerService.Stub { public void onStart() { mService = new GameManagerService(getContext()); publishBinderService(Context.GAME_SERVICE, mService); + mService.registerDeviceConfigListener(); + mService.registerPackageReceiver(); } @Override @@ -200,10 +411,27 @@ public final class GameManagerService extends IGameManagerService.Stub { } } + /** + * 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 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 @@ public final class GameManagerService extends IGameManagerService.Stub { mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY); } } + updateCompatModeDownscale(packageName, gameMode); } /** @@ -303,6 +532,8 @@ public final class GameManagerService extends IGameManagerService.Stub { @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 @@ public final class GameManagerService extends IGameManagerService.Stub { } } + 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 804550b8782d..2ce60d05e541 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 @@ public class AudioService extends IAudioService.Stub } } + /** @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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 3cfaaf7c23e0..677ea5dc8f03 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 @@ public abstract class BaseClientMonitor extends LoggableMonitor * * @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 @@ public abstract class BaseClientMonitor extends LoggableMonitor * @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 07d173c8da02..0c883b047fe4 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 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @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 0eb51fdba159..58eb3ba9e722 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 @@ public class FaceEnrollClient extends EnrollClient<ISession> { 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 7a846f5b14a5..8cbb896f80a2 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 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<ISessio @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 773647b4bf20..af826c2f68ba 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 @@ class FaceGetAuthenticatorIdClient extends HalClientMonitor<ISession> { @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 75888a5f28c9..0ece884ecff8 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 @@ class FaceInternalEnumerateClient extends InternalEnumerateClient<ISession> { @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 855ee1dbf52f..405e2b23b3af 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 @@ public class FaceInvalidationClient extends InvalidationClient<Face, ISession> { @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 48796c173dd8..ba678f3b19ae 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 @@ class FaceRemovalClient extends RemovalClient<Face, ISession> { @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 ce728bc7c315..5e57950ccf6e 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 @@ public class FaceResetLockoutClient extends HalClientMonitor<ISession> { @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 2863f9fc2959..229417361ddb 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 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<ISession> { @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 8d3853b96b4e..06328e311b06 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 @@ public class FaceStopUserClient extends StopUserClient<ISession> { @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 768f464517b9..ee367756dc04 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 @@ public class Sensor { } @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 @@ public class Sensor { 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 36327bb5c21a..c63af7e4b60c 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.IFace; 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 @@ public class TestHal extends IFace.Stub { 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 @@ public class TestHal extends IFace.Stub { } @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 @@ public class TestHal extends IFace.Stub { } @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 @@ public class TestHal extends IFace.Stub { } @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 50756c89613c..79e361f531c7 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } } + 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 76a47d382e4b..4e5d12d2021c 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 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp 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 620a9cf3e6f2..9e9d0eec74ab 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 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> { 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 63fa66cdca20..fd4aece339f3 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 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { 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 3c9cceddb5fd..83c64210d6f6 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 @@ class FingerprintGenerateChallengeClient extends GenerateChallengeClient<ISessio @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 ce1a31899a0c..ed2345e3362e 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 @@ class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<ISession> { @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 c9303609c8dc..e20544a86d01 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 @@ class FingerprintInternalEnumerateClient extends InternalEnumerateClient<ISessio @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 80d1a0ffea6b..6cd2ef1aa0e6 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 @@ public class FingerprintInvalidationClient extends InvalidationClient<Fingerprin @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 c622208262e0..9a9d6ab19eb6 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 @@ class FingerprintRemovalClient extends RemovalClient<Fingerprint, ISession> { @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 adffba280c7f..b00c592e83ff 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 @@ class FingerprintResetLockoutClient extends HalClientMonitor<ISession> { @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 ebb4fe63e4a1..d9bf1c3aa303 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 @@ class FingerprintRevokeChallengeClient extends RevokeChallengeClient<ISession> { @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 ba813578245c..7055d653dd16 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 @@ public class FingerprintStopUserClient extends StopUserClient<ISession> { @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 cd12d02ceef2..4862d849613d 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 @@ class Sensor { } @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 @@ class Sensor { 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 31fc06818b90..abc3597a5183 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.IFingerprint; 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 @@ public class TestHal extends IFingerprint.Stub { 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 @@ public class TestHal extends IFingerprint.Stub { } @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 @@ public class TestHal extends IFingerprint.Stub { } @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 @@ public class TestHal extends IFingerprint.Stub { } @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 00b39f11a4c6..cd2457635932 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.os.UserManager; 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.Arrays; 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 @@ public class CameraServiceProxy extends SystemService } } + 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 @@ public class CameraServiceProxy extends SystemService }; 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 @@ public class CameraServiceProxy extends SystemService 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 41bc0b928d41..874e9a6deda6 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 @@ public class ClipboardService extends SystemService { 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 @@ public class ClipboardService extends SystemService { } }); } + + /** + * 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 103ab957f312..97df5bff4946 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.IBinder; 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 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } } + /** + * 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 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { @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 0f5400d0f8e6..534dbe7699a7 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.net.QosSession; 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 @@ class QosCallbackAgentConnection implements IBinder.DeathRecipient { 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 8bda5323e4f8..b6ab47b276e3 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.Binder; 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 @@ public class QosCallbackTracker { } /** - * 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 000000000000..8ce7b66e6c7e --- /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 c0109065fea4..e38d91ca9d33 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 @@ public final class DisplayManagerService extends SystemService { 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 @@ public final class DisplayManagerService extends SystemService { } @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 @@ public final class DisplayManagerService extends SystemService { 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 d1d04966bdec..48edb73ac81d 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 @@ class DisplayManagerShellCommand extends ShellCommand { } 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 645ca7ac33e0..4bbd33817d28 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 @@ public class DisplayModeDirector { 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 @@ public class DisplayModeDirector { } 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 7110d3e6a7c1..56ad01b3c41b 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mContext = context; mBrightnessTracker = brightnessTracker; - + mBrightnessSetting = brightnessSetting; PowerManager pm = context.getSystemService(PowerManager.class); final Resources resources = context.getResources(); @@ -785,6 +788,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController.stop(); } + if (mBrightnessSetting != null) { + mBrightnessSetting.unregisterListener(mBrightnessSettingListener); + } + mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); } } @@ -831,10 +838,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void handleSettingsChange(boolean userSwitch) { mPendingScreenBrightnessSetting = getScreenBrightnessSetting(); - if (userSwitch) { // Don't treat user switches as user initiated change. mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting; @@ -1825,10 +1833,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } break; case MSG_CONFIGURE_BRIGHTNESS: - mBrightnessConfiguration = (BrightnessConfiguration)msg.obj; + mBrightnessConfiguration = (BrightnessConfiguration) msg.obj; updatePowerState(); break; @@ -2197,6 +2208,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 a62642b1f842..c90ddf48a091 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 @@ import java.util.Objects; * <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 @@ import java.util.Objects; * 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 @@ final class PersistentDataStore { 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 @@ final class PersistentDataStore { 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 @@ final class PersistentDataStore { private static final class DisplayState { private int mColorMode; + private float mBrightness; public boolean setColorMode(int colorMode) { if (colorMode == mColorMode) { @@ -486,14 +513,33 @@ final class PersistentDataStore { 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 @@ final class PersistentDataStore { 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 4f95d27a085e..75181307dc42 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.FileInputStream; 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 @@ final class UpdatableFontDir { 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 @@ final class UpdatableFontDir { 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 @@ final class UpdatableFontDir { 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 @@ final class UpdatableFontDir { } // 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 c9364c62d8b7..27f8fd3e8f5c 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 0e908d471f74..ac3c31d5cca4 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 @@ final class InputMethodUtils { 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 @@ final class InputMethodUtils { 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 f173fc7386ba..4d302b19d89b 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 @@ public class ContextHubService extends IContextHubService.Stub { 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 @@ public class ContextHubService extends IContextHubService.Stub { 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 3245fdfaebd6..7be47a4e52a8 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 abstract class IContextHubWrapper { } 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 39ed7e8b1e1a..2e4d41c7d364 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 @@ public abstract class NetworkPolicyManagerInternal { /** * 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 16eac91ee6bb..385928525ada 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_DIRECT_BOOT_UNAWARE; 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_ALLOWLI 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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); - - 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; + 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 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - 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 setInterfaceQuotasAsync(@NonNull String iface, + long warningBytes, long limitBytes) { + mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTAS, + new IfaceQuotas(iface, warningBytes, limitBytes)).sendToTarget(); } - private void setInterfaceQuota(String iface, long quotaBytes) { + 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @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 0cb0bc2c0896..0e9a9da6804b 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 class NetworkStatsManagerInternal { 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 fe43c311da6f..de5aae07d6be 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @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 8c3c42374acb..fd8ec7f4bdeb 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 @@ class ShortcutPackage extends ShortcutPackageItem { } 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 @@ class ShortcutPackage extends ShortcutPackageItem { @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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 0b21487088ba..e1d1c264e666 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 51cb995aac91..ce49d8894fdb 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 @@ class ShortcutUser { 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 629f12035b39..21e44ab9b539 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 @@ public class PackageInfoUtils { 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 f0d54b4c0617..e16c67f07020 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 @@ final class DefaultPermissionGrantPolicy { @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 3bb5c1694734..e63426f8a4db 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_RESTRICTION_UPGR 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { | 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 f4bcd3e65913..0b48b5c6dd70 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 @@ public class DomainVerificationEnforcer { 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 8075bdb4f6f2..1b2ff08bb185 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 @@ public class DomainVerificationService extends SystemService @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 @@ public class DomainVerificationService extends SystemService @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 @@ public class DomainVerificationService extends SystemService } if (sendBroadcast) { - sendBroadcastForPackage(pkgName); + sendBroadcast(pkgName); } } @@ -937,7 +946,7 @@ public class DomainVerificationService extends SystemService } if (sendBroadcast && hasAutoVerifyDomains) { - sendBroadcastForPackage(pkgName); + sendBroadcast(pkgName); } } @@ -1098,8 +1107,19 @@ public class DomainVerificationService extends SystemService 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 @@ public class DomainVerificationService extends SystemService } 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 1b5bb95fd40b..f18546474ece 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 2fcd178c3d15..9732eba33c22 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 @@ public class DisplayGroupPowerStateMapper { 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 class DisplayGroupPowerStateMapper { 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 d2a4cd604c01..54d5b7a8b67e 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_ASLEEP; 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.SparseArray; 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService mContext.getOpPackageName(), "groupId: " + groupId); } - mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; + mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS; updatePowerStateLocked(); } } @@ -1059,6 +1061,10 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService // 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService } 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService "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 @@ public final class PowerManagerService extends SystemService */ 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); - - 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; + if ((dirty & (DIRTY_DISPLAY_GROUP_WAKEFULNESS | DIRTY_WAKE_LOCKS + | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) == 0) { + return; + } + mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT); + + 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService | 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 @@ public final class PowerManagerService extends SystemService } /** - * 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService } // 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService && mBatteryLevel < mDreamsBatteryLevelMinimumWhenNotPoweredConfig) { return false; } - if (mIsPowered - && mDreamsBatteryLevelMinimumWhenPoweredConfig >= 0 - && mBatteryLevel < mDreamsBatteryLevelMinimumWhenPoweredConfig) { - return false; - } + return !mIsPowered + || mDreamsBatteryLevelMinimumWhenPoweredConfig < 0 + || mBatteryLevel >= mDreamsBatteryLevelMinimumWhenPoweredConfig; } return true; } @@ -3002,7 +3060,7 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService } 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService @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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 000000000000..054044b47245 --- /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 12c67bb9b7be..c09136eac302 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.Bundle; 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 @@ public abstract class ActivityTaskManagerInternal { * 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 29c5cec59789..c8fa50c35baa 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.text.format.TimeMigrationUtils; 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @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 38ad4f08b35d..7b0fb0146bc2 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.CONTROLLABLE; 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.AnimationType; 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 @@ class InsetsSourceProvider { 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 @@ class InsetsSourceProvider { // 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 @@ class InsetsSourceProvider { 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 @@ class InsetsSourceProvider { } } - 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 @@ class InsetsSourceProvider { 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 857217fb19e2..72610481ad32 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> * @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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> 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 7c5afa8282ee..7644cf2fa592 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.os.SystemClock; 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 @@ class WallpaperController { } 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 9245f8c3efe5..ffd6d21c1026 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 @@ public class WindowFrames { } /** - * @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 @@ public class WindowFrames { } /** + * @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 a8ca5b6d6f37..d494b7573359 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 48d4fc5464b0..eb8315248db2 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ private InsetsState mFrozenInsetsState; - @Nullable InsetsSourceProvider mPendingPositionChanged; - private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f; private KeyInterceptionInfo mKeyInterceptionInfo; @@ -824,6 +822,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 cdd5a92bec7a..55ab8c3b1af6 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 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { 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 09ae8fc4e03a..8739a01e00c7 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.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { }); } + /** + * 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .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; + } + 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 packageNames; + return result; } @Override @@ -10656,9 +10725,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * * @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { am.set(AlarmManager.RTC, alarmTime, pi); } - return deadlineState; + return notificationState; } private void suspendPersonalAppsInternal(int userId, boolean suspended) { @@ -16242,7 +16320,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @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 60d9ea20d06a..94f8e59c6057 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -323,6 +323,14 @@ void IncrementalService::IncFsMount::setReadLogsEnabled(bool value) { } } +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 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog 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 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog 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::setStorageParams(StorageId storageId, bool enableReadLog } int IncrementalService::disableReadLogsLocked(IncFsMount& ifs) { + ifs.setReadLogsRequested(false); return applyStorageParamsLocked(ifs, /*enableReadLogs=*/false); } @@ -1065,17 +1086,14 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m 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::runCmdLooper() { } } +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 @@ void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderPara 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 @@ void IncrementalService::onAppOpChanged(const std::string& packageName) { 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 @@ void IncrementalService::onAppOpChanged(const std::string& packageName) { } } for (auto&& ifs : affected) { - applyStorageParamsLocked(*ifs, /*enableReadLogs=*/false); + std::unique_lock ll(ifs->lock); + disableReadLogsLocked(*ifs); } } @@ -2251,7 +2294,7 @@ void IncrementalService::addIfsStateCallback(StorageId storageId, IfsStateCallba 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 @@ void IncrementalService::processIfsStateCallbacks() { } 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(); + } + if (callbacks.empty()) { + it = mIfsStateCallbacks.erase(it); + if (mIfsStateCallbacks.empty()) { + return; + } + } else { + ++it; } - } else { - ++it; } } @@ -2309,7 +2359,7 @@ void IncrementalService::processIfsStateCallbacks() { 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 a8f32dec824e..fb6f56c9166e 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -95,7 +95,8 @@ public: #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 @@ public: enum StorageFlags { ReadLogsAllowed = 1 << 0, ReadLogsEnabled = 1 << 1, + ReadLogsRequested = 1 << 2, }; struct LoadingProgress { @@ -365,6 +367,9 @@ private: 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 @@ private: 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 @@ private: 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 34654994c9fc..8e416f36f49e 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -212,6 +212,9 @@ public: 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 @@ public: 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 @@ public: 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 a787db573dfc..d4cdcbe9cac0 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -84,6 +84,8 @@ public: 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 @@ public: 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 @@ public: 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 e6d487255eff..ddb778462df5 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -379,6 +379,7 @@ public: 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 @@ public: 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 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedNoData) { 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 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedError) { 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 @@ TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccess) { 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDone) { 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDone) { 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDoneWithReadlogs) { 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDoneWithReadlogs) { 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDoneWithReadlogs) { 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 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { { // 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 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { { // 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 @@ TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) { { // 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 a262939c0ef9..29aedcea0cd2 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 @@ public final class ProfcollectForwardingService extends SystemService { 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 1b0a305b5bdd..537a49e4adcd 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 @@ class DomainVerificationEnforcerTest { allowQueryAll.set(true) - runMethod(target, NON_VERIFIER_UID) + assertFails { runMethod(target, NON_VERIFIER_UID) } } private fun approvedVerifier() { @@ -816,7 +816,7 @@ class DomainVerificationEnforcerTest { // 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 28940b34c82a..84819619ad79 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; 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_MAX_INT 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 @@ public class AlarmManagerServiceTest { 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 @@ public class AlarmManagerServiceTest { 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 @@ public class AlarmManagerServiceTest { 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 @@ public class AlarmManagerServiceTest { } @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 22b2f7e04069..d220444f47de 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 @@ public class ApplicationExitInfoTest { 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 5761958ea89d..9513c6e276b1 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 73a2febf72aa..4a67ec71fcaa 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_ 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 @@ public class BatteryExternalStatsWorkerTest { 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 @@ public class BatteryExternalStatsWorkerTest { 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 9ffb50176f0e..5c8a7d25a25d 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 android.util.Log; 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { /* 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 f3ee2332906f..4240581bc504 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 @@ public class AppSearchImplPlatformTest { // 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 @@ public class AppSearchImplPlatformTest { // 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 @@ public class AppSearchImplPlatformTest { // 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 @@ public class AppSearchImplPlatformTest { 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 @@ public class AppSearchImplPlatformTest { 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 15ada896512b..c8099e2467a1 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 @@ public class DisplayModeDirectorTest { } @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 dbb415c88a5f..e7ffea0a650d 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.HashSet; 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 @@ public final class UpdatableFontDirTest { } } + 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 @@ public final class UpdatableFontDirTest { @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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { + " <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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -485,7 +492,7 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { try { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - readonlyFile); + readonlyFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -551,7 +558,7 @@ public final class UpdatableFontDirTest { public long getRevision(File file) throws IOException { return 0; } - }, fakeFsverityUtil, mConfigFile); + }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -580,7 +587,7 @@ public final class UpdatableFontDirTest { public long getRevision(File file) throws IOException { return 0; } - }, fakeFsverityUtil, mConfigFile); + }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -617,7 +624,7 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -637,7 +644,7 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -703,7 +710,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -723,7 +730,7 @@ public final class UpdatableFontDirTest { 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 @@ public final class UpdatableFontDirTest { 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 70718f765412..000000000000 --- 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 c61635cbd4b6..d74bff2837ce 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.IThermalService; 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 @@ public class OneTouchPlayActionTest { @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 @@ public class OneTouchPlayActionTest { } @Test - public void succeedWithUnknownTvDevice() { + public void succeedWithUnknownTvDevice() throws Exception { + setUp(true); + HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); @@ -185,7 +193,9 @@ public class OneTouchPlayActionTest { } @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 @@ public class OneTouchPlayActionTest { } @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 @@ public class OneTouchPlayActionTest { } @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 @@ public class OneTouchPlayActionTest { } @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 @@ public class OneTouchPlayActionTest { } @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 @@ public class OneTouchPlayActionTest { } @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 @@ public class OneTouchPlayActionTest { 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 @@ public class OneTouchPlayActionTest { 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 eebc25aab279..6f1268e5de24 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.hamcrest.core.Is.is; 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.content.res.Resources; 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.Test; 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 @@ public class InputMethodUtilsTest { } } + @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 @@ public class InputMethodUtilsTest { } 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 fb01ff6e16c6..100d3ea87a89 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 @@ public class NetworkPolicyManagerServiceTest { true); } + private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) { + stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, + 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 limit reached, new limit will be calculated and - * re-armed. + * Test that when StatsProvider triggers warning and limit reached, new quotas will be + * calculated and re-armed. */ @Test - public void testStatsProviderLimitReached() throws Exception { + public void testStatsProviderWarningAndLimitReached() throws Exception { final int CYCLE_DAY = 15; final NetworkStats stats = new NetworkStats(0L, 1); - stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, - 2999, 1, 2000, 1, 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenReturn(stats.getTotalBytes()); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenReturn(stats); + 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 212a9c614632..8e2b20700e2c 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.FileUtils; 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 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @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 d63a4674a83d..a2311690744e 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 @@ public class PackageManagerSettingsTests { 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 @@ public class PackageManagerSettingsTests { .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 322e448d983f..826a8d446e1d 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 @@ public class SuspendDialogInfoTest { } @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 @@ public class SuspendDialogInfoTest { } @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 @@ public class SuspendDialogInfoTest { } @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 6e5fbd0b6ed0..2df6c5ab80e8 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.provider.Settings; 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 class PowerManagerServiceTest { 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 class PowerManagerServiceTest { 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 @@ public class PowerManagerServiceTest { @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 1700707d9866..000000000000 --- 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 ce5fc4021eac..678defe36566 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 class AppTransitionControllerTest extends WindowTestsBase { 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 83aca5e2d482..c3279bf05737 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 @@ public class AppTransitionTests extends WindowTestsBase { } @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 @@ public class AppTransitionTests extends WindowTestsBase { 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 24b4f65f65ff..0afd39f1ed01 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.same; 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 @@ public class DisplayContentTests extends WindowTestsBase { } /** - * 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 @@ public class DisplayContentTests extends WindowTestsBase { } /** - * 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 @@ public class DisplayContentTests extends WindowTestsBase { // 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 @@ public class DisplayContentTests extends WindowTestsBase { 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 @@ public class DisplayContentTests extends WindowTestsBase { 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 @@ public class DisplayContentTests extends WindowTestsBase { } @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 @@ public class DisplayContentTests extends WindowTestsBase { @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 @@ public class DisplayContentTests extends WindowTestsBase { @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 @@ public class DisplayContentTests extends WindowTestsBase { @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 @@ public class DisplayContentTests extends WindowTestsBase { @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 @@ public class DisplayContentTests extends WindowTestsBase { @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 @@ public class DisplayContentTests extends WindowTestsBase { 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 @@ public class DisplayContentTests extends WindowTestsBase { 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 4e2697ab64f8..1bddd7b4ef64 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 @@ public class DragDropControllerTests extends WindowTestsBase { */ 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 f97e79444d59..7d137bcb2cff 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 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { @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 8388f2a6c6fa..000000000000 --- 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 index 3a7954b3a5ce..748622b810d5 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_PRIMAR 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_INVISIBLE; 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 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.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.ArrayList; 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 @@ public class ActivityStackTests extends WindowTestsBase { } @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 @@ public class ActivityStackTests extends WindowTestsBase { @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 @@ public class ActivityStackTests extends WindowTestsBase { // 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 @@ public class ActivityStackTests extends WindowTestsBase { 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 @@ public class ActivityStackTests extends WindowTestsBase { @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 @@ public class ActivityStackTests extends WindowTestsBase { 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 class ActivityStackTests extends WindowTestsBase { 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 @@ public class ActivityStackTests extends WindowTestsBase { } @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 @@ public class ActivityStackTests extends WindowTestsBase { } @Test - public void testStackOverridesDisplayWindowingMode() { + public void testRootTaskOverridesDisplayWindowingMode() { final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); @@ -358,95 +533,92 @@ public class ActivityStackTests extends WindowTestsBase { } @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 */)); - - 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 */)); + assertTrue(homeRootTask.shouldBeVisible(null /* starting */)); + assertTrue(pinnedRootTask.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 */)); + 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 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 @@ public class ActivityStackTests extends WindowTestsBase { 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 @@ public class ActivityStackTests extends WindowTestsBase { 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 @@ public class ActivityStackTests extends WindowTestsBase { 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 @@ public class ActivityStackTests extends WindowTestsBase { 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 @@ public class ActivityStackTests extends WindowTestsBase { @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 */); - - doReturn(false).when(homeStack).isTranslucent(any()); + 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(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 @@ public class ActivityStackTests extends WindowTestsBase { // 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 */); + 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(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_NoMoveHomeBehindTranslucent() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + public void testMoveHomeRootTaskBehindBottomMostVisible_NoMoveHomeBehindTranslucent() { + final Task homeRootTask = createTaskForShouldBeVisibleTest(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(true).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_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 */); - doReturn(false).when(homeStack).isTranslucent(any()); - doReturn(false).when(fullscreenStack).isTranslucent(any()); + doReturn(false).when(homeRootTask).isTranslucent(any()); + doReturn(false).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 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 testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + public void testMoveHomeRootTaskBehindBottomMostVisible_MoveHomeBehindFullscreen() { + 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, + 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)); - - 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)); - - 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); + // Ensure non always on top root task is put below always on top root tasks. + assertEquals(alwaysOnTopRootTask, getRootTaskAbove(nonAlwaysOnTopRootTask)); + + 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)); + + 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 @@ public class ActivityStackTests extends WindowTestsBase { } @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 class ActivityStackTests extends WindowTestsBase { 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 @@ public class ActivityStackTests extends WindowTestsBase { // 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 @@ public class ActivityStackTests extends WindowTestsBase { } @Test - public void testStackOrderChangedOnAddPositionStack() { + public void testRootTaskOrderChangedOnAddPositionRootTask() { final Task task = new TaskBuilder(mSupervisor).build(); mDefaultTaskDisplayArea.removeRootTask(task); @@ -1260,14 +1412,14 @@ public class ActivityStackTests extends WindowTestsBase { } @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 @@ public class ActivityStackTests extends WindowTestsBase { 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 @@ public class ActivityStackTests extends WindowTestsBase { 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 1b114c194bfb..20bced252764 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 @@ public class RootWindowContainerTests extends WindowTestsBase { 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 eba5634e4355..92d4edec85f4 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_PINNED; 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 android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; 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 androidx.test.filters.SmallTest; 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 @@ import org.junit.runner.RunWith; @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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { } @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); - - final WindowContainer taskStackContainer = stack1.getParent(); - - 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); - - 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); - - taskStackContainer.positionChildAt(1, mPinnedTask, false); - assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1); - assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask); + 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()); + + // 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 WindowContainer taskContainer = rootTask1.getParent(); + + 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); + + 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()); + + // Test that no root task can be above pinned root task. + final Task rootTask1 = createTask(mDisplayContent); - final WindowContainer taskStackContainer = stack1.getParent(); + final WindowContainer taskContainer = rootTask1.getParent(); - final int stackPos = taskStackContainer.mChildren.indexOf(stack1); - final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedTask); - assertThat(pinnedStackPos).isGreaterThan(stackPos); + final int rootTaskPos = taskContainer.mChildren.indexOf(rootTask1); + final int pinnedTaskPos = taskContainer.mChildren.indexOf(pinnedTask); + assertThat(pinnedTaskPos).isGreaterThan(rootTaskPos); - taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, 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); - taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false); - assertEquals(taskStackContainer.mChildren.get(stackPos), stack1); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask); + 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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { 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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { 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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { } @Test - public void testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange() { + public void testGetOrientation_nonResizableHomeTaskWithHomeActivityPendingVisibilityChange() { final RootWindowContainer rootWindowContainer = mWm.mAtmService.mRootWindowContainer; final TaskDisplayArea defaultTaskDisplayArea = rootWindowContainer.getDefaultTaskDisplayArea(); @@ -330,7 +328,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { ActivityRecord primarySplitActivity = primarySplitTask.getTopNonFinishingActivity(); assertNotNull(primarySplitActivity); primarySplitActivity.setState(RESUMED, - "testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange"); + "testGetOrientation_nonResizableHomeTaskWithHomeActivityPendingVisibilityChange"); ActivityRecord homeActivity = rootHomeTask.getTopNonFinishingActivity(); if (homeActivity == null) { @@ -350,14 +348,14 @@ public class TaskDisplayAreaTests extends WindowTestsBase { 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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { 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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { 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 ed5729400a39..de4c40df1c79 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 class TaskLaunchParamsModifierTests extends WindowTestsBase { 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 class TaskLaunchParamsModifierTests extends WindowTestsBase { 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 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { 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 class TaskLaunchParamsModifierTests extends WindowTestsBase { 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 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { 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 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { 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 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } 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 d853b930af11..000000000000 --- 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 e58c162ceadd..000000000000 --- 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 dca6b089d66b..2389d2d6e8d6 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.junit.Assert.assertTrue; 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 @@ import org.junit.runner.RunWith; * 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 @@ public class TaskTests extends WindowTestsBase { @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 @@ public class TaskTests extends WindowTestsBase { @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 @@ public class TaskTests extends WindowTestsBase { } 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 @@ public class TaskTests extends WindowTestsBase { @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 @@ public class TaskTests extends WindowTestsBase { } @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 @@ public class TaskTests extends WindowTestsBase { @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 @@ public class TaskTests extends WindowTestsBase { @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 @@ public class TaskTests extends WindowTestsBase { @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 @@ public class TaskTests extends WindowTestsBase { @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 @@ public class TaskTests extends WindowTestsBase { @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 @@ public class TaskTests extends WindowTestsBase { @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 @@ public class TaskTests extends WindowTestsBase { 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 79ef8680dfec..2dfb3a1a84bc 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 @@ public class TransitionTests extends WindowTestsBase { 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 @@ public class TransitionTests extends WindowTestsBase { 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 @@ public class TransitionTests extends WindowTestsBase { 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 class TransitionTests extends WindowTestsBase { 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 @@ public class TransitionTests extends WindowTestsBase { 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 @@ public class TransitionTests extends WindowTestsBase { 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 @@ public class TransitionTests extends WindowTestsBase { 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 b88173d5c1f0..6919c4cf6a7c 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 @@ public class WindowContainerTests extends WindowTestsBase { @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 @@ public class WindowContainerTests extends WindowTestsBase { @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 @@ public class WindowContainerTests extends WindowTestsBase { 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 @@ public class WindowContainerTests extends WindowTestsBase { @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 @@ public class WindowContainerTests extends WindowTestsBase { @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 6d0e510ba626..baf072dbbc10 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 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @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 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @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 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @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 01c503e01326..e2b58bcb4bd5 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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { // 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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { // 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 @@ public class WindowOrganizerTests extends WindowTestsBase { // 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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { 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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 @@ public class WindowOrganizerTests extends WindowTestsBase { @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 5b5b1da327bd..bfbe203fb65e 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 @@ public class WindowStateTests extends WindowTestsBase { 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 b210dfb654ba..e9e0c99c3d93 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 @@ class WindowTestsBase extends SystemServiceTestsBase { 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) - .setWindowingMode(windowingMode) - .setActivityType(activityType) - .setIntent(new Intent()) - .build(); + /** 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 */); } - Task createTaskStackOnTaskDisplayArea(int windowingMode, int activityType, - TaskDisplayArea tda) { - return new TaskBuilder(tda.mDisplayContent.mAtmService.mTaskSupervisor) - .setTaskDisplayArea(tda) + /** 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(); + } } - /** 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 @@ class WindowTestsBase extends SystemServiceTestsBase { */ 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 @@ class WindowTestsBase extends SystemServiceTestsBase { */ 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 @@ class WindowTestsBase extends SystemServiceTestsBase { .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 @@ class WindowTestsBase extends SystemServiceTestsBase { 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 @@ class WindowTestsBase extends SystemServiceTestsBase { .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 afa35fe8deba..2e692e6e68f7 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 748e5dfe4e91..22a6445843a4 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 6541774e9ac3..11ccfd88c100 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 @@ final class HotwordDetectionConnection { 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 @@ final class HotwordDetectionConnection { } } - 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 29354eb0fd1f..c110b231bd0e 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 @@ public class VoiceInteractionManagerService extends SystemService { } @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 c9b0a3e30ca3..0742cb4c0c84 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.IVoiceInteractionSession; 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.IVoiceActionCheckCallback; 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 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne 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 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne 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 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne 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 428d342a80c9..cc021a9acfe2 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.IVoiceInteractionSession; 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; 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, IVoiceInteractionSession mSession; IVoiceInteractor mInteractor; ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>(); + private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>(); AssistDataRequester mAssistDataRequester; IVoiceInteractionSessionShowCallback mShowCallback = @@ -192,7 +193,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } 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 4e64838d99ce..d77ab2b24b31 100755 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -687,7 +687,11 @@ public final class Call { 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 a012498e157d..3b2861600b56 100644 --- a/telephony/java/android/telephony/PhysicalChannelConfig.java +++ b/telephony/java/android/telephony/PhysicalChannelConfig.java @@ -293,6 +293,14 @@ public final class PhysicalChannelConfig implements Parcelable { } /** + * 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 @@ public final class PhysicalChannelConfig implements Parcelable { 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 2011eed26977..fe124ac15393 100644 --- a/telephony/java/android/telephony/data/NrQos.java +++ b/telephony/java/android/telephony/data/NrQos.java @@ -50,6 +50,18 @@ public final class NrQos extends Qos implements Parcelable { 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 000000000000..fd3bbb0865cb --- /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 000000000000..857ccb960d52 --- /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 f49d4fcab5f2..4259a8620727 100644 --- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java @@ -32,7 +32,7 @@ public class HierrarchicalDataClassBase implements Parcelable { - // 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 @@ public class HierrarchicalDataClassBase implements Parcelable { }; @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 e8cce23fa324..677094b14fd6 100644 --- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java @@ -46,7 +46,7 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { - // 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 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { }; @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 9de65522fccd..eb260ab6d35d 100644 --- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java +++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java @@ -54,7 +54,7 @@ public class ParcelAllTheThingsDataClass implements Parcelable { - // 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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { } @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 5a3e273275ed..158e0656b574 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.Size; 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 @@ public final class SampleDataClass implements Parcelable { /** + * 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 @@ public final class SampleDataClass implements Parcelable { - // 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 @@ public final class SampleDataClass implements Parcelable { * * 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 @@ public final class SampleDataClass implements Parcelable { @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 @@ public final class SampleDataClass implements Parcelable { "from", 0f); } + this.mToken = token; + AnnotationValidations.validate( + NonNull.class, null, mToken); + this.mIPCInterface = iPCInterface; onConstructed(); } @@ -797,6 +820,22 @@ public final class SampleDataClass implements Parcelable { } /** + * 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 @@ public final class SampleDataClass implements Parcelable { 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 @@ public final class SampleDataClass implements Parcelable { "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 @@ public final class SampleDataClass implements Parcelable { && 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 @@ public final class SampleDataClass implements Parcelable { _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 @@ public final class SampleDataClass implements Parcelable { 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 @@ public final class SampleDataClass implements Parcelable { 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 @@ public final class SampleDataClass implements Parcelable { 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 @@ public final class SampleDataClass implements Parcelable { dest.writeInt(mStringRes); dest.writeInt(mDayOfWeek); dest.writeFloatArray(mCoords); + dest.writeStrongBinder(mToken); + if (mIPCInterface != null) dest.writeStrongInterface(mIPCInterface); } @Override @@ -1326,6 +1398,8 @@ public final class SampleDataClass implements Parcelable { 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 @@ public final class SampleDataClass implements Parcelable { "from", 0f); } + this.mToken = token; + AnnotationValidations.validate( + NonNull.class, null, mToken); + this.mIPCInterface = iPCInterface; onConstructed(); } @@ -1454,6 +1532,8 @@ public final class SampleDataClass implements Parcelable { 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 @@ public final class SampleDataClass implements Parcelable { 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 @@ public final class SampleDataClass implements Parcelable { 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 @@ public final class SampleDataClass implements Parcelable { 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 @@ public final class SampleDataClass implements Parcelable { } @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 3ab34452f9fc..a535e227cccf 100644 --- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java +++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java @@ -85,7 +85,7 @@ public class SampleWithCustomBuilder implements Parcelable { - // 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 @@ public class SampleWithCustomBuilder implements Parcelable { } @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 8901cac1cb1b..d40962456741 100644 --- a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java +++ b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java @@ -36,7 +36,7 @@ public class SampleWithNestedDataClasses { - // 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 @@ public class SampleWithNestedDataClasses { }; @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 @@ public class SampleWithNestedDataClasses { - // 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 @@ public class SampleWithNestedDataClasses { }; @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 @@ public class SampleWithNestedDataClasses { - // 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 @@ public class SampleWithNestedDataClasses { }; @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 ac776f3c2764..3583b95fb4ce 100644 --- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java +++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java @@ -64,7 +64,7 @@ public class StaleDataclassDetectorFalsePositivesTest { - // 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 @@ public class StaleDataclassDetectorFalsePositivesTest { } @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/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index b25bc997e9ab..dfb229d54c99 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -136,7 +136,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - @Presubmit + @FlakyTest @Test fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index 95b1d3c28e47..be0357e826a8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -153,7 +153,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation) } - @Presubmit + @FlakyTest @Test fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index a9888b1876b7..a3d1395a680a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.launch +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -24,6 +25,7 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.dsl.FlickerBuilder import org.junit.FixMethodOrder +import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -56,6 +58,12 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp } } + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index a19a95da021c..62b9b81ac35d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -80,6 +80,24 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio super.navBarLayerRotatesAndScales() } + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() { + super.statusBarLayerRotatesScales() + } + + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + @FlakyTest + @Test + override fun focusChanges() { + super.focusChanges() + } + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt index dcc64c94e0c4..38af8a7d24df 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.launch +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -24,6 +25,7 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.dsl.FlickerBuilder import org.junit.FixMethodOrder +import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -61,6 +63,18 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp } } + @FlakyTest + @Test + override fun focusChanges() { + super.focusChanges() + } + + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index 6d2a9238bed5..f037f084f172 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.rotation import android.app.Instrumentation import android.platform.test.annotations.Presubmit +import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter @@ -79,7 +80,7 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) testSpec.navBarLayerIsAlwaysVisible() } - @Presubmit + @FlakyTest @Test open fun navBarLayerRotatesAndScales() { testSpec.navBarLayerRotatesAndScales( @@ -98,14 +99,14 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) testSpec.statusBarLayerIsAlwaysVisible() } - @Presubmit + @FlakyTest @Test open fun statusBarLayerRotatesScales() { testSpec.statusBarLayerRotatesScales( testSpec.config.startRotation, testSpec.config.endRotation) } - @Presubmit + @FlakyTest @Test open fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { @@ -121,7 +122,7 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } - @Presubmit + @FlakyTest @Test open fun noUncoveredRegions() { testSpec.noUncoveredRegions(testSpec.config.startRotation, @@ -134,7 +135,7 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) testSpec.focusDoesNotChange() } - @Presubmit + @FlakyTest @Test open fun appLayerRotates_StartingPos() { testSpec.assertLayersStart { @@ -142,7 +143,7 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } - @Presubmit + @FlakyTest @Test open fun appLayerRotates_EndingPos() { testSpec.assertLayersEnd { diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp index 2e57467e55fe..c679d0487629 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -25,17 +25,24 @@ android_test_helper_app { 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 ad8aac17d844..e633c87d7fbb 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 @@ public class StagedInstallInternalTest { 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 @@ public class StagedInstallInternalTest { } @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 8dc53ac26715..9fd190cc12fe 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.Test; 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 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } @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 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { //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 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { .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 11d44b86bc50..cd9da8eaa727 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.assertNotEquals; 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 @@ import java.net.SocketException; 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 @@ public class NetworkTest { } @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 14dddcbd6ce7..e039ef072542 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.assertTrue 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 d82565ab7d17..44298d4b7652 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.MATCH_ANY_USER; 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_ETHERNET; 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.security.Credentials; 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 @@ public class ConnectivityServiceTest { && 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 @@ public class ConnectivityServiceTest { } @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 97c65eb6d31b..8b072c49de82 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 @@ public class IpConnectivityMetricsTest { 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 @@ public class IpConnectivityMetricsTest { 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 52975e4424bc..50aaaee24418 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 @@ public class NetdEventListenerServiceTest { 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 @@ public class NetdEventListenerServiceTest { 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 7a0be97fa2bf..bcd6ed73e133 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 @@ public final class FrameworksTestsFilter extends SelectTest { "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 babea364edbe..f15d4204d125 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -49,7 +49,9 @@ import static org.mockito.Mockito.when; 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 @@ public class VcnManagementServiceTest { 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 @@ public class VcnManagementServiceTest { } @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 02ebaef90f0b..74392ddc30e6 100644 --- a/tools/codegen/src/com/android/codegen/FieldInfo.kt +++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt @@ -220,11 +220,12 @@ data class FieldInfo( 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 d9ad649782bb..4da401951470 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" |