diff options
765 files changed, 18847 insertions, 10227 deletions
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb index 29bcfe046b98..1bd90a8eafae 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "6508977" + build_id: "7197701" target: "CtsShim" source_file: "aosp_arm64/CtsShimPriv.apk" } @@ -8,5 +8,5 @@ drops { version: "" version_group: "" git_project: "platform/frameworks/base" - git_branch: "rvc-dev" + git_branch: "sc-dev" } diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb index be172e6122cf..544bca029888 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "6508977" + build_id: "7197701" target: "CtsShim" source_file: "aosp_arm64/CtsShim.apk" } @@ -8,5 +8,5 @@ drops { version: "" version_group: "" git_project: "platform/frameworks/base" - git_branch: "rvc-dev" + git_branch: "sc-dev" } diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb index 13eca13c9308..72386bb88465 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "6508977" + build_id: "7197701" target: "CtsShim" source_file: "aosp_x86_64/CtsShimPriv.apk" } @@ -8,5 +8,5 @@ drops { version: "" version_group: "" git_project: "platform/frameworks/base" - git_branch: "rvc-dev" + git_branch: "sc-dev" } diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb index 2e863fe57f3c..893eac214daa 100644 --- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb +++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb @@ -1,6 +1,6 @@ drops { android_build_drop { - build_id: "6508977" + build_id: "7197701" target: "CtsShim" source_file: "aosp_x86_64/CtsShim.apk" } @@ -8,5 +8,5 @@ drops { version: "" version_group: "" git_project: "platform/frameworks/base" - git_branch: "rvc-dev" + git_branch: "sc-dev" } diff --git a/Android.bp b/Android.bp index c5980b911b17..9690969305bf 100644 --- a/Android.bp +++ b/Android.bp @@ -584,7 +584,7 @@ java_library { "android.hardware.vibrator-V2-java", "android.security.apc-java", "android.security.authorization-java", - "android.security.usermanager-java", + "android.security.maintenance-java", "android.security.vpnprofilestore-java", "android.system.keystore2-V1-java", "android.system.suspend.control.internal-java", @@ -665,9 +665,8 @@ java_defaults { ], required: [ "framework-platform-compat-config", - // TODO: remove gps_debug, cec_config.xml and protolog.conf.json when the build system propagates "required" properly. + // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly. "gps_debug.conf", - "cec_config.xml", "icu4j-platform-compat-config", "libcore-platform-compat-config", "protolog.conf.json.gz", diff --git a/TEST_MAPPING b/TEST_MAPPING index d08c52782fb3..9ceef6bbe8a3 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -82,5 +82,65 @@ { "name": "ManagedProfileLifecycleStressTest" } - ] + ], + "auto-postsubmit": [ + // Test tag for automotive targets. These are only running in postsubmit so as to harden the + // automotive targets to avoid introducing additional test flake and build time. The plan for + // presubmit testing for auto is to augment the existing tests to cover auto use cases as well. + // Additionally, this tag is used in targeted test suites to limit resource usage on the test + // infra during the hardening phase. + // TODO: this tag to be removed once the above is no longer an issue. + { + "name": "FrameworksUiServicesTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "ExtServicesUnitTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TestablesTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-annotation": "android.platform.test.annotations.Presubmit" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + }, + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-annotation": "android.platform.test.annotations.Presubmit" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] } diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index da446bf1e7c6..a62bb504debc 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -19,19 +19,10 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.SystemService; import android.content.Context; -import android.os.Bundle; -import android.os.ParcelableException; -import android.os.RemoteException; -import com.android.internal.infra.AndroidFuture; import com.android.internal.util.Preconditions; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -246,349 +237,9 @@ public class AppSearchManager { mService, mContext.getUserId(), getPackageName(), executor, callback); } - /** - * Sets the schema being used by documents provided to the {@link #putDocuments} method. - * - * <p>The schema provided here is compared to the stored copy of the schema previously supplied - * to {@link #setSchema}, if any, to determine how to treat existing documents. The following - * types of schema modifications are always safe and are made without deleting any existing - * documents: - * - * <ul> - * <li>Addition of new types - * <li>Addition of new {@link - * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or - * {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED - * REPEATED} properties to a type - * <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an {@link - * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} - * property into a {@link - * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} - * property. - * </ul> - * - * <p>The following types of schema changes are not backwards-compatible: - * - * <ul> - * <li>Removal of an existing type - * <li>Removal of a property from a type - * <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property - * <li>For properties of {@code GenericDocument} type, changing the schema type of {@code - * GenericDocument}s of that property - * <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an {@link - * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} - * property into a {@link - * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} - * property). - * <li>Adding a {@link - * android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} - * property. - * </ul> - * - * <p>Supplying a schema with such changes will result in this call returning an {@link - * AppSearchResult} with a code of {@link AppSearchResult#RESULT_INVALID_SCHEMA} and an error - * message describing the incompatibility. In this case the previously set schema will remain - * active. - * - * <p>If you need to make non-backwards-compatible changes as described above, instead use the - * {@link #setSchema(List, boolean)} method with the {@code forceOverride} parameter set to - * {@code true}. - * - * <p>It is a no-op to set the same schema as has been previously set; this is handled - * efficiently. - * - * @param request The schema update request. - * @return the result of performing this operation. - * @hide - * @deprecated use {@link AppSearchSession#setSchema} instead. - */ - @NonNull - public AppSearchResult<Void> setSchema(@NonNull SetSchemaRequest request) { - Preconditions.checkNotNull(request); - // TODO: This should use com.android.internal.infra.RemoteStream or another mechanism to - // avoid binder limits. - List<Bundle> schemaBundles = new ArrayList<>(request.getSchemas().size()); - for (AppSearchSchema schema : request.getSchemas()) { - schemaBundles.add(schema.getBundle()); - } - AndroidFuture<AppSearchResult> future = new AndroidFuture<>(); - try { - mService.setSchema( - getPackageName(), - DEFAULT_DATABASE_NAME, - schemaBundles, - new ArrayList<>(request.getSchemasNotDisplayedBySystem()), - /*schemasPackageAccessible=*/ Collections.emptyMap(), - request.isForceOverride(), - mContext.getUserId(), - new IAppSearchResultCallback.Stub() { - public void onResult(AppSearchResult result) { - future.complete(result); - } - }); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - return getFutureOrThrow(future); - } - - /** - * Index {@link GenericDocument}s into AppSearch. - * - * <p>You should not call this method directly; instead, use the {@code - * AppSearch#putDocuments()} API provided by JetPack. - * - * <p>Each {@link GenericDocument}'s {@code schemaType} field must be set to the name of a - * schema type previously registered via the {@link #setSchema} method. - * - * @param request {@link PutDocumentsRequest} containing documents to be indexed - * @return The pending result of performing this operation. The keys of the returned {@link - * AppSearchBatchResult} are the URIs of the input documents. The values are {@code null} if - * they were successfully indexed, or a failed {@link AppSearchResult} otherwise. - * @throws RuntimeException If an error occurred during the execution. - * @hide - * @deprecated use {@link AppSearchSession#put} instead. - */ - public AppSearchBatchResult<String, Void> putDocuments(@NonNull PutDocumentsRequest request) { - // TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in - // one big list. - List<GenericDocument> documents = request.getGenericDocuments(); - List<Bundle> documentBundles = new ArrayList<>(documents.size()); - for (int i = 0; i < documents.size(); i++) { - documentBundles.add(documents.get(i).getBundle()); - } - AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>(); - try { - mService.putDocuments( - getPackageName(), - DEFAULT_DATABASE_NAME, - documentBundles, - mContext.getUserId(), - new IAppSearchBatchResultCallback.Stub() { - public void onResult(AppSearchBatchResult result) { - future.complete(result); - } - - public void onSystemError(ParcelableException exception) { - future.completeExceptionally(exception); - } - }); - } catch (RemoteException e) { - future.completeExceptionally(e); - } - return getFutureOrThrow(future); - } - - /** - * Retrieves {@link GenericDocument}s by URI. - * - * <p>You should not call this method directly; instead, use the {@code - * AppSearch#getDocuments()} API provided by JetPack. - * - * @param request {@link GetByUriRequest} containing URIs to be retrieved. - * @return The pending result of performing this operation. The keys of the returned {@link - * AppSearchBatchResult} are the input URIs. The values are the returned {@link - * GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise. URIs that - * are not found will return a failed {@link AppSearchResult} with a result code of {@link - * AppSearchResult#RESULT_NOT_FOUND}. - * @throws RuntimeException If an error occurred during the execution. - * @hide - * @deprecated use {@link AppSearchSession#getByUri} instead. - */ - public AppSearchBatchResult<String, GenericDocument> getByUri( - @NonNull GetByUriRequest request) { - // TODO(b/146386470): Transmit the result documents as a RemoteStream instead of sending - // them in one big list. - List<String> uris = new ArrayList<>(request.getUris()); - AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>(); - try { - mService.getDocuments( - getPackageName(), - DEFAULT_DATABASE_NAME, - request.getNamespace(), - uris, - request.getProjectionsInternal(), - mContext.getUserId(), - new IAppSearchBatchResultCallback.Stub() { - public void onResult(AppSearchBatchResult result) { - future.complete(result); - } - - public void onSystemError(ParcelableException exception) { - future.completeExceptionally(exception); - } - }); - } catch (RemoteException e) { - future.completeExceptionally(e); - } - - // Translate from document bundles to GenericDocument instances - AppSearchBatchResult<String, Bundle> bundleResult = getFutureOrThrow(future); - AppSearchBatchResult.Builder<String, GenericDocument> documentResultBuilder = - new AppSearchBatchResult.Builder<>(); - - // Translate successful results - for (Map.Entry<String, Bundle> bundleEntry : bundleResult.getSuccesses().entrySet()) { - GenericDocument document; - try { - document = new GenericDocument(bundleEntry.getValue()); - } catch (Throwable t) { - // These documents went through validation, so how could this fail? We must have - // done something wrong. - documentResultBuilder.setFailure( - bundleEntry.getKey(), - AppSearchResult.RESULT_INTERNAL_ERROR, - t.getMessage()); - continue; - } - documentResultBuilder.setSuccess(bundleEntry.getKey(), document); - } - - // Translate failed results - for (Map.Entry<String, AppSearchResult<Bundle>> bundleEntry : - bundleResult.getFailures().entrySet()) { - documentResultBuilder.setFailure( - bundleEntry.getKey(), - bundleEntry.getValue().getResultCode(), - bundleEntry.getValue().getErrorMessage()); - } - - return documentResultBuilder.build(); - } - - /** - * Searches a document based on a given query string. - * - * <p>You should not call this method directly; instead, use the {@code AppSearch#query()} API - * provided by JetPack. - * - * <p>Currently we support following features in the raw query format: - * - * <ul> - * <li>AND - * <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and ‘cat’”). - * Example: hello world matches documents that have both ‘hello’ and ‘world’ - * <li>OR - * <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example: - * dog OR puppy - * <li>Exclusion - * <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example: - * -dog excludes the term ‘dog’ - * <li>Grouping terms - * <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g. - * “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”). - * Example: (dog puppy) (cat kitten) two one group containing two terms. - * <li>Property restricts - * <p>Specifies which properties of a document to specifically match terms in (e.g. “match - * documents where the ‘subject’ property contains ‘important’”). Example: - * subject:important matches documents with the term ‘important’ in the ‘subject’ property - * <li>Schema type restricts - * <p>This is similar to property restricts, but allows for restricts on top-level - * document fields, such as schema_type. Clients should be able to limit their query to - * documents of a certain schema_type (e.g. “match documents that are of the ‘Email’ - * schema_type”). Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will - * match documents that contain the query term ‘dog’ and are of either the ‘Email’ schema - * type or the ‘Video’ schema type. - * </ul> - * - * @param queryExpression Query String to search. - * @param searchSpec Spec for setting filters, raw query etc. - * @throws RuntimeException If an error occurred during the execution. - * @hide - * @deprecated use AppSearchSession#query instead. - */ - @NonNull - public AppSearchResult<List<SearchResult>> query( - @NonNull String queryExpression, @NonNull SearchSpec searchSpec) { - // TODO(b/146386470): Transmit the result documents as a RemoteStream instead of sending - // them in one big list. - AndroidFuture<AppSearchResult> future = new AndroidFuture<>(); - try { - mService.query( - getPackageName(), - DEFAULT_DATABASE_NAME, - queryExpression, - searchSpec.getBundle(), - mContext.getUserId(), - new IAppSearchResultCallback.Stub() { - public void onResult(AppSearchResult result) { - future.complete(result); - } - }); - AppSearchResult<Bundle> bundleResult = getFutureOrThrow(future); - if (!bundleResult.isSuccess()) { - return AppSearchResult.newFailedResult( - bundleResult.getResultCode(), bundleResult.getErrorMessage()); - } - SearchResultPage searchResultPage = new SearchResultPage(bundleResult.getResultValue()); - return AppSearchResult.newSuccessfulResult(searchResultPage.getResults()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (Throwable t) { - return AppSearchResult.throwableToFailedResult(t); - } - } - - /** - * Removes {@link GenericDocument}s by URI. - * - * <p>You should not call this method directly; instead, use the {@code AppSearch#delete()} API - * provided by JetPack. - * - * @param request Request containing URIs to be removed. - * @return The pending result of performing this operation. The keys of the returned {@link - * AppSearchBatchResult} are the input URIs. The values are {@code null} on success, or a - * failed {@link AppSearchResult} otherwise. URIs that are not found will return a failed - * {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}. - * @throws RuntimeException If an error occurred during the execution. - * @hide - * @deprecated use {@link AppSearchSession#remove} instead. - */ - public AppSearchBatchResult<String, Void> removeByUri(@NonNull RemoveByUriRequest request) { - List<String> uris = new ArrayList<>(request.getUris()); - AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>(); - try { - mService.removeByUri( - getPackageName(), - DEFAULT_DATABASE_NAME, - request.getNamespace(), - uris, - mContext.getUserId(), - new IAppSearchBatchResultCallback.Stub() { - public void onResult(AppSearchBatchResult result) { - future.complete(result); - } - - public void onSystemError(ParcelableException exception) { - future.completeExceptionally(exception); - } - }); - } catch (RemoteException e) { - future.completeExceptionally(e); - } - return getFutureOrThrow(future); - } - /** Returns the package name that should be used for uid verification. */ @NonNull private String getPackageName() { return mContext.getOpPackageName(); } - - private static <T> T getFutureOrThrow(@NonNull AndroidFuture<T> future) { - try { - return future.get(); - } catch (Throwable e) { - if (e instanceof ExecutionException) { - e = e.getCause(); - } - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } - if (e instanceof Error) { - throw (Error) e; - } - throw new RuntimeException(e); - } - } } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java index 4b0f719b13be..999860fdf4da 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java @@ -42,7 +42,7 @@ public class BlobStoreIdleJobService extends JobService { blobStoreManagerInternal.onIdleMaintenance(); jobFinished(params, false); }); - return false; + return true; } @Override diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index b7a3f1083176..6967d819a448 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -59,6 +59,9 @@ import java.util.Objects; * constraint on the JobInfo object that you are creating. Otherwise, the builder would throw an * exception when building. From Android version {@link Build.VERSION_CODES#Q} and onwards, it is * valid to schedule jobs with no constraints. + * <p> Prior to Android version {@link Build.VERSION_CODES#S}, jobs could only have a maximum of 100 + * jobs scheduled at a time. Starting with Android version {@link Build.VERSION_CODES#S}, that limit + * has been increased to 150. Expedited jobs also count towards the limit. * <p> In Android version {@link Build.VERSION_CODES#LOLLIPOP}, jobs had a maximum execution time * of one minute. Starting with Android version {@link Build.VERSION_CODES#M} and ending with * Android version {@link Build.VERSION_CODES#R}, jobs had a maximum execution time of 10 minutes. diff --git a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java index 0c4fcb4ec1b0..6e4a5a0c5784 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java @@ -22,6 +22,7 @@ import android.app.SystemServiceRegistry; import android.content.Context; import android.os.DeviceIdleManager; import android.os.IDeviceIdleController; +import android.os.PowerExemptionManager; import android.os.PowerWhitelistManager; /** @@ -52,5 +53,8 @@ public class JobSchedulerFrameworkInitializer { SystemServiceRegistry.registerContextAwareService( Context.POWER_WHITELIST_MANAGER, PowerWhitelistManager.class, PowerWhitelistManager::new); + SystemServiceRegistry.registerContextAwareService( + Context.POWER_EXEMPTION_SERVICE, PowerExemptionManager.class, + PowerExemptionManager::new); } } diff --git a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java index 752c36e53bf9..6cdf5853339a 100644 --- a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java +++ b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java @@ -65,7 +65,7 @@ public class DeviceIdleManager { * @return package names the system has white-listed to opt out of power save restrictions, * except for device idle mode. * - * @hide Should be migrated to PowerWhitelistManager + * @hide Should be migrated to PowerExemptionManager */ @TestApi public @NonNull String[] getSystemPowerWhitelistExceptIdle() { @@ -80,7 +80,7 @@ public class DeviceIdleManager { * @return package names the system has white-listed to opt out of power save restrictions for * all modes. * - * @hide Should be migrated to PowerWhitelistManager + * @hide Should be migrated to PowerExemptionManager */ @TestApi public @NonNull String[] getSystemPowerWhitelist() { diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl index 43d4873a3540..9d18dfe98a34 100644 --- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl +++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl @@ -42,7 +42,7 @@ interface IDeviceIdleController { boolean isPowerSaveWhitelistExceptIdleApp(String name); boolean isPowerSaveWhitelistApp(String name); @UnsupportedAppUsage(maxTargetSdk = 30, - publicAlternatives = "Use SystemApi {@code PowerWhitelistManager#whitelistAppTemporarily(String, int, String)}.") + publicAlternatives = "Use SystemApi {@code PowerExemptionManager#addToTemporaryAllowList(String, int, int, String)}.") void addPowerSaveTempWhitelistApp(String name, long duration, int userId, int reasonCode, String reason); long addPowerSaveTempWhitelistAppForMms(String name, int userId, int reasonCode, String reason); long addPowerSaveTempWhitelistAppForSms(String name, int userId, int reasonCode, String reason); diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java new file mode 100644 index 000000000000..d9a49aa52365 --- /dev/null +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; +import static android.app.ActivityManager.PROCESS_STATE_TOP; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.List; + +/** + * Interface to access and modify the permanent and temporary power save allow list. The two lists + * are kept separately. Apps placed on the permanent allow list are only removed via an explicit + * {@link #removeFromAllowList(String)} call. Apps allow-listed by default by the system cannot be + * removed. Apps placed on the temporary allow list are removed from that allow list after a + * predetermined amount of time. + * + * @hide + */ +@SystemApi +@SystemService(Context.POWER_EXEMPTION_SERVICE) +public class PowerExemptionManager { + private final Context mContext; + // Proxy to DeviceIdleController for now + // TODO: migrate to PowerExemptionController + private final IDeviceIdleController mService; + + /** + * Indicates that an unforeseen event has occurred and the app should be allow-listed to handle + * it. + */ + public static final int EVENT_UNSPECIFIED = 0; + + /** + * Indicates that an SMS event has occurred and the app should be allow-listed to handle it. + */ + public static final int EVENT_SMS = 1; + + /** + * Indicates that an MMS event has occurred and the app should be allow-listed to handle it. + */ + public static final int EVENT_MMS = 2; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"EVENT_"}, value = { + EVENT_UNSPECIFIED, + EVENT_SMS, + EVENT_MMS, + }) + public @interface AllowListEvent { + } + + /** + * Allow the temp allow list behavior, plus allow foreground service start from background. + */ + public static final int TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; + /** + * Only allow the temp allow list behavior, not allow foreground service start from background. + */ + public static final int TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; + + /** + * The list of temp allow list types. + * @hide + */ + @IntDef(flag = true, prefix = { "TEMPORARY_ALLOW_LIST_TYPE_" }, value = { + TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TempAllowListType {} + + /* Reason codes for BG-FGS-launch. */ + /** + * BG-FGS-launch is denied. + * @hide + */ + public static final int REASON_DENIED = -1; + + /* Reason code range 0-9 are reserved for default reasons */ + /** + * The default reason code if reason is unknown. + */ + public static final int REASON_UNKNOWN = 0; + /** + * Use REASON_OTHER if there is no better choice. + */ + public static final int REASON_OTHER = 1; + + /* Reason code range 10-49 are reserved for BG-FGS-launch allowed proc states */ + /** @hide */ + public static final int REASON_PROC_STATE_PERSISTENT = 10; + /** @hide */ + public static final int REASON_PROC_STATE_PERSISTENT_UI = 11; + /** @hide */ + public static final int REASON_PROC_STATE_TOP = 12; + /** @hide */ + public static final int REASON_PROC_STATE_BTOP = 13; + /** @hide */ + public static final int REASON_PROC_STATE_FGS = 14; + /** @hide */ + public static final int REASON_PROC_STATE_BFGS = 15; + + /* Reason code range 50-99 are reserved for BG-FGS-launch allowed reasons */ + /** @hide */ + public static final int REASON_UID_VISIBLE = 50; + /** @hide */ + public static final int REASON_SYSTEM_UID = 51; + /** @hide */ + public static final int REASON_ACTIVITY_STARTER = 52; + /** @hide */ + public static final int REASON_START_ACTIVITY_FLAG = 53; + /** @hide */ + public static final int REASON_FGS_BINDING = 54; + /** @hide */ + public static final int REASON_DEVICE_OWNER = 55; + /** @hide */ + public static final int REASON_PROFILE_OWNER = 56; + /** @hide */ + public static final int REASON_COMPANION_DEVICE_MANAGER = 57; + /** + * START_ACTIVITIES_FROM_BACKGROUND permission. + * @hide + */ + public static final int REASON_BACKGROUND_ACTIVITY_PERMISSION = 58; + /** + * START_FOREGROUND_SERVICES_FROM_BACKGROUND permission. + * @hide + */ + public static final int REASON_BACKGROUND_FGS_PERMISSION = 59; + /** @hide */ + public static final int REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 60; + /** @hide */ + public static final int REASON_INSTR_BACKGROUND_FGS_PERMISSION = 61; + /** @hide */ + public static final int REASON_SYSTEM_ALERT_WINDOW_PERMISSION = 62; + /** @hide */ + public static final int REASON_DEVICE_DEMO_MODE = 63; + /** @hide */ + public static final int REASON_EXEMPTED_PACKAGE = 64; + /** @hide */ + public static final int REASON_ALLOWLISTED_PACKAGE = 65; + /** @hide */ + public static final int REASON_APPOP = 66; + + /* BG-FGS-launch is allowed by temp-allow-list or system-allow-list. + Reason code for temp and system allow list starts here. + Reason code range 100-199 are reserved for public reasons. */ + /** + * Set temp-allow-list for location geofence purpose. + */ + public static final int REASON_GEOFENCING = 100; + /** + * Set temp-allow-list for server push messaging. + */ + public static final int REASON_PUSH_MESSAGING = 101; + /** + * Set temp-allow-list for server push messaging over the quota. + */ + public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; + /** + * Set temp-allow-list for activity recognition. + */ + public static final int REASON_ACTIVITY_RECOGNITION = 103; + /** + * Set temp-allow-list for transferring accounts between users. + */ + public static final int REASON_ACCOUNT_TRANSFER = 104; + + /* Reason code range 200-299 are reserved for broadcast actions */ + /** + * Broadcast ACTION_BOOT_COMPLETED. + * @hide + */ + public static final int REASON_BOOT_COMPLETED = 200; + /** + * Broadcast ACTION_PRE_BOOT_COMPLETED. + * @hide + */ + public static final int REASON_PRE_BOOT_COMPLETED = 201; + /** + * Broadcast ACTION_LOCKED_BOOT_COMPLETED. + * @hide + */ + public static final int REASON_LOCKED_BOOT_COMPLETED = 202; + + /* Reason code range 300-399 are reserved for other internal reasons */ + /** + * Device idle system allow list, including EXCEPT-IDLE + * @hide + */ + public static final int REASON_SYSTEM_ALLOW_LISTED = 300; + /** @hide */ + public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 301; + /** + * AlarmManagerService. + * @hide + */ + public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 302; + /** + * ActiveServices. + * @hide + */ + public static final int REASON_SERVICE_LAUNCH = 303; + /** + * KeyChainSystemService. + * @hide + */ + public static final int REASON_KEY_CHAIN = 304; + /** + * PackageManagerService. + * @hide + */ + public static final int REASON_PACKAGE_VERIFIER = 305; + /** + * SyncManager. + * @hide + */ + public static final int REASON_SYNC_MANAGER = 306; + /** + * DomainVerificationProxyV1. + * @hide + */ + public static final int REASON_DOMAIN_VERIFICATION_V1 = 307; + /** + * DomainVerificationProxyV2. + * @hide + */ + public static final int REASON_DOMAIN_VERIFICATION_V2 = 308; + /** @hide */ + public static final int REASON_VPN = 309; + /** + * NotificationManagerService. + * @hide + */ + public static final int REASON_NOTIFICATION_SERVICE = 310; + /** + * Broadcast ACTION_MY_PACKAGE_REPLACED. + * @hide + */ + public static final int REASON_PACKAGE_REPLACED = 311; + /** + * LocationProviderManager. + * @hide + */ + public static final int REASON_LOCATION_PROVIDER = 312; + /** + * MediaButtonReceiver. + * @hide + */ + public static final int REASON_MEDIA_BUTTON = 313; + /** + * InboundSmsHandler. + * @hide + */ + public static final int REASON_EVENT_SMS = 314; + /** + * InboundSmsHandler. + * @hide + */ + public static final int REASON_EVENT_MMS = 315; + /** + * Shell app. + * @hide + */ + public static final int REASON_SHELL = 316; + + /** + * The list of BG-FGS-Launch and temp-allow-list reason code. + * @hide + */ + @IntDef(flag = true, prefix = { "REASON_" }, value = { + // BG-FGS-Launch reasons. + REASON_DENIED, + REASON_UNKNOWN, + REASON_OTHER, + REASON_PROC_STATE_PERSISTENT, + REASON_PROC_STATE_PERSISTENT_UI, + REASON_PROC_STATE_TOP, + REASON_PROC_STATE_BTOP, + REASON_PROC_STATE_FGS, + REASON_PROC_STATE_BFGS, + REASON_UID_VISIBLE, + REASON_SYSTEM_UID, + REASON_ACTIVITY_STARTER, + REASON_START_ACTIVITY_FLAG, + REASON_FGS_BINDING, + REASON_DEVICE_OWNER, + REASON_PROFILE_OWNER, + REASON_COMPANION_DEVICE_MANAGER, + REASON_BACKGROUND_ACTIVITY_PERMISSION, + REASON_BACKGROUND_FGS_PERMISSION, + REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION, + REASON_INSTR_BACKGROUND_FGS_PERMISSION, + REASON_SYSTEM_ALERT_WINDOW_PERMISSION, + REASON_DEVICE_DEMO_MODE, + REASON_EXEMPTED_PACKAGE, + REASON_ALLOWLISTED_PACKAGE, + REASON_APPOP, + // temp and system allow list reasons. + REASON_GEOFENCING, + REASON_PUSH_MESSAGING, + REASON_PUSH_MESSAGING_OVER_QUOTA, + REASON_ACTIVITY_RECOGNITION, + REASON_ACCOUNT_TRANSFER, + REASON_BOOT_COMPLETED, + REASON_PRE_BOOT_COMPLETED, + REASON_LOCKED_BOOT_COMPLETED, + REASON_SYSTEM_ALLOW_LISTED, + REASON_ALARM_MANAGER_ALARM_CLOCK, + REASON_ALARM_MANAGER_WHILE_IDLE, + REASON_SERVICE_LAUNCH, + REASON_KEY_CHAIN, + REASON_PACKAGE_VERIFIER, + REASON_SYNC_MANAGER, + REASON_DOMAIN_VERIFICATION_V1, + REASON_DOMAIN_VERIFICATION_V2, + REASON_VPN, + REASON_NOTIFICATION_SERVICE, + REASON_PACKAGE_REPLACED, + REASON_LOCATION_PROVIDER, + REASON_MEDIA_BUTTON, + REASON_EVENT_SMS, + REASON_EVENT_MMS, + REASON_SHELL, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ReasonCode {} + + /** + * @hide + */ + public PowerExemptionManager(@NonNull Context context) { + mContext = context; + mService = context.getSystemService(DeviceIdleManager.class).getService(); + } + + /** + * Add the specified package to the permanent power save allow list. + */ + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public void addToPermanentAllowList(@NonNull String packageName) { + addToPermanentAllowList(Collections.singletonList(packageName)); + } + + /** + * Add the specified packages to the permanent power save allow list. + */ + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public void addToPermanentAllowList(@NonNull List<String> packageNames) { + try { + mService.addPowerSaveWhitelistApps(packageNames); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get a list of app IDs of app that are allow-listed. This does not include temporarily + * allow-listed apps. + * + * @param includingIdle Set to true if the app should be allow-listed from device idle as well + * as other power save restrictions + * @hide + */ + @NonNull + public int[] getAllowListedAppIds(boolean includingIdle) { + try { + if (includingIdle) { + return mService.getAppIdWhitelist(); + } else { + return mService.getAppIdWhitelistExceptIdle(); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns true if the app is allow-listed from power save restrictions. This does not include + * temporarily allow-listed apps. + * + * @param includingIdle Set to true if the app should be allow-listed from device + * idle as well as other power save restrictions + * @hide + */ + public boolean isAllowListed(@NonNull String packageName, boolean includingIdle) { + try { + if (includingIdle) { + return mService.isPowerSaveWhitelistApp(packageName); + } else { + return mService.isPowerSaveWhitelistExceptIdleApp(packageName); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove an app from the permanent power save allow list. Only apps that were added via + * {@link #addToPermanentAllowList(String)} or {@link #addToPermanentAllowList(List)} will be + * removed. Apps allow-listed by default by the system cannot be removed. + * + * @param packageName The app to remove from the allow list + */ + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public void removeFromAllowList(@NonNull String packageName) { + try { + mService.removePowerSaveWhitelistApp(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Add an app to the temporary allow list for a short amount of time. + * + * @param packageName The package to add to the temp allow list + * @param durationMs How long to keep the app on the temp allow list for (in milliseconds) + * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure. + * @param reason a optional human readable reason string, could be null or empty string. + */ + @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) + public void addToTemporaryAllowList(@NonNull String packageName, long durationMs, + @ReasonCode int reasonCode, @Nullable String reason) { + try { + mService.addPowerSaveTempWhitelistApp(packageName, durationMs, mContext.getUserId(), + reasonCode, reason); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Add an app to the temporary allow list for a short amount of time for a specific reason. + * The temporary allow list is kept separately from the permanent allow list and apps are + * automatically removed from the temporary allow list after a predetermined amount of time. + * + * @param packageName The package to add to the temp allow list + * @param event The reason to add the app to the temp allow list + * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure. + * @param reason A human-readable reason explaining why the app is temp allow-listed. Only + * used for logging purposes. Could be null or empty string. + * @return The duration (in milliseconds) that the app is allow-listed for + */ + @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) + public long addToTemporaryAllowListForEvent(@NonNull String packageName, + @AllowListEvent int event, @ReasonCode int reasonCode, @Nullable String reason) { + try { + switch (event) { + case EVENT_MMS: + return mService.addPowerSaveTempWhitelistAppForMms( + packageName, mContext.getUserId(), reasonCode, reason); + case EVENT_SMS: + return mService.addPowerSaveTempWhitelistAppForSms( + packageName, mContext.getUserId(), reasonCode, reason); + case EVENT_UNSPECIFIED: + default: + return mService.whitelistAppTemporarily( + packageName, mContext.getUserId(), reasonCode, reason); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public static @ReasonCode int getReasonCodeFromProcState(int procState) { + if (procState <= PROCESS_STATE_PERSISTENT) { + return REASON_PROC_STATE_PERSISTENT; + } else if (procState <= PROCESS_STATE_PERSISTENT_UI) { + return REASON_PROC_STATE_PERSISTENT_UI; + } else if (procState <= PROCESS_STATE_TOP) { + return REASON_PROC_STATE_TOP; + } else if (procState <= PROCESS_STATE_BOUND_TOP) { + return REASON_PROC_STATE_BTOP; + } else if (procState <= PROCESS_STATE_FOREGROUND_SERVICE) { + return REASON_PROC_STATE_FGS; + } else if (procState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + return REASON_PROC_STATE_BFGS; + } else { + return REASON_DENIED; + } + } + + /** + * Return string name of the integer reason code. + * @hide + * @param reasonCode + * @return string name of the reason code. + */ + public static String reasonCodeToString(@ReasonCode int reasonCode) { + switch (reasonCode) { + case REASON_DENIED: + return "DENIED"; + case REASON_UNKNOWN: + return "UNKNOWN"; + case REASON_OTHER: + return "OTHER"; + case REASON_PROC_STATE_PERSISTENT: + return "PROC_STATE_PERSISTENT"; + case REASON_PROC_STATE_PERSISTENT_UI: + return "PROC_STATE_PERSISTENT_UI"; + case REASON_PROC_STATE_TOP: + return "PROC_STATE_TOP"; + case REASON_PROC_STATE_BTOP: + return "PROC_STATE_BTOP"; + case REASON_PROC_STATE_FGS: + return "PROC_STATE_FGS"; + case REASON_PROC_STATE_BFGS: + return "PROC_STATE_BFGS"; + case REASON_UID_VISIBLE: + return "UID_VISIBLE"; + case REASON_SYSTEM_UID: + return "SYSTEM_UID"; + case REASON_ACTIVITY_STARTER: + return "ACTIVITY_STARTER"; + case REASON_START_ACTIVITY_FLAG: + return "START_ACTIVITY_FLAG"; + case REASON_FGS_BINDING: + return "FGS_BINDING"; + case REASON_DEVICE_OWNER: + return "DEVICE_OWNER"; + case REASON_PROFILE_OWNER: + return "PROFILE_OWNER"; + case REASON_COMPANION_DEVICE_MANAGER: + return "COMPANION_DEVICE_MANAGER"; + case REASON_BACKGROUND_ACTIVITY_PERMISSION: + return "BACKGROUND_ACTIVITY_PERMISSION"; + case REASON_BACKGROUND_FGS_PERMISSION: + return "BACKGROUND_FGS_PERMISSION"; + case REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION: + return "INSTR_BACKGROUND_ACTIVITY_PERMISSION"; + case REASON_INSTR_BACKGROUND_FGS_PERMISSION: + return "INSTR_BACKGROUND_FGS_PERMISSION"; + case REASON_SYSTEM_ALERT_WINDOW_PERMISSION: + return "SYSTEM_ALERT_WINDOW_PERMISSION"; + case REASON_DEVICE_DEMO_MODE: + return "DEVICE_DEMO_MODE"; + case REASON_EXEMPTED_PACKAGE: + return "EXEMPTED_PACKAGE"; + case REASON_ALLOWLISTED_PACKAGE: + return "ALLOWLISTED_PACKAGE"; + case REASON_APPOP: + return "APPOP"; + case REASON_GEOFENCING: + return "GEOFENCING"; + case REASON_PUSH_MESSAGING: + return "PUSH_MESSAGING"; + case REASON_PUSH_MESSAGING_OVER_QUOTA: + return "PUSH_MESSAGING_OVER_QUOTA"; + case REASON_ACTIVITY_RECOGNITION: + return "ACTIVITY_RECOGNITION"; + case REASON_ACCOUNT_TRANSFER: + return "REASON_ACCOUNT_TRANSFER"; + case REASON_BOOT_COMPLETED: + return "BOOT_COMPLETED"; + case REASON_PRE_BOOT_COMPLETED: + return "PRE_BOOT_COMPLETED"; + case REASON_LOCKED_BOOT_COMPLETED: + return "LOCKED_BOOT_COMPLETED"; + case REASON_SYSTEM_ALLOW_LISTED: + return "SYSTEM_ALLOW_LISTED"; + case REASON_ALARM_MANAGER_ALARM_CLOCK: + return "ALARM_MANAGER_ALARM_CLOCK"; + case REASON_ALARM_MANAGER_WHILE_IDLE: + return "ALARM_MANAGER_WHILE_IDLE"; + case REASON_SERVICE_LAUNCH: + return "SERVICE_LAUNCH"; + case REASON_KEY_CHAIN: + return "KEY_CHAIN"; + case REASON_PACKAGE_VERIFIER: + return "PACKAGE_VERIFIER"; + case REASON_SYNC_MANAGER: + return "SYNC_MANAGER"; + case REASON_DOMAIN_VERIFICATION_V1: + return "DOMAIN_VERIFICATION_V1"; + case REASON_DOMAIN_VERIFICATION_V2: + return "DOMAIN_VERIFICATION_V2"; + case REASON_VPN: + return "VPN"; + case REASON_NOTIFICATION_SERVICE: + return "NOTIFICATION_SERVICE"; + case REASON_PACKAGE_REPLACED: + return "PACKAGE_REPLACED"; + case REASON_LOCATION_PROVIDER: + return "LOCATION_PROVIDER"; + case REASON_MEDIA_BUTTON: + return "MEDIA_BUTTON"; + case REASON_EVENT_SMS: + return "EVENT_SMS"; + case REASON_EVENT_MMS: + return "EVENT_MMS"; + case REASON_SHELL: + return "SHELL"; + default: + return "(unknown:" + reasonCode + ")"; + } + } +} diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java index b1b733a599c6..eba39c7573be 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java @@ -16,13 +16,6 @@ package android.os; -import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; -import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; -import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; -import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT; -import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; -import static android.app.ActivityManager.PROCESS_STATE_TOP; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -33,7 +26,6 @@ import android.content.Context; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Collections; import java.util.List; /** @@ -43,9 +35,11 @@ import java.util.List; * placed on the temporary whitelist are removed from that whitelist after a predetermined amount of * time. * + * @deprecated Use {@link PowerExemptionManager} instead * @hide */ @SystemApi +@Deprecated @SystemService(Context.POWER_WHITELIST_MANAGER) public class PowerWhitelistManager { private final Context mContext; @@ -53,21 +47,23 @@ public class PowerWhitelistManager { // TODO: migrate to PowerWhitelistController private final IDeviceIdleController mService; + private final PowerExemptionManager mPowerExemptionManager; + /** * Indicates that an unforeseen event has occurred and the app should be whitelisted to handle * it. */ - public static final int EVENT_UNSPECIFIED = 0; + public static final int EVENT_UNSPECIFIED = PowerExemptionManager.EVENT_UNSPECIFIED; /** * Indicates that an SMS event has occurred and the app should be whitelisted to handle it. */ - public static final int EVENT_SMS = 1; + public static final int EVENT_SMS = PowerExemptionManager.EVENT_SMS; /** * Indicates that an MMS event has occurred and the app should be whitelisted to handle it. */ - public static final int EVENT_MMS = 2; + public static final int EVENT_MMS = PowerExemptionManager.EVENT_MMS; /** * @hide @@ -84,12 +80,14 @@ public class PowerWhitelistManager { /** * Allow the temp allowlist behavior, plus allow foreground service start from background. */ - public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; + public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = + PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; /** * Only allow the temp allowlist behavior, not allow foreground service start from * background. */ - public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; + public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = + PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; /** * The list of temp allowlist types. @@ -107,73 +105,83 @@ public class PowerWhitelistManager { * BG-FGS-launch is denied. * @hide */ - public static final int REASON_DENIED = -1; + public static final int REASON_DENIED = PowerExemptionManager.REASON_DENIED; /* Reason code range 0-9 are reserved for default reasons */ /** * The default reason code if reason is unknown. */ - public static final int REASON_UNKNOWN = 0; + public static final int REASON_UNKNOWN = PowerExemptionManager.REASON_UNKNOWN; /** * Use REASON_OTHER if there is no better choice. */ - public static final int REASON_OTHER = 1; + public static final int REASON_OTHER = PowerExemptionManager.REASON_OTHER; /* Reason code range 10-49 are reserved for BG-FGS-launch allowed proc states */ /** @hide */ - public static final int REASON_PROC_STATE_PERSISTENT = 10; + public static final int REASON_PROC_STATE_PERSISTENT = + PowerExemptionManager.REASON_PROC_STATE_PERSISTENT; /** @hide */ - public static final int REASON_PROC_STATE_PERSISTENT_UI = 11; + public static final int REASON_PROC_STATE_PERSISTENT_UI = + PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI; /** @hide */ - public static final int REASON_PROC_STATE_TOP = 12; + public static final int REASON_PROC_STATE_TOP = PowerExemptionManager.REASON_PROC_STATE_TOP; /** @hide */ - public static final int REASON_PROC_STATE_BTOP = 13; + public static final int REASON_PROC_STATE_BTOP = PowerExemptionManager.REASON_PROC_STATE_BTOP; /** @hide */ - public static final int REASON_PROC_STATE_FGS = 14; + public static final int REASON_PROC_STATE_FGS = PowerExemptionManager.REASON_PROC_STATE_FGS; /** @hide */ - public static final int REASON_PROC_STATE_BFGS = 15; + public static final int REASON_PROC_STATE_BFGS = PowerExemptionManager.REASON_PROC_STATE_BFGS; /* Reason code range 50-99 are reserved for BG-FGS-launch allowed reasons */ /** @hide */ - public static final int REASON_UID_VISIBLE = 50; + public static final int REASON_UID_VISIBLE = PowerExemptionManager.REASON_UID_VISIBLE; /** @hide */ - public static final int REASON_SYSTEM_UID = 51; + public static final int REASON_SYSTEM_UID = PowerExemptionManager.REASON_SYSTEM_UID; /** @hide */ - public static final int REASON_ACTIVITY_STARTER = 52; + public static final int REASON_ACTIVITY_STARTER = PowerExemptionManager.REASON_ACTIVITY_STARTER; /** @hide */ - public static final int REASON_START_ACTIVITY_FLAG = 53; + public static final int REASON_START_ACTIVITY_FLAG = + PowerExemptionManager.REASON_START_ACTIVITY_FLAG; /** @hide */ - public static final int REASON_FGS_BINDING = 54; + public static final int REASON_FGS_BINDING = PowerExemptionManager.REASON_FGS_BINDING; /** @hide */ - public static final int REASON_DEVICE_OWNER = 55; + public static final int REASON_DEVICE_OWNER = PowerExemptionManager.REASON_DEVICE_OWNER; /** @hide */ - public static final int REASON_PROFILE_OWNER = 56; + public static final int REASON_PROFILE_OWNER = PowerExemptionManager.REASON_PROFILE_OWNER; /** @hide */ - public static final int REASON_COMPANION_DEVICE_MANAGER = 57; + public static final int REASON_COMPANION_DEVICE_MANAGER = + PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER; /** * START_ACTIVITIES_FROM_BACKGROUND permission. * @hide */ - public static final int REASON_BACKGROUND_ACTIVITY_PERMISSION = 58; + public static final int REASON_BACKGROUND_ACTIVITY_PERMISSION = + PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION; /** * START_FOREGROUND_SERVICES_FROM_BACKGROUND permission. * @hide */ - public static final int REASON_BACKGROUND_FGS_PERMISSION = 59; + public static final int REASON_BACKGROUND_FGS_PERMISSION = + PowerExemptionManager.REASON_BACKGROUND_FGS_PERMISSION; /** @hide */ - public static final int REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 60; + public static final int REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION = + PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION; /** @hide */ - public static final int REASON_INSTR_BACKGROUND_FGS_PERMISSION = 61; + public static final int REASON_INSTR_BACKGROUND_FGS_PERMISSION = + PowerExemptionManager.REASON_INSTR_BACKGROUND_FGS_PERMISSION; /** @hide */ - public static final int REASON_SYSTEM_ALERT_WINDOW_PERMISSION = 62; + public static final int REASON_SYSTEM_ALERT_WINDOW_PERMISSION = + PowerExemptionManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION; /** @hide */ - public static final int REASON_DEVICE_DEMO_MODE = 63; + public static final int REASON_DEVICE_DEMO_MODE = PowerExemptionManager.REASON_DEVICE_DEMO_MODE; /** @hide */ - public static final int REASON_EXEMPTED_PACKAGE = 64; + public static final int REASON_EXEMPTED_PACKAGE = PowerExemptionManager.REASON_EXEMPTED_PACKAGE; /** @hide */ - public static final int REASON_ALLOWLISTED_PACKAGE = 65; + public static final int REASON_ALLOWLISTED_PACKAGE = + PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE; /** @hide */ - public static final int REASON_APPOP = 66; + public static final int REASON_APPOP = PowerExemptionManager.REASON_APPOP; /* BG-FGS-launch is allowed by temp-allowlist or system-allowlist. Reason code for temp and system allowlist starts here. @@ -181,117 +189,128 @@ public class PowerWhitelistManager { /** * Set temp-allowlist for location geofence purpose. */ - public static final int REASON_GEOFENCING = 100; + public static final int REASON_GEOFENCING = PowerExemptionManager.REASON_GEOFENCING; /** * Set temp-allowlist for server push messaging. */ - public static final int REASON_PUSH_MESSAGING = 101; + public static final int REASON_PUSH_MESSAGING = PowerExemptionManager.REASON_PUSH_MESSAGING; /** * Set temp-allowlist for server push messaging over the quota. */ - public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; + public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = + PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA; /** * Set temp-allowlist for activity recognition. */ - public static final int REASON_ACTIVITY_RECOGNITION = 103; + public static final int REASON_ACTIVITY_RECOGNITION = + PowerExemptionManager.REASON_ACTIVITY_RECOGNITION; /* Reason code range 200-299 are reserved for broadcast actions */ /** * Broadcast ACTION_BOOT_COMPLETED. * @hide */ - public static final int REASON_BOOT_COMPLETED = 200; + public static final int REASON_BOOT_COMPLETED = PowerExemptionManager.REASON_BOOT_COMPLETED; /** * Broadcast ACTION_PRE_BOOT_COMPLETED. * @hide */ - public static final int REASON_PRE_BOOT_COMPLETED = 201; + public static final int REASON_PRE_BOOT_COMPLETED = + PowerExemptionManager.REASON_PRE_BOOT_COMPLETED; /** * Broadcast ACTION_LOCKED_BOOT_COMPLETED. * @hide */ - public static final int REASON_LOCKED_BOOT_COMPLETED = 202; + public static final int REASON_LOCKED_BOOT_COMPLETED = + PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED; /* Reason code range 300-399 are reserved for other internal reasons */ /** * Device idle system allowlist, including EXCEPT-IDLE * @hide */ - public static final int REASON_SYSTEM_ALLOW_LISTED = 300; + public static final int REASON_SYSTEM_ALLOW_LISTED = + PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED; /** @hide */ - public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 301; + public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = + PowerExemptionManager.REASON_ALARM_MANAGER_ALARM_CLOCK; /** * AlarmManagerService. * @hide */ - public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 302; + public static final int REASON_ALARM_MANAGER_WHILE_IDLE = + PowerExemptionManager.REASON_ALARM_MANAGER_WHILE_IDLE; /** * ActiveServices. * @hide */ - public static final int REASON_SERVICE_LAUNCH = 303; + public static final int REASON_SERVICE_LAUNCH = PowerExemptionManager.REASON_SERVICE_LAUNCH; /** * KeyChainSystemService. * @hide */ - public static final int REASON_KEY_CHAIN = 304; + public static final int REASON_KEY_CHAIN = PowerExemptionManager.REASON_KEY_CHAIN; /** * PackageManagerService. * @hide */ - public static final int REASON_PACKAGE_VERIFIER = 305; + public static final int REASON_PACKAGE_VERIFIER = PowerExemptionManager.REASON_PACKAGE_VERIFIER; /** * SyncManager. * @hide */ - public static final int REASON_SYNC_MANAGER = 306; + public static final int REASON_SYNC_MANAGER = PowerExemptionManager.REASON_SYNC_MANAGER; /** * DomainVerificationProxyV1. * @hide */ - public static final int REASON_DOMAIN_VERIFICATION_V1 = 307; + public static final int REASON_DOMAIN_VERIFICATION_V1 = + PowerExemptionManager.REASON_DOMAIN_VERIFICATION_V1; /** * DomainVerificationProxyV2. * @hide */ - public static final int REASON_DOMAIN_VERIFICATION_V2 = 308; + public static final int REASON_DOMAIN_VERIFICATION_V2 = + PowerExemptionManager.REASON_DOMAIN_VERIFICATION_V2; /** @hide */ public static final int REASON_VPN = 309; /** * NotificationManagerService. * @hide */ - public static final int REASON_NOTIFICATION_SERVICE = 310; + public static final int REASON_NOTIFICATION_SERVICE = + PowerExemptionManager.REASON_NOTIFICATION_SERVICE; /** * Broadcast ACTION_MY_PACKAGE_REPLACED. * @hide */ - public static final int REASON_PACKAGE_REPLACED = 311; + public static final int REASON_PACKAGE_REPLACED = PowerExemptionManager.REASON_PACKAGE_REPLACED; /** * LocationProviderManager. * @hide */ - public static final int REASON_LOCATION_PROVIDER = 312; + public static final int REASON_LOCATION_PROVIDER = + PowerExemptionManager.REASON_LOCATION_PROVIDER; /** * MediaButtonReceiver. * @hide */ - public static final int REASON_MEDIA_BUTTON = 313; + public static final int REASON_MEDIA_BUTTON = PowerExemptionManager.REASON_MEDIA_BUTTON; /** * InboundSmsHandler. * @hide */ - public static final int REASON_EVENT_SMS = 314; + public static final int REASON_EVENT_SMS = PowerExemptionManager.REASON_EVENT_SMS; /** * InboundSmsHandler. * @hide */ - public static final int REASON_EVENT_MMS = 315; + public static final int REASON_EVENT_MMS = PowerExemptionManager.REASON_EVENT_MMS; /** * Shell app. * @hide */ - public static final int REASON_SHELL = 316; + public static final int REASON_SHELL = PowerExemptionManager.REASON_SHELL; /** * The list of BG-FGS-Launch and temp-allowlist reason code. @@ -360,26 +379,29 @@ public class PowerWhitelistManager { public PowerWhitelistManager(@NonNull Context context) { mContext = context; mService = context.getSystemService(DeviceIdleManager.class).getService(); + mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class); } /** * Add the specified package to the permanent power save whitelist. + * + * @deprecated Use {@link PowerExemptionManager#addToPermanentAllowList(String)} instead */ + @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String packageName) { - addToWhitelist(Collections.singletonList(packageName)); + mPowerExemptionManager.addToPermanentAllowList(packageName); } /** * Add the specified packages to the permanent power save whitelist. + * + * @deprecated Use {@link PowerExemptionManager#addToPermanentAllowList(List)} instead */ + @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull List<String> packageNames) { - try { - mService.addPowerSaveWhitelistApps(packageNames); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mPowerExemptionManager.addToPermanentAllowList(packageNames); } /** @@ -388,19 +410,13 @@ public class PowerWhitelistManager { * * @param includingIdle Set to true if the app should be whitelisted from device idle as well * as other power save restrictions + * @deprecated Use {@link PowerExemptionManager#getAllowListedAppIds(boolean)} instead * @hide */ + @Deprecated @NonNull public int[] getWhitelistedAppIds(boolean includingIdle) { - try { - if (includingIdle) { - return mService.getAppIdWhitelist(); - } else { - return mService.getAppIdWhitelistExceptIdle(); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return mPowerExemptionManager.getAllowListedAppIds(includingIdle); } /** @@ -409,18 +425,12 @@ public class PowerWhitelistManager { * * @param includingIdle Set to true if the app should be whitelisted from device * idle as well as other power save restrictions + * @deprecated Use {@link PowerExemptionManager#isAllowListed(String, boolean)} instead * @hide */ + @Deprecated public boolean isWhitelisted(@NonNull String packageName, boolean includingIdle) { - try { - if (includingIdle) { - return mService.isPowerSaveWhitelistApp(packageName); - } else { - return mService.isPowerSaveWhitelistExceptIdleApp(packageName); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return mPowerExemptionManager.isAllowListed(packageName, includingIdle); } /** @@ -429,14 +439,12 @@ public class PowerWhitelistManager { * whitelisted by default by the system cannot be removed. * * @param packageName The app to remove from the whitelist + * @deprecated Use {@link PowerExemptionManager#removeFromAllowList(String)} instead */ + @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromWhitelist(@NonNull String packageName) { - try { - mService.removePowerSaveWhitelistApp(packageName); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mPowerExemptionManager.removeFromAllowList(packageName); } /** @@ -446,16 +454,14 @@ public class PowerWhitelistManager { * @param durationMs How long to keep the app on the temp whitelist for (in milliseconds) * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure. * @param reason a optional human readable reason string, could be null or empty string. + * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList( + * String, long, int, String)} instead */ + @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String packageName, long durationMs, @ReasonCode int reasonCode, @Nullable String reason) { - try { - mService.addPowerSaveTempWhitelistApp(packageName, durationMs, mContext.getUserId(), - reasonCode, reason); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mPowerExemptionManager.addToTemporaryAllowList(packageName, durationMs, reasonCode, reason); } /** @@ -463,12 +469,14 @@ public class PowerWhitelistManager { * * @param packageName The package to add to the temp whitelist * @param durationMs How long to keep the app on the temp whitelist for (in milliseconds) - * @deprecated Use {@link #whitelistAppTemporarily(String, long, int, String)} instead + * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList( + * String, long, int, String)} instead */ @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String packageName, long durationMs) { - whitelistAppTemporarily(packageName, durationMs, REASON_UNKNOWN, packageName); + mPowerExemptionManager.addToTemporaryAllowList( + packageName, durationMs, REASON_UNKNOWN, packageName); } /** @@ -481,13 +489,15 @@ public class PowerWhitelistManager { * @param reason A human-readable reason explaining why the app is temp whitelisted. Only * used for logging purposes. Could be null or empty string. * @return The duration (in milliseconds) that the app is whitelisted for - * @deprecated Use {@link #whitelistAppTemporarilyForEvent(String, int, int, String)} instead + * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent( + * String, int, int, String)} instead */ @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String packageName, @WhitelistEvent int event, @Nullable String reason) { - return whitelistAppTemporarilyForEvent(packageName, event, REASON_UNKNOWN, reason); + return mPowerExemptionManager.addToTemporaryAllowListForEvent( + packageName, event, REASON_UNKNOWN, reason); } /** @@ -501,47 +511,25 @@ public class PowerWhitelistManager { * @param reason A human-readable reason explaining why the app is temp whitelisted. Only * used for logging purposes. Could be null or empty string. * @return The duration (in milliseconds) that the app is whitelisted for + * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent( + * String, int, int, String)} instead */ + @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String packageName, @WhitelistEvent int event, @ReasonCode int reasonCode, @Nullable String reason) { - try { - switch (event) { - case EVENT_MMS: - return mService.addPowerSaveTempWhitelistAppForMms( - packageName, mContext.getUserId(), reasonCode, reason); - case EVENT_SMS: - return mService.addPowerSaveTempWhitelistAppForSms( - packageName, mContext.getUserId(), reasonCode, reason); - case EVENT_UNSPECIFIED: - default: - return mService.whitelistAppTemporarily( - packageName, mContext.getUserId(), reasonCode, reason); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return mPowerExemptionManager.addToTemporaryAllowListForEvent( + packageName, event, reasonCode, reason); } /** * @hide + * + * @deprecated Use {@link PowerExemptionManager#getReasonCodeFromProcState(int)} instead */ + @Deprecated public static @ReasonCode int getReasonCodeFromProcState(int procState) { - if (procState <= PROCESS_STATE_PERSISTENT) { - return REASON_PROC_STATE_PERSISTENT; - } else if (procState <= PROCESS_STATE_PERSISTENT_UI) { - return REASON_PROC_STATE_PERSISTENT_UI; - } else if (procState <= PROCESS_STATE_TOP) { - return REASON_PROC_STATE_TOP; - } else if (procState <= PROCESS_STATE_BOUND_TOP) { - return REASON_PROC_STATE_BTOP; - } else if (procState <= PROCESS_STATE_FOREGROUND_SERVICE) { - return REASON_PROC_STATE_FGS; - } else if (procState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { - return REASON_PROC_STATE_BFGS; - } else { - return REASON_DENIED; - } + return PowerExemptionManager.getReasonCodeFromProcState(procState); } /** @@ -549,111 +537,10 @@ public class PowerWhitelistManager { * @hide * @param reasonCode * @return string name of the reason code. + * @deprecated Use {@link PowerExemptionManager#reasonCodeToString(int)} instead */ + @Deprecated public static String reasonCodeToString(@ReasonCode int reasonCode) { - switch (reasonCode) { - case REASON_DENIED: - return "DENIED"; - case REASON_UNKNOWN: - return "UNKNOWN"; - case REASON_OTHER: - return "OTHER"; - case REASON_PROC_STATE_PERSISTENT: - return "PROC_STATE_PERSISTENT"; - case REASON_PROC_STATE_PERSISTENT_UI: - return "PROC_STATE_PERSISTENT_UI"; - case REASON_PROC_STATE_TOP: - return "PROC_STATE_TOP"; - case REASON_PROC_STATE_BTOP: - return "PROC_STATE_BTOP"; - case REASON_PROC_STATE_FGS: - return "PROC_STATE_FGS"; - case REASON_PROC_STATE_BFGS: - return "PROC_STATE_BFGS"; - case REASON_UID_VISIBLE: - return "UID_VISIBLE"; - case REASON_SYSTEM_UID: - return "SYSTEM_UID"; - case REASON_ACTIVITY_STARTER: - return "ACTIVITY_STARTER"; - case REASON_START_ACTIVITY_FLAG: - return "START_ACTIVITY_FLAG"; - case REASON_FGS_BINDING: - return "FGS_BINDING"; - case REASON_DEVICE_OWNER: - return "DEVICE_OWNER"; - case REASON_PROFILE_OWNER: - return "PROFILE_OWNER"; - case REASON_COMPANION_DEVICE_MANAGER: - return "COMPANION_DEVICE_MANAGER"; - case REASON_BACKGROUND_ACTIVITY_PERMISSION: - return "BACKGROUND_ACTIVITY_PERMISSION"; - case REASON_BACKGROUND_FGS_PERMISSION: - return "BACKGROUND_FGS_PERMISSION"; - case REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION: - return "INSTR_BACKGROUND_ACTIVITY_PERMISSION"; - case REASON_INSTR_BACKGROUND_FGS_PERMISSION: - return "INSTR_BACKGROUND_FGS_PERMISSION"; - case REASON_SYSTEM_ALERT_WINDOW_PERMISSION: - return "SYSTEM_ALERT_WINDOW_PERMISSION"; - case REASON_DEVICE_DEMO_MODE: - return "DEVICE_DEMO_MODE"; - case REASON_EXEMPTED_PACKAGE: - return "EXEMPTED_PACKAGE"; - case REASON_ALLOWLISTED_PACKAGE: - return "ALLOWLISTED_PACKAGE"; - case REASON_APPOP: - return "APPOP"; - case REASON_GEOFENCING: - return "GEOFENCING"; - case REASON_PUSH_MESSAGING: - return "PUSH_MESSAGING"; - case REASON_PUSH_MESSAGING_OVER_QUOTA: - return "PUSH_MESSAGING_OVER_QUOTA"; - case REASON_ACTIVITY_RECOGNITION: - return "ACTIVITY_RECOGNITION"; - case REASON_BOOT_COMPLETED: - return "BOOT_COMPLETED"; - case REASON_PRE_BOOT_COMPLETED: - return "PRE_BOOT_COMPLETED"; - case REASON_LOCKED_BOOT_COMPLETED: - return "LOCKED_BOOT_COMPLETED"; - case REASON_SYSTEM_ALLOW_LISTED: - return "SYSTEM_ALLOW_LISTED"; - case REASON_ALARM_MANAGER_ALARM_CLOCK: - return "ALARM_MANAGER_ALARM_CLOCK"; - case REASON_ALARM_MANAGER_WHILE_IDLE: - return "ALARM_MANAGER_WHILE_IDLE"; - case REASON_SERVICE_LAUNCH: - return "SERVICE_LAUNCH"; - case REASON_KEY_CHAIN: - return "KEY_CHAIN"; - case REASON_PACKAGE_VERIFIER: - return "PACKAGE_VERIFIER"; - case REASON_SYNC_MANAGER: - return "SYNC_MANAGER"; - case REASON_DOMAIN_VERIFICATION_V1: - return "DOMAIN_VERIFICATION_V1"; - case REASON_DOMAIN_VERIFICATION_V2: - return "DOMAIN_VERIFICATION_V2"; - case REASON_VPN: - return "VPN"; - case REASON_NOTIFICATION_SERVICE: - return "NOTIFICATION_SERVICE"; - case REASON_PACKAGE_REPLACED: - return "PACKAGE_REPLACED"; - case REASON_LOCATION_PROVIDER: - return "LOCATION_PROVIDER"; - case REASON_MEDIA_BUTTON: - return "MEDIA_BUTTON"; - case REASON_EVENT_SMS: - return "EVENT_SMS"; - case REASON_EVENT_MMS: - return "EVENT_MMS"; - case REASON_SHELL: - return "SHELL"; - default: - return "(unknown:" + reasonCode + ")"; - } + return PowerExemptionManager.reasonCodeToString(reasonCode); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 515cb747a99e..82f2f69bbde5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -147,10 +147,8 @@ public class JobSchedulerService extends com.android.server.SystemService /** The maximum number of concurrent jobs we run at one time. */ static final int MAX_JOB_CONTEXTS_COUNT = 16; - /** Enforce a per-app limit on scheduled jobs? */ - private static final boolean ENFORCE_MAX_JOBS = true; - /** The maximum number of jobs that we allow an unprivileged app to schedule */ - private static final int MAX_JOBS_PER_APP = 100; + /** The maximum number of jobs that we allow an app to schedule */ + private static final int MAX_JOBS_PER_APP = 150; /** The number of the most recently completed jobs to keep track of for debugging purposes. */ private static final int NUM_COMPLETED_JOB_HISTORY = 20; @@ -1011,7 +1009,7 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString()); // Jobs on behalf of others don't apply to the per-app job cap - if (ENFORCE_MAX_JOBS && packageName == null) { + if (packageName == null) { if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) { Slog.w(TAG, "Too many jobs for uid " + uId); throw new IllegalStateException("Apps may not schedule more than " diff --git a/api/Android.bp b/api/Android.bp index 1d4698e7c512..1fdf1771bb13 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -347,3 +347,49 @@ genrule { out: ["combined-removed-dex.txt"], cmd: "$(location gen_combined_removed_dex.sh) $(location metalava) $(genDir) $(in) > $(out)", } + +genrule { + name: "services-system-server-current.txt", + srcs: [ + ":service-permission{.system-server.api.txt}", + ":non-updatable-system-server-current.txt", + ], + out: ["system-server-current.txt"], + tools: ["metalava"], + cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)", + dists: [ + { + targets: ["droidcore"], + dir: "api", + dest: "system-server-current.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "merge-android.txt", + }, + ], +} + +genrule { + name: "services-system-server-removed.txt", + srcs: [ + ":service-permission{.system-server.removed-api.txt}", + ":non-updatable-system-server-removed.txt", + ], + out: ["system-server-removed.txt"], + tools: ["metalava"], + cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)", + dists: [ + { + targets: ["droidcore"], + dir: "api", + dest: "system-server-removed.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "merge-removed.txt", + }, + ], +} diff --git a/config/preloaded-classes b/config/preloaded-classes index c6ec37690d88..7c3fd8ce8aea 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -1844,11 +1844,9 @@ android.database.sqlite.SqliteWrapper android.ddm.DdmHandleAppName$Names android.ddm.DdmHandleAppName android.ddm.DdmHandleExit -android.ddm.DdmHandleHeap android.ddm.DdmHandleHello android.ddm.DdmHandleNativeHeap android.ddm.DdmHandleProfiling -android.ddm.DdmHandleThread android.ddm.DdmHandleViewDebug android.ddm.DdmRegister android.debug.AdbManager diff --git a/core/api/current.txt b/core/api/current.txt index ece1ce64d928..9f5255b9de98 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -329,6 +329,7 @@ package android { field public static final int apiKey = 16843281; // 0x1010211 field public static final int appCategory = 16844101; // 0x1010545 field public static final int appComponentFactory = 16844154; // 0x101057a + field public static final int attributionTags = 16844353; // 0x1010641 field public static final int author = 16843444; // 0x10102b4 field public static final int authorities = 16842776; // 0x1010018 field public static final int autoAdvanceViewId = 16843535; // 0x101030f @@ -506,7 +507,7 @@ package android { field public static final int dashGap = 16843175; // 0x10101a7 field public static final int dashWidth = 16843174; // 0x10101a6 field public static final int data = 16842798; // 0x101002e - field public static final int dataExtractionRules = 16844350; // 0x101063e + field public static final int dataExtractionRules = 16844349; // 0x101063d field public static final int datePickerDialogTheme = 16843948; // 0x10104ac field public static final int datePickerMode = 16843955; // 0x10104b3 field public static final int datePickerStyle = 16843612; // 0x101035c @@ -528,8 +529,8 @@ package android { field public static final int detailSocialSummary = 16843428; // 0x10102a4 field public static final int detailsElementBackground = 16843598; // 0x101034e field public static final int dial = 16843010; // 0x1010102 - field public static final int dialTint = 16844342; // 0x1010636 - field public static final int dialTintMode = 16844343; // 0x1010637 + field public static final int dialTint = 16844341; // 0x1010635 + field public static final int dialTintMode = 16844342; // 0x1010636 field public static final int dialogCornerRadius = 16844145; // 0x1010571 field public static final int dialogIcon = 16843252; // 0x10101f4 field public static final int dialogLayout = 16843255; // 0x10101f7 @@ -727,14 +728,14 @@ package android { field public static final int groupIndicator = 16843019; // 0x101010b field public static final int gwpAsanMode = 16844310; // 0x1010616 field public static final int hand_hour = 16843011; // 0x1010103 - field public static final int hand_hourTint = 16844344; // 0x1010638 - field public static final int hand_hourTintMode = 16844345; // 0x1010639 + field public static final int hand_hourTint = 16844343; // 0x1010637 + field public static final int hand_hourTintMode = 16844344; // 0x1010638 field public static final int hand_minute = 16843012; // 0x1010104 - field public static final int hand_minuteTint = 16844346; // 0x101063a - field public static final int hand_minuteTintMode = 16844347; // 0x101063b + field public static final int hand_minuteTint = 16844345; // 0x1010639 + field public static final int hand_minuteTintMode = 16844346; // 0x101063a field public static final int hand_second = 16844323; // 0x1010623 - field public static final int hand_secondTint = 16844348; // 0x101063c - field public static final int hand_secondTintMode = 16844349; // 0x101063d + field public static final int hand_secondTint = 16844347; // 0x101063b + field public static final int hand_secondTintMode = 16844348; // 0x101063c field public static final int handle = 16843354; // 0x101025a field public static final int handleProfiling = 16842786; // 0x1010022 field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e @@ -818,7 +819,7 @@ package android { field public static final int installLocation = 16843447; // 0x10102b7 field public static final int interactiveUiTimeout = 16844181; // 0x1010595 field public static final int interpolator = 16843073; // 0x1010141 - field public static final int isAccessibilityTool = 16844353; // 0x1010641 + field public static final int isAccessibilityTool = 16844352; // 0x1010640 field public static final int isAlwaysSyncable = 16843571; // 0x1010333 field public static final int isAsciiCapable = 16843753; // 0x10103e9 field public static final int isAuxiliary = 16843647; // 0x101037f @@ -970,8 +971,8 @@ package android { field public static final int maxLines = 16843091; // 0x1010153 field public static final int maxLongVersionCode = 16844163; // 0x1010583 field public static final int maxRecents = 16843846; // 0x1010446 - field public static final int maxResizeHeight = 16844339; // 0x1010633 - field public static final int maxResizeWidth = 16844338; // 0x1010632 + field public static final int maxResizeHeight = 16844338; // 0x1010632 + field public static final int maxResizeWidth = 16844337; // 0x1010631 field public static final int maxRows = 16843059; // 0x1010133 field public static final int maxSdkVersion = 16843377; // 0x1010271 field public static final int maxWidth = 16843039; // 0x101011f @@ -1074,7 +1075,7 @@ package android { field public static final int panelTextAppearance = 16842850; // 0x1010062 field public static final int parentActivityName = 16843687; // 0x10103a7 field @Deprecated public static final int password = 16843100; // 0x101015c - field public static final int passwordsActivity = 16844351; // 0x101063f + field public static final int passwordsActivity = 16844350; // 0x101063e field public static final int path = 16842794; // 0x101002a field public static final int pathAdvancedPattern = 16844320; // 0x1010620 field public static final int pathData = 16843781; // 0x1010405 @@ -1199,7 +1200,6 @@ package android { field public static final int right = 16843183; // 0x10101af field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093 field public static final int ringtoneType = 16843257; // 0x10101f9 - field public static final int rippleStyle = 16844337; // 0x1010631 field public static final int rollbackDataPolicy = 16844311; // 0x1010617 field public static final int rotation = 16843558; // 0x1010326 field public static final int rotationAnimation = 16844090; // 0x101053a @@ -1261,7 +1261,7 @@ package android { field public static final int segmentedButtonStyle = 16843568; // 0x1010330 field public static final int selectAllOnFocus = 16843102; // 0x101015e field public static final int selectable = 16843238; // 0x10101e6 - field public static final int selectableAsDefault = 16844352; // 0x1010640 + field public static final int selectableAsDefault = 16844351; // 0x101063f field public static final int selectableItemBackground = 16843534; // 0x101030e field public static final int selectableItemBackgroundBorderless = 16843868; // 0x101045c field @Deprecated public static final int selectedDateVerticalBar = 16843591; // 0x1010347 @@ -1408,8 +1408,8 @@ package android { field public static final int tabWidgetStyle = 16842883; // 0x1010083 field public static final int tag = 16842961; // 0x10100d1 field public static final int targetActivity = 16843266; // 0x1010202 - field public static final int targetCellHeight = 16844341; // 0x1010635 - field public static final int targetCellWidth = 16844340; // 0x1010634 + field public static final int targetCellHeight = 16844340; // 0x1010634 + field public static final int targetCellWidth = 16844339; // 0x1010633 field public static final int targetClass = 16842799; // 0x101002f field @Deprecated public static final int targetDescriptions = 16843680; // 0x10103a0 field public static final int targetId = 16843740; // 0x10103dc @@ -8491,7 +8491,7 @@ package android.appwidget { field public static final int WIDGET_CATEGORY_HOME_SCREEN = 1; // 0x1 field public static final int WIDGET_CATEGORY_KEYGUARD = 2; // 0x2 field public static final int WIDGET_CATEGORY_SEARCHBOX = 4; // 0x4 - field public static final int WIDGET_FEATURE_CONFIGURATION_OPTIONAL = 3; // 0x3 + field public static final int WIDGET_FEATURE_CONFIGURATION_OPTIONAL = 4; // 0x4 field public static final int WIDGET_FEATURE_HIDE_FROM_PICKER = 2; // 0x2 field public static final int WIDGET_FEATURE_RECONFIGURABLE = 1; // 0x1 field public int autoAdvanceViewId; @@ -10501,6 +10501,7 @@ package android.content { field public static final String DEVICE_POLICY_SERVICE = "device_policy"; field public static final String DISPLAY_HASH_SERVICE = "display_hash"; field public static final String DISPLAY_SERVICE = "display"; + field public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification"; field public static final String DOWNLOAD_SERVICE = "download"; field public static final String DROPBOX_SERVICE = "dropbox"; field public static final String EUICC_SERVICE = "euicc"; @@ -16687,13 +16688,9 @@ package android.graphics.drawable { public class RippleDrawable extends android.graphics.drawable.LayerDrawable { ctor public RippleDrawable(@NonNull android.content.res.ColorStateList, @Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable); method public int getRadius(); - method public int getRippleStyle(); method public void setColor(android.content.res.ColorStateList); method public void setRadius(int); - method public void setRippleStyle(int) throws java.lang.IllegalArgumentException; field public static final int RADIUS_AUTO = -1; // 0xffffffff - field public static final int STYLE_PATTERNED = 1; // 0x1 - field public static final int STYLE_SOLID = 0; // 0x0 } public class RotateDrawable extends android.graphics.drawable.DrawableWrapper { @@ -20569,6 +20566,7 @@ package android.media { method @NonNull public android.media.AudioRecord.Builder setAudioPlaybackCaptureConfig(@NonNull android.media.AudioPlaybackCaptureConfiguration); method public android.media.AudioRecord.Builder setAudioSource(int) throws java.lang.IllegalArgumentException; method public android.media.AudioRecord.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException; + method @NonNull public android.media.AudioRecord.Builder setContext(@NonNull android.content.Context); method @NonNull public android.media.AudioRecord.Builder setPrivacySensitive(boolean); } @@ -22743,7 +22741,8 @@ package android.media { } public class MediaRecorder implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection { - ctor public MediaRecorder(); + ctor @Deprecated public MediaRecorder(); + ctor public MediaRecorder(@NonNull android.content.Context); method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); method protected void finalize(); method public java.util.List<android.media.MicrophoneInfo> getActiveMicrophones() throws java.io.IOException; @@ -26140,10 +26139,6 @@ package android.net { ctor public NetworkSpecifier(); } - public class ParseException extends java.lang.RuntimeException { - field public String response; - } - public abstract class PlatformVpnProfile { method public final int getType(); method @NonNull public final String getTypeString(); @@ -26555,236 +26550,236 @@ package android.net.nsd { package android.net.rtp { - public class AudioCodec { - method public static android.net.rtp.AudioCodec getCodec(int, String, String); - method public static android.net.rtp.AudioCodec[] getCodecs(); - field public static final android.net.rtp.AudioCodec AMR; - field public static final android.net.rtp.AudioCodec GSM; - field public static final android.net.rtp.AudioCodec GSM_EFR; - field public static final android.net.rtp.AudioCodec PCMA; - field public static final android.net.rtp.AudioCodec PCMU; - field public final String fmtp; - field public final String rtpmap; - field public final int type; + @Deprecated public class AudioCodec { + method @Deprecated public static android.net.rtp.AudioCodec getCodec(int, String, String); + method @Deprecated public static android.net.rtp.AudioCodec[] getCodecs(); + field @Deprecated public static final android.net.rtp.AudioCodec AMR; + field @Deprecated public static final android.net.rtp.AudioCodec GSM; + field @Deprecated public static final android.net.rtp.AudioCodec GSM_EFR; + field @Deprecated public static final android.net.rtp.AudioCodec PCMA; + field @Deprecated public static final android.net.rtp.AudioCodec PCMU; + field @Deprecated public final String fmtp; + field @Deprecated public final String rtpmap; + field @Deprecated public final int type; } - public class AudioGroup { + @Deprecated public class AudioGroup { ctor @Deprecated public AudioGroup(); - ctor public AudioGroup(@NonNull android.content.Context); - method public void clear(); - method public int getMode(); - method public android.net.rtp.AudioStream[] getStreams(); - method public void sendDtmf(int); - method public void setMode(int); - field public static final int MODE_ECHO_SUPPRESSION = 3; // 0x3 - field public static final int MODE_MUTED = 1; // 0x1 - field public static final int MODE_NORMAL = 2; // 0x2 - field public static final int MODE_ON_HOLD = 0; // 0x0 - } - - public class AudioStream extends android.net.rtp.RtpStream { - ctor public AudioStream(java.net.InetAddress) throws java.net.SocketException; - method public android.net.rtp.AudioCodec getCodec(); - method public int getDtmfType(); - method public android.net.rtp.AudioGroup getGroup(); - method public final boolean isBusy(); - method public void join(android.net.rtp.AudioGroup); - method public void setCodec(android.net.rtp.AudioCodec); - method public void setDtmfType(int); - } - - public class RtpStream { - method public void associate(java.net.InetAddress, int); - method public java.net.InetAddress getLocalAddress(); - method public int getLocalPort(); - method public int getMode(); - method public java.net.InetAddress getRemoteAddress(); - method public int getRemotePort(); - method public boolean isBusy(); - method public void release(); - method public void setMode(int); - field public static final int MODE_NORMAL = 0; // 0x0 - field public static final int MODE_RECEIVE_ONLY = 2; // 0x2 - field public static final int MODE_SEND_ONLY = 1; // 0x1 + ctor @Deprecated public AudioGroup(@NonNull android.content.Context); + method @Deprecated public void clear(); + method @Deprecated public int getMode(); + method @Deprecated public android.net.rtp.AudioStream[] getStreams(); + method @Deprecated public void sendDtmf(int); + method @Deprecated public void setMode(int); + field @Deprecated public static final int MODE_ECHO_SUPPRESSION = 3; // 0x3 + field @Deprecated public static final int MODE_MUTED = 1; // 0x1 + field @Deprecated public static final int MODE_NORMAL = 2; // 0x2 + field @Deprecated public static final int MODE_ON_HOLD = 0; // 0x0 + } + + @Deprecated public class AudioStream extends android.net.rtp.RtpStream { + ctor @Deprecated public AudioStream(java.net.InetAddress) throws java.net.SocketException; + method @Deprecated public android.net.rtp.AudioCodec getCodec(); + method @Deprecated public int getDtmfType(); + method @Deprecated public android.net.rtp.AudioGroup getGroup(); + method @Deprecated public final boolean isBusy(); + method @Deprecated public void join(android.net.rtp.AudioGroup); + method @Deprecated public void setCodec(android.net.rtp.AudioCodec); + method @Deprecated public void setDtmfType(int); + } + + @Deprecated public class RtpStream { + method @Deprecated public void associate(java.net.InetAddress, int); + method @Deprecated public java.net.InetAddress getLocalAddress(); + method @Deprecated public int getLocalPort(); + method @Deprecated public int getMode(); + method @Deprecated public java.net.InetAddress getRemoteAddress(); + method @Deprecated public int getRemotePort(); + method @Deprecated public boolean isBusy(); + method @Deprecated public void release(); + method @Deprecated public void setMode(int); + field @Deprecated public static final int MODE_NORMAL = 0; // 0x0 + field @Deprecated public static final int MODE_RECEIVE_ONLY = 2; // 0x2 + field @Deprecated public static final int MODE_SEND_ONLY = 1; // 0x1 } } package android.net.sip { - public class SipAudioCall { - ctor public SipAudioCall(android.content.Context, android.net.sip.SipProfile); - method public void answerCall(int) throws android.net.sip.SipException; - method public void attachCall(android.net.sip.SipSession, String) throws android.net.sip.SipException; - method public void close(); - method public void continueCall(int) throws android.net.sip.SipException; - method public void endCall() throws android.net.sip.SipException; - method public android.net.sip.SipProfile getLocalProfile(); - method public android.net.sip.SipProfile getPeerProfile(); - method public int getState(); - method public void holdCall(int) throws android.net.sip.SipException; - method public boolean isInCall(); - method public boolean isMuted(); - method public boolean isOnHold(); - method public void makeCall(android.net.sip.SipProfile, android.net.sip.SipSession, int) throws android.net.sip.SipException; - method public void sendDtmf(int); - method public void sendDtmf(int, android.os.Message); - method public void setListener(android.net.sip.SipAudioCall.Listener); - method public void setListener(android.net.sip.SipAudioCall.Listener, boolean); - method public void setSpeakerMode(boolean); - method public void startAudio(); - method public void toggleMute(); - } - - public static class SipAudioCall.Listener { - ctor public SipAudioCall.Listener(); - method public void onCallBusy(android.net.sip.SipAudioCall); - method public void onCallEnded(android.net.sip.SipAudioCall); - method public void onCallEstablished(android.net.sip.SipAudioCall); - method public void onCallHeld(android.net.sip.SipAudioCall); - method public void onCalling(android.net.sip.SipAudioCall); - method public void onChanged(android.net.sip.SipAudioCall); - method public void onError(android.net.sip.SipAudioCall, int, String); - method public void onReadyToCall(android.net.sip.SipAudioCall); - method public void onRinging(android.net.sip.SipAudioCall, android.net.sip.SipProfile); - method public void onRingingBack(android.net.sip.SipAudioCall); - } - - public class SipErrorCode { - method public static String toString(int); - field public static final int CLIENT_ERROR = -4; // 0xfffffffc - field public static final int CROSS_DOMAIN_AUTHENTICATION = -11; // 0xfffffff5 - field public static final int DATA_CONNECTION_LOST = -10; // 0xfffffff6 - field public static final int INVALID_CREDENTIALS = -8; // 0xfffffff8 - field public static final int INVALID_REMOTE_URI = -6; // 0xfffffffa - field public static final int IN_PROGRESS = -9; // 0xfffffff7 - field public static final int NO_ERROR = 0; // 0x0 - field public static final int PEER_NOT_REACHABLE = -7; // 0xfffffff9 - field public static final int SERVER_ERROR = -2; // 0xfffffffe - field public static final int SERVER_UNREACHABLE = -12; // 0xfffffff4 - field public static final int SOCKET_ERROR = -1; // 0xffffffff - field public static final int TIME_OUT = -5; // 0xfffffffb - field public static final int TRANSACTION_TERMINTED = -3; // 0xfffffffd - } - - public class SipException extends java.lang.Exception { - ctor public SipException(); - ctor public SipException(String); - ctor public SipException(String, Throwable); - } - - public class SipManager { - method public void close(String) throws android.net.sip.SipException; - method public android.net.sip.SipSession createSipSession(android.net.sip.SipProfile, android.net.sip.SipSession.Listener) throws android.net.sip.SipException; - method public static String getCallId(android.content.Intent); - method public static String getOfferSessionDescription(android.content.Intent); - method public android.net.sip.SipSession getSessionFor(android.content.Intent) throws android.net.sip.SipException; - method public static boolean isApiSupported(android.content.Context); - method public static boolean isIncomingCallIntent(android.content.Intent); - method public boolean isOpened(String) throws android.net.sip.SipException; - method public boolean isRegistered(String) throws android.net.sip.SipException; - method public static boolean isSipWifiOnly(android.content.Context); - method public static boolean isVoipSupported(android.content.Context); - method public android.net.sip.SipAudioCall makeAudioCall(android.net.sip.SipProfile, android.net.sip.SipProfile, android.net.sip.SipAudioCall.Listener, int) throws android.net.sip.SipException; - method public android.net.sip.SipAudioCall makeAudioCall(String, String, android.net.sip.SipAudioCall.Listener, int) throws android.net.sip.SipException; - method public static android.net.sip.SipManager newInstance(android.content.Context); - method public void open(android.net.sip.SipProfile) throws android.net.sip.SipException; - method public void open(android.net.sip.SipProfile, android.app.PendingIntent, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; - method public void register(android.net.sip.SipProfile, int, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; - method public void setRegistrationListener(String, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; - method public android.net.sip.SipAudioCall takeAudioCall(android.content.Intent, android.net.sip.SipAudioCall.Listener) throws android.net.sip.SipException; - method public void unregister(android.net.sip.SipProfile, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; - field public static final String EXTRA_CALL_ID = "android:sipCallID"; - field public static final String EXTRA_OFFER_SD = "android:sipOfferSD"; - field public static final int INCOMING_CALL_RESULT_CODE = 101; // 0x65 - } - - public class SipProfile implements java.lang.Cloneable android.os.Parcelable java.io.Serializable { - method public int describeContents(); - method public String getAuthUserName(); - method public boolean getAutoRegistration(); - method public String getDisplayName(); - method public String getPassword(); - method public int getPort(); - method public String getProfileName(); - method public String getProtocol(); - method public String getProxyAddress(); - method public boolean getSendKeepAlive(); - method public String getSipDomain(); - method public String getUriString(); - method public String getUserName(); - method public void setCallingUid(int); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.net.sip.SipProfile> CREATOR; - } - - public static class SipProfile.Builder { - ctor public SipProfile.Builder(android.net.sip.SipProfile); - ctor public SipProfile.Builder(String) throws java.text.ParseException; - ctor public SipProfile.Builder(String, String) throws java.text.ParseException; - method public android.net.sip.SipProfile build(); - method public android.net.sip.SipProfile.Builder setAuthUserName(String); - method public android.net.sip.SipProfile.Builder setAutoRegistration(boolean); - method public android.net.sip.SipProfile.Builder setDisplayName(String); - method public android.net.sip.SipProfile.Builder setOutboundProxy(String); - method public android.net.sip.SipProfile.Builder setPassword(String); - method public android.net.sip.SipProfile.Builder setPort(int) throws java.lang.IllegalArgumentException; - method public android.net.sip.SipProfile.Builder setProfileName(String); - method public android.net.sip.SipProfile.Builder setProtocol(String) throws java.lang.IllegalArgumentException; - method public android.net.sip.SipProfile.Builder setSendKeepAlive(boolean); - } - - public interface SipRegistrationListener { - method public void onRegistering(String); - method public void onRegistrationDone(String, long); - method public void onRegistrationFailed(String, int, String); - } - - public final class SipSession { - method public void answerCall(String, int); - method public void changeCall(String, int); - method public void endCall(); - method public String getCallId(); - method public String getLocalIp(); - method public android.net.sip.SipProfile getLocalProfile(); - method public android.net.sip.SipProfile getPeerProfile(); - method public int getState(); - method public boolean isInCall(); - method public void makeCall(android.net.sip.SipProfile, String, int); - method public void register(int); - method public void setListener(android.net.sip.SipSession.Listener); - method public void unregister(); - } - - public static class SipSession.Listener { - ctor public SipSession.Listener(); - method public void onCallBusy(android.net.sip.SipSession); - method public void onCallChangeFailed(android.net.sip.SipSession, int, String); - method public void onCallEnded(android.net.sip.SipSession); - method public void onCallEstablished(android.net.sip.SipSession, String); - method public void onCalling(android.net.sip.SipSession); - method public void onError(android.net.sip.SipSession, int, String); - method public void onRegistering(android.net.sip.SipSession); - method public void onRegistrationDone(android.net.sip.SipSession, int); - method public void onRegistrationFailed(android.net.sip.SipSession, int, String); - method public void onRegistrationTimeout(android.net.sip.SipSession); - method public void onRinging(android.net.sip.SipSession, android.net.sip.SipProfile, String); - method public void onRingingBack(android.net.sip.SipSession); - } - - public static class SipSession.State { - method public static String toString(int); - field public static final int DEREGISTERING = 2; // 0x2 - field public static final int INCOMING_CALL = 3; // 0x3 - field public static final int INCOMING_CALL_ANSWERING = 4; // 0x4 - field public static final int IN_CALL = 8; // 0x8 - field public static final int NOT_DEFINED = 101; // 0x65 - field public static final int OUTGOING_CALL = 5; // 0x5 - field public static final int OUTGOING_CALL_CANCELING = 7; // 0x7 - field public static final int OUTGOING_CALL_RING_BACK = 6; // 0x6 - field public static final int PINGING = 9; // 0x9 - field public static final int READY_TO_CALL = 0; // 0x0 - field public static final int REGISTERING = 1; // 0x1 + @Deprecated public class SipAudioCall { + ctor @Deprecated public SipAudioCall(android.content.Context, android.net.sip.SipProfile); + method @Deprecated public void answerCall(int) throws android.net.sip.SipException; + method @Deprecated public void attachCall(android.net.sip.SipSession, String) throws android.net.sip.SipException; + method @Deprecated public void close(); + method @Deprecated public void continueCall(int) throws android.net.sip.SipException; + method @Deprecated public void endCall() throws android.net.sip.SipException; + method @Deprecated public android.net.sip.SipProfile getLocalProfile(); + method @Deprecated public android.net.sip.SipProfile getPeerProfile(); + method @Deprecated public int getState(); + method @Deprecated public void holdCall(int) throws android.net.sip.SipException; + method @Deprecated public boolean isInCall(); + method @Deprecated public boolean isMuted(); + method @Deprecated public boolean isOnHold(); + method @Deprecated public void makeCall(android.net.sip.SipProfile, android.net.sip.SipSession, int) throws android.net.sip.SipException; + method @Deprecated public void sendDtmf(int); + method @Deprecated public void sendDtmf(int, android.os.Message); + method @Deprecated public void setListener(android.net.sip.SipAudioCall.Listener); + method @Deprecated public void setListener(android.net.sip.SipAudioCall.Listener, boolean); + method @Deprecated public void setSpeakerMode(boolean); + method @Deprecated public void startAudio(); + method @Deprecated public void toggleMute(); + } + + @Deprecated public static class SipAudioCall.Listener { + ctor @Deprecated public SipAudioCall.Listener(); + method @Deprecated public void onCallBusy(android.net.sip.SipAudioCall); + method @Deprecated public void onCallEnded(android.net.sip.SipAudioCall); + method @Deprecated public void onCallEstablished(android.net.sip.SipAudioCall); + method @Deprecated public void onCallHeld(android.net.sip.SipAudioCall); + method @Deprecated public void onCalling(android.net.sip.SipAudioCall); + method @Deprecated public void onChanged(android.net.sip.SipAudioCall); + method @Deprecated public void onError(android.net.sip.SipAudioCall, int, String); + method @Deprecated public void onReadyToCall(android.net.sip.SipAudioCall); + method @Deprecated public void onRinging(android.net.sip.SipAudioCall, android.net.sip.SipProfile); + method @Deprecated public void onRingingBack(android.net.sip.SipAudioCall); + } + + @Deprecated public class SipErrorCode { + method @Deprecated public static String toString(int); + field @Deprecated public static final int CLIENT_ERROR = -4; // 0xfffffffc + field @Deprecated public static final int CROSS_DOMAIN_AUTHENTICATION = -11; // 0xfffffff5 + field @Deprecated public static final int DATA_CONNECTION_LOST = -10; // 0xfffffff6 + field @Deprecated public static final int INVALID_CREDENTIALS = -8; // 0xfffffff8 + field @Deprecated public static final int INVALID_REMOTE_URI = -6; // 0xfffffffa + field @Deprecated public static final int IN_PROGRESS = -9; // 0xfffffff7 + field @Deprecated public static final int NO_ERROR = 0; // 0x0 + field @Deprecated public static final int PEER_NOT_REACHABLE = -7; // 0xfffffff9 + field @Deprecated public static final int SERVER_ERROR = -2; // 0xfffffffe + field @Deprecated public static final int SERVER_UNREACHABLE = -12; // 0xfffffff4 + field @Deprecated public static final int SOCKET_ERROR = -1; // 0xffffffff + field @Deprecated public static final int TIME_OUT = -5; // 0xfffffffb + field @Deprecated public static final int TRANSACTION_TERMINTED = -3; // 0xfffffffd + } + + @Deprecated public class SipException extends java.lang.Exception { + ctor @Deprecated public SipException(); + ctor @Deprecated public SipException(String); + ctor @Deprecated public SipException(String, Throwable); + } + + @Deprecated public class SipManager { + method @Deprecated public void close(String) throws android.net.sip.SipException; + method @Deprecated public android.net.sip.SipSession createSipSession(android.net.sip.SipProfile, android.net.sip.SipSession.Listener) throws android.net.sip.SipException; + method @Deprecated public static String getCallId(android.content.Intent); + method @Deprecated public static String getOfferSessionDescription(android.content.Intent); + method @Deprecated public android.net.sip.SipSession getSessionFor(android.content.Intent) throws android.net.sip.SipException; + method @Deprecated public static boolean isApiSupported(android.content.Context); + method @Deprecated public static boolean isIncomingCallIntent(android.content.Intent); + method @Deprecated public boolean isOpened(String) throws android.net.sip.SipException; + method @Deprecated public boolean isRegistered(String) throws android.net.sip.SipException; + method @Deprecated public static boolean isSipWifiOnly(android.content.Context); + method @Deprecated public static boolean isVoipSupported(android.content.Context); + method @Deprecated public android.net.sip.SipAudioCall makeAudioCall(android.net.sip.SipProfile, android.net.sip.SipProfile, android.net.sip.SipAudioCall.Listener, int) throws android.net.sip.SipException; + method @Deprecated public android.net.sip.SipAudioCall makeAudioCall(String, String, android.net.sip.SipAudioCall.Listener, int) throws android.net.sip.SipException; + method @Deprecated public static android.net.sip.SipManager newInstance(android.content.Context); + method @Deprecated public void open(android.net.sip.SipProfile) throws android.net.sip.SipException; + method @Deprecated public void open(android.net.sip.SipProfile, android.app.PendingIntent, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; + method @Deprecated public void register(android.net.sip.SipProfile, int, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; + method @Deprecated public void setRegistrationListener(String, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; + method @Deprecated public android.net.sip.SipAudioCall takeAudioCall(android.content.Intent, android.net.sip.SipAudioCall.Listener) throws android.net.sip.SipException; + method @Deprecated public void unregister(android.net.sip.SipProfile, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; + field @Deprecated public static final String EXTRA_CALL_ID = "android:sipCallID"; + field @Deprecated public static final String EXTRA_OFFER_SD = "android:sipOfferSD"; + field @Deprecated public static final int INCOMING_CALL_RESULT_CODE = 101; // 0x65 + } + + @Deprecated public class SipProfile implements java.lang.Cloneable android.os.Parcelable java.io.Serializable { + method @Deprecated public int describeContents(); + method @Deprecated public String getAuthUserName(); + method @Deprecated public boolean getAutoRegistration(); + method @Deprecated public String getDisplayName(); + method @Deprecated public String getPassword(); + method @Deprecated public int getPort(); + method @Deprecated public String getProfileName(); + method @Deprecated public String getProtocol(); + method @Deprecated public String getProxyAddress(); + method @Deprecated public boolean getSendKeepAlive(); + method @Deprecated public String getSipDomain(); + method @Deprecated public String getUriString(); + method @Deprecated public String getUserName(); + method @Deprecated public void setCallingUid(int); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated public static final android.os.Parcelable.Creator<android.net.sip.SipProfile> CREATOR; + } + + @Deprecated public static class SipProfile.Builder { + ctor @Deprecated public SipProfile.Builder(android.net.sip.SipProfile); + ctor @Deprecated public SipProfile.Builder(String) throws java.text.ParseException; + ctor @Deprecated public SipProfile.Builder(String, String) throws java.text.ParseException; + method @Deprecated public android.net.sip.SipProfile build(); + method @Deprecated public android.net.sip.SipProfile.Builder setAuthUserName(String); + method @Deprecated public android.net.sip.SipProfile.Builder setAutoRegistration(boolean); + method @Deprecated public android.net.sip.SipProfile.Builder setDisplayName(String); + method @Deprecated public android.net.sip.SipProfile.Builder setOutboundProxy(String); + method @Deprecated public android.net.sip.SipProfile.Builder setPassword(String); + method @Deprecated public android.net.sip.SipProfile.Builder setPort(int) throws java.lang.IllegalArgumentException; + method @Deprecated public android.net.sip.SipProfile.Builder setProfileName(String); + method @Deprecated public android.net.sip.SipProfile.Builder setProtocol(String) throws java.lang.IllegalArgumentException; + method @Deprecated public android.net.sip.SipProfile.Builder setSendKeepAlive(boolean); + } + + @Deprecated public interface SipRegistrationListener { + method @Deprecated public void onRegistering(String); + method @Deprecated public void onRegistrationDone(String, long); + method @Deprecated public void onRegistrationFailed(String, int, String); + } + + @Deprecated public final class SipSession { + method @Deprecated public void answerCall(String, int); + method @Deprecated public void changeCall(String, int); + method @Deprecated public void endCall(); + method @Deprecated public String getCallId(); + method @Deprecated public String getLocalIp(); + method @Deprecated public android.net.sip.SipProfile getLocalProfile(); + method @Deprecated public android.net.sip.SipProfile getPeerProfile(); + method @Deprecated public int getState(); + method @Deprecated public boolean isInCall(); + method @Deprecated public void makeCall(android.net.sip.SipProfile, String, int); + method @Deprecated public void register(int); + method @Deprecated public void setListener(android.net.sip.SipSession.Listener); + method @Deprecated public void unregister(); + } + + @Deprecated public static class SipSession.Listener { + ctor @Deprecated public SipSession.Listener(); + method @Deprecated public void onCallBusy(android.net.sip.SipSession); + method @Deprecated public void onCallChangeFailed(android.net.sip.SipSession, int, String); + method @Deprecated public void onCallEnded(android.net.sip.SipSession); + method @Deprecated public void onCallEstablished(android.net.sip.SipSession, String); + method @Deprecated public void onCalling(android.net.sip.SipSession); + method @Deprecated public void onError(android.net.sip.SipSession, int, String); + method @Deprecated public void onRegistering(android.net.sip.SipSession); + method @Deprecated public void onRegistrationDone(android.net.sip.SipSession, int); + method @Deprecated public void onRegistrationFailed(android.net.sip.SipSession, int, String); + method @Deprecated public void onRegistrationTimeout(android.net.sip.SipSession); + method @Deprecated public void onRinging(android.net.sip.SipSession, android.net.sip.SipProfile, String); + method @Deprecated public void onRingingBack(android.net.sip.SipSession); + } + + @Deprecated public static class SipSession.State { + method @Deprecated public static String toString(int); + field @Deprecated public static final int DEREGISTERING = 2; // 0x2 + field @Deprecated public static final int INCOMING_CALL = 3; // 0x3 + field @Deprecated public static final int INCOMING_CALL_ANSWERING = 4; // 0x4 + field @Deprecated public static final int IN_CALL = 8; // 0x8 + field @Deprecated public static final int NOT_DEFINED = 101; // 0x65 + field @Deprecated public static final int OUTGOING_CALL = 5; // 0x5 + field @Deprecated public static final int OUTGOING_CALL_CANCELING = 7; // 0x7 + field @Deprecated public static final int OUTGOING_CALL_RING_BACK = 6; // 0x6 + field @Deprecated public static final int PINGING = 9; // 0x9 + field @Deprecated public static final int READY_TO_CALL = 0; // 0x0 + field @Deprecated public static final int REGISTERING = 1; // 0x1 } } @@ -30430,6 +30425,7 @@ package android.os { field public static final String BASE_OS; field public static final String CODENAME; field public static final String INCREMENTAL; + field public static final int MEDIA_PERFORMANCE_CLASS; field public static final int PREVIEW_SDK_INT; field public static final String RELEASE; field @NonNull public static final String RELEASE_OR_CODENAME; @@ -39604,22 +39600,22 @@ package android.telecom { method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection); method public final void connectionServiceFocusReleased(); method @Nullable public final android.telecom.RemoteConference createRemoteIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); - method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); + method @Nullable public final android.telecom.RemoteConnection createRemoteIncomingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest); method @Nullable public final android.telecom.RemoteConference createRemoteOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); - method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); + method @Nullable public final android.telecom.RemoteConnection createRemoteOutgoingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest); method public final java.util.Collection<android.telecom.Conference> getAllConferences(); method public final java.util.Collection<android.telecom.Connection> getAllConnections(); method public final android.os.IBinder onBind(android.content.Intent); method public void onConference(android.telecom.Connection, android.telecom.Connection); method public void onConnectionServiceFocusGained(); method public void onConnectionServiceFocusLost(); - method @Nullable public android.telecom.Conference onCreateIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); + method @Nullable public android.telecom.Conference onCreateIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest); method public void onCreateIncomingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateIncomingHandoverConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); - method @Nullable public android.telecom.Conference onCreateOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); - method public void onCreateOutgoingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); + method @Nullable public android.telecom.Conference onCreateOutgoingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest); + method public void onCreateOutgoingConferenceFailed(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateOutgoingHandoverConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); @@ -40536,6 +40532,9 @@ package android.telephony { field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool"; + field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool"; + field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL = "supports_device_to_device_communication_using_rtp_bool"; + field public static final String KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL = "supports_sdp_negotiation_of_d2d_rtp_header_extensions_bool"; field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; field public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL = "support_add_conference_participants_bool"; field public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = "support_adhoc_conference_calls_bool"; @@ -40614,14 +40613,6 @@ package android.telephony { public static final class CarrierConfigManager.Iwlan { field public static final int AUTHENTICATION_METHOD_CERT = 1; // 0x1 field public static final int AUTHENTICATION_METHOD_EAP_ONLY = 0; // 0x0 - field public static final int DH_GROUP_1024_BIT_MODP = 2; // 0x2 - field public static final int DH_GROUP_1536_BIT_MODP = 5; // 0x5 - field public static final int DH_GROUP_2048_BIT_MODP = 14; // 0xe - field public static final int DH_GROUP_3072_BIT_MODP = 15; // 0xf - field public static final int DH_GROUP_4096_BIT_MODP = 16; // 0x10 - field public static final int DH_GROUP_NONE = 0; // 0x0 - field public static final int ENCRYPTION_ALGORITHM_AES_CBC = 12; // 0xc - field public static final int ENCRYPTION_ALGORITHM_AES_CTR = 13; // 0xd field public static final int EPDG_ADDRESS_CELLULAR_LOC = 3; // 0x3 field public static final int EPDG_ADDRESS_PCO = 2; // 0x2 field public static final int EPDG_ADDRESS_PLMN = 1; // 0x1 @@ -40629,12 +40620,6 @@ package android.telephony { field public static final int ID_TYPE_FQDN = 2; // 0x2 field public static final int ID_TYPE_KEY_ID = 11; // 0xb field public static final int ID_TYPE_RFC822_ADDR = 3; // 0x3 - field public static final int INTEGRITY_ALGORITHM_AES_XCBC_96 = 5; // 0x5 - field public static final int INTEGRITY_ALGORITHM_HMAC_SHA1_96 = 2; // 0x2 - field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_256_128 = 12; // 0xc - field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_384_192 = 13; // 0xd - field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_512_256 = 14; // 0xe - field public static final int INTEGRITY_ALGORITHM_NONE = 0; // 0x0 field public static final String KEY_ADD_KE_TO_CHILD_SESSION_REKEY_BOOL = "iwlan.add_ke_to_child_session_rekey_bool"; field public static final String KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT = "iwlan.child_sa_rekey_hard_timer_sec_int"; field public static final String KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT = "iwlan.child_sa_rekey_soft_timer_sec_int"; @@ -40654,10 +40639,6 @@ package android.telephony { field public static final String KEY_IKE_REMOTE_ID_TYPE_INT = "iwlan.ike_remote_id_type_int"; field public static final String KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_cbc_key_size_int_array"; field public static final String KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_ctr_key_size_int_array"; - field public static final int KEY_LEN_AES_128 = 128; // 0x80 - field public static final int KEY_LEN_AES_192 = 192; // 0xc0 - field public static final int KEY_LEN_AES_256 = 256; // 0x100 - field public static final int KEY_LEN_UNUSED = 0; // 0x0 field public static final String KEY_MAX_RETRIES_INT = "iwlan.max_retries_int"; field public static final String KEY_MCC_MNCS_STRING_ARRAY = "iwlan.mcc_mncs_string_array"; field public static final String KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT = "iwlan.natt_keep_alive_timer_sec_int"; @@ -40667,11 +40648,6 @@ package android.telephony { field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array"; field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array"; field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array"; - field public static final int PSEUDORANDOM_FUNCTION_AES128_XCBC = 4; // 0x4 - field public static final int PSEUDORANDOM_FUNCTION_HMAC_SHA1 = 2; // 0x2 - field public static final int PSEUDORANDOM_FUNCTION_SHA2_256 = 5; // 0x5 - field public static final int PSEUDORANDOM_FUNCTION_SHA2_384 = 6; // 0x6 - field public static final int PSEUDORANDOM_FUNCTION_SHA2_512 = 7; // 0x7 } public abstract class CellIdentity implements android.os.Parcelable { @@ -42040,6 +42016,7 @@ package android.telephony { method public static int getDefaultSmsSubscriptionId(); method public static int getDefaultSubscriptionId(); method public static int getDefaultVoiceSubscriptionId(); + method public int getDeviceToDeviceStatusSharing(int); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions(); method public static int getSlotIndex(int); method @Nullable public int[] getSubscriptionIds(int); @@ -42052,6 +42029,7 @@ package android.telephony { method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharing(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int); method public void setSubscriptionOverrideCongested(int, boolean, long); method public void setSubscriptionOverrideCongested(int, boolean, @NonNull int[], long); @@ -42063,6 +42041,11 @@ package android.telephony { field public static final String ACTION_DEFAULT_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED"; field public static final String ACTION_MANAGE_SUBSCRIPTION_PLANS = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS"; field public static final String ACTION_REFRESH_SUBSCRIPTION_PLANS = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS"; + field public static final int D2D_SHARING_ALL = 3; // 0x3 + field public static final int D2D_SHARING_ALL_CONTACTS = 1; // 0x1 + field public static final int D2D_SHARING_DISABLED = 0; // 0x0 + field public static final int D2D_SHARING_STARRED_CONTACTS = 2; // 0x2 + field public static final String D2D_STATUS_SHARING = "d2d_sharing_status"; field public static final int DATA_ROAMING_DISABLE = 0; // 0x0 field public static final int DATA_ROAMING_ENABLE = 1; // 0x1 field public static final int DEFAULT_SUBSCRIPTION_ID = 2147483647; // 0x7fffffff @@ -42912,7 +42895,7 @@ package android.telephony.ims { method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public int getVoWiFiModeSetting(); method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isAdvancedCallingSettingEnabled(); - method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isCrossSimCallingEnabledByUser() throws android.telephony.ims.ImsException; + method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isCrossSimCallingEnabled() throws android.telephony.ims.ImsException; method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isTtyOverVolteEnabled(); method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isVoWiFiRoamingSettingEnabled(); method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isVoWiFiSettingEnabled(); @@ -49895,6 +49878,7 @@ package android.view { public interface WindowManager extends android.view.ViewManager { method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); + method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics(); method @Deprecated public android.view.Display getDefaultDisplay(); method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics(); @@ -51476,7 +51460,6 @@ package android.view.inputmethod { method public int describeContents(); method public void dump(android.util.Printer, String); method public android.content.ComponentName getComponent(); - method public int getConfigChanges(); method public String getId(); method public int getIsDefaultResourceId(); method public String getPackageName(); @@ -54983,6 +54966,7 @@ package android.widget { ctor public RemoteViews(@NonNull java.util.Map<android.util.SizeF,android.widget.RemoteViews>); ctor public RemoteViews(android.widget.RemoteViews); ctor public RemoteViews(android.os.Parcel); + method public void addStableView(@IdRes int, @NonNull android.widget.RemoteViews, int); method public void addView(@IdRes int, android.widget.RemoteViews); method public android.view.View apply(android.content.Context, android.view.ViewGroup); method @Deprecated public android.widget.RemoteViews clone(); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 057e16c8de5c..18b0a4344414 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -59,6 +59,10 @@ package android.content { method @NonNull public android.os.UserHandle getUser(); } + public class Intent implements java.lang.Cloneable android.os.Parcelable { + field public static final String ACTION_CLEAR_DNS_CACHE = "android.intent.action.CLEAR_DNS_CACHE"; + } + } package android.content.pm { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 70c945fe2fb9..bfc205b9f9a6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -147,6 +147,7 @@ package android { field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY"; field public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE"; field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER"; + field public static final String MANAGE_SPEECH_RECOGNITION = "android.permission.MANAGE_SPEECH_RECOGNITION"; field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS"; field public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS"; field public static final String MANAGE_TIME_AND_ZONE_DETECTION = "android.permission.MANAGE_TIME_AND_ZONE_DETECTION"; @@ -359,6 +360,7 @@ package android { field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemShell = 17039402; // 0x104002a field public static final int config_systemSpeechRecognizer = 17039406; // 0x104002e + field public static final int config_systemTelevisionNotificationHandler = 17039409; // 0x1040031 field public static final int config_systemWellbeing = 17039408; // 0x1040030 field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f } @@ -2160,7 +2162,6 @@ package android.content { field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000 field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; - field public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification"; field public static final String ETHERNET_SERVICE = "ethernet"; field public static final String EUICC_CARD_SERVICE = "euicc_card"; field public static final String FONT_SERVICE = "font"; @@ -2238,6 +2239,7 @@ package android.content { field @RequiresPermission(android.Manifest.permission.REVIEW_ACCESSIBILITY_SERVICES) public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES = "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES"; field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_ONGOING_PERMISSION_USAGE = "android.intent.action.REVIEW_ONGOING_PERMISSION_USAGE"; field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS"; + field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_PERMISSION_HISTORY = "android.intent.action.REVIEW_PERMISSION_HISTORY"; field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE"; field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED"; field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; @@ -2881,11 +2883,16 @@ package android.graphics.fonts { } public static final class FontFamilyUpdateRequest.FontFamily { - ctor public FontFamilyUpdateRequest.FontFamily(@NonNull String, @NonNull java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font>); method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font> getFonts(); method @NonNull public String getName(); } + public static final class FontFamilyUpdateRequest.FontFamily.Builder { + ctor public FontFamilyUpdateRequest.FontFamily.Builder(@NonNull String, @NonNull java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font>); + method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.FontFamily.Builder addFont(@NonNull android.graphics.fonts.FontFamilyUpdateRequest.Font); + method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.FontFamily build(); + } + public final class FontFileUpdateRequest { ctor public FontFileUpdateRequest(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[]); method @NonNull public android.os.ParcelFileDescriptor getParcelFileDescriptor(); @@ -2893,7 +2900,7 @@ package android.graphics.fonts { } public class FontManager { - method @NonNull public android.text.FontConfig getFontConfig(); + method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public android.text.FontConfig getFontConfig(); method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int); field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff @@ -3102,6 +3109,7 @@ package android.hardware.hdmi { method @NonNull public java.util.List<android.hardware.hdmi.HdmiDeviceInfo> getConnectedDevices(); method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getHdmiCecEnabled(); method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getHdmiCecVersion(); + method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getHdmiCecVolumeControlEnabled(); method public int getPhysicalAddress(); method @Nullable public android.hardware.hdmi.HdmiPlaybackClient getPlaybackClient(); method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public String getPowerControlMode(); @@ -3109,6 +3117,8 @@ package android.hardware.hdmi { method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient(); method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getSystemAudioModeMuting(); method @Nullable public android.hardware.hdmi.HdmiTvClient getTvClient(); + method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getTvSendStandbyOnSleep(); + method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public int getTvWakeOnOneTouchPlay(); method @NonNull @RequiresPermission(android.Manifest.permission.HDMI_CEC) public java.util.List<java.lang.String> getUserCecSettings(); method public boolean isDeviceConnected(@NonNull android.hardware.hdmi.HdmiDeviceInfo); method public void powerOffDevice(@NonNull android.hardware.hdmi.HdmiDeviceInfo); @@ -3117,10 +3127,13 @@ package android.hardware.hdmi { method public void setActiveSource(@NonNull android.hardware.hdmi.HdmiDeviceInfo); method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setHdmiCecEnabled(@NonNull int); method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setHdmiCecVersion(@NonNull int); + method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setHdmiCecVolumeControlEnabled(int); method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setPowerControlMode(@NonNull String); method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setPowerStateChangeOnActiveSourceLost(@NonNull String); method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean); method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setSystemAudioModeMuting(@NonNull int); + method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setTvSendStandbyOnSleep(@NonNull int); + method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setTvWakeOnOneTouchPlay(@NonNull int); field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE"; field public static final int AVR_VOLUME_MUTED = 101; // 0x65 field public static final String CEC_SETTING_NAME_HDMI_CEC_ENABLED = "hdmi_cec_enabled"; @@ -3128,6 +3141,9 @@ package android.hardware.hdmi { field public static final String CEC_SETTING_NAME_POWER_CONTROL_MODE = "send_standby_on_sleep"; field public static final String CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST = "power_state_change_on_active_source_lost"; field public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING = "system_audio_mode_muting"; + field public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP = "tv_send_standby_on_sleep"; + field public static final String CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY = "tv_wake_on_one_touch_play"; + field public static final String CEC_SETTING_NAME_VOLUME_CONTROL_MODE = "volume_control_enabled"; field public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 162; // 0xa2 field public static final int CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION = 160; // 0xa0 field public static final int CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE = 161; // 0xa1 @@ -3223,6 +3239,12 @@ package android.hardware.hdmi { field public static final int TIMER_STATUS_PROGRAMMED_INFO_MIGHT_NOT_ENOUGH_SPACE = 11; // 0xb field public static final int TIMER_STATUS_PROGRAMMED_INFO_NOT_ENOUGH_SPACE = 9; // 0x9 field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa + field public static final int TV_SEND_STANDBY_ON_SLEEP_DISABLED = 0; // 0x0 + field public static final int TV_SEND_STANDBY_ON_SLEEP_ENABLED = 1; // 0x1 + field public static final int TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED = 0; // 0x0 + field public static final int TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED = 1; // 0x1 + field public static final int VOLUME_CONTROL_DISABLED = 0; // 0x0 + field public static final int VOLUME_CONTROL_ENABLED = 1; // 0x1 } public static interface HdmiControlManager.CecSettingChangeListener { @@ -7462,22 +7484,22 @@ package android.net.netstats.provider { package android.net.sip { - public class SipAudioCall { - method @Nullable public android.net.rtp.AudioGroup getAudioGroup(); - method public void setAudioGroup(@NonNull android.net.rtp.AudioGroup); + @Deprecated public class SipAudioCall { + method @Deprecated @Nullable public android.net.rtp.AudioGroup getAudioGroup(); + method @Deprecated public void setAudioGroup(@NonNull android.net.rtp.AudioGroup); } - public class SipManager { - method @NonNull public java.util.List<android.net.sip.SipProfile> getProfiles() throws android.net.sip.SipException; - field public static final String ACTION_SIP_CALL_OPTION_CHANGED = "android.net.sip.action.SIP_CALL_OPTION_CHANGED"; - field public static final String ACTION_SIP_INCOMING_CALL = "android.net.sip.action.SIP_INCOMING_CALL"; - field public static final String ACTION_SIP_REMOVE_PROFILE = "android.net.sip.action.SIP_REMOVE_PROFILE"; - field public static final String ACTION_SIP_SERVICE_UP = "android.net.sip.action.SIP_SERVICE_UP"; - field public static final String ACTION_START_SIP = "android.net.sip.action.START_SIP"; + @Deprecated public class SipManager { + method @Deprecated @NonNull public java.util.List<android.net.sip.SipProfile> getProfiles() throws android.net.sip.SipException; + field @Deprecated public static final String ACTION_SIP_CALL_OPTION_CHANGED = "android.net.sip.action.SIP_CALL_OPTION_CHANGED"; + field @Deprecated public static final String ACTION_SIP_INCOMING_CALL = "android.net.sip.action.SIP_INCOMING_CALL"; + field @Deprecated public static final String ACTION_SIP_REMOVE_PROFILE = "android.net.sip.action.SIP_REMOVE_PROFILE"; + field @Deprecated public static final String ACTION_SIP_SERVICE_UP = "android.net.sip.action.SIP_SERVICE_UP"; + field @Deprecated public static final String ACTION_START_SIP = "android.net.sip.action.START_SIP"; } - public class SipProfile implements java.lang.Cloneable android.os.Parcelable java.io.Serializable { - method public int getCallingUid(); + @Deprecated public class SipProfile implements java.lang.Cloneable android.os.Parcelable java.io.Serializable { + method @Deprecated public int getCallingUid(); } } @@ -7498,12 +7520,12 @@ package android.net.util { package android.net.vcn { public class VcnManager { - method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyListener); + method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.vcn.VcnNetworkPolicyResult applyVcnNetworkPolicy(@NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties); - method public void removeVcnNetworkPolicyListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyListener); + method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void removeVcnNetworkPolicyChangeListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener); } - public static interface VcnManager.VcnNetworkPolicyListener { + public static interface VcnManager.VcnNetworkPolicyChangeListener { method public void onPolicyChanged(); } @@ -8152,6 +8174,26 @@ package android.os { field @NonNull public static final android.os.Parcelable.Creator<android.os.ParcelableHolder> CREATOR; } + public class PowerExemptionManager { + method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToPermanentAllowList(@NonNull String); + method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToPermanentAllowList(@NonNull java.util.List<java.lang.String>); + method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void addToTemporaryAllowList(@NonNull String, long, int, @Nullable String); + method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long addToTemporaryAllowListForEvent(@NonNull String, int, int, @Nullable String); + method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromAllowList(@NonNull String); + field public static final int EVENT_MMS = 2; // 0x2 + field public static final int EVENT_SMS = 1; // 0x1 + field public static final int EVENT_UNSPECIFIED = 0; // 0x0 + field public static final int REASON_ACCOUNT_TRANSFER = 104; // 0x68 + field public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67 + field public static final int REASON_GEOFENCING = 100; // 0x64 + field public static final int REASON_OTHER = 1; // 0x1 + field public static final int REASON_PUSH_MESSAGING = 101; // 0x65 + field public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; // 0x66 + field public static final int REASON_UNKNOWN = 0; // 0x0 + field public static final int TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0 + field public static final int TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1 + } + public final class PowerManager { method @RequiresPermission(allOf={android.Manifest.permission.READ_DREAM_STATE, android.Manifest.permission.WRITE_DREAM_STATE}) public void dream(long); method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean forceSuspend(); @@ -8182,25 +8224,25 @@ package android.os { field public static final int USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS = 1; // 0x1 } - public class PowerWhitelistManager { - method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String); - method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>); - method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromWhitelist(@NonNull String); - method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long, int, @Nullable String); + @Deprecated public class PowerWhitelistManager { + method @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String); + method @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>); + method @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromWhitelist(@NonNull String); + method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long, int, @Nullable String); method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long); method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @Nullable String); - method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, int, @Nullable String); - field public static final int EVENT_MMS = 2; // 0x2 - field public static final int EVENT_SMS = 1; // 0x1 - field public static final int EVENT_UNSPECIFIED = 0; // 0x0 - field public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67 - field public static final int REASON_GEOFENCING = 100; // 0x64 - field public static final int REASON_OTHER = 1; // 0x1 - field public static final int REASON_PUSH_MESSAGING = 101; // 0x65 - field public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; // 0x66 - field public static final int REASON_UNKNOWN = 0; // 0x0 - field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0 - field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1 + method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, int, @Nullable String); + field @Deprecated public static final int EVENT_MMS = 2; // 0x2 + field @Deprecated public static final int EVENT_SMS = 1; // 0x1 + field @Deprecated public static final int EVENT_UNSPECIFIED = 0; // 0x0 + field @Deprecated public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67 + field @Deprecated public static final int REASON_GEOFENCING = 100; // 0x64 + field @Deprecated public static final int REASON_OTHER = 1; // 0x1 + field @Deprecated public static final int REASON_PUSH_MESSAGING = 101; // 0x65 + field @Deprecated public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; // 0x66 + field @Deprecated public static final int REASON_UNKNOWN = 0; // 0x0 + field @Deprecated public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0 + field @Deprecated public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1 } public class RecoverySystem { @@ -9931,11 +9973,13 @@ package android.service.resumeonreboot { package android.service.rotationresolver { public final class RotationResolutionRequest implements android.os.Parcelable { + ctor public RotationResolutionRequest(@NonNull String, int, int, boolean, long); method public int describeContents(); method public int getCurrentRotation(); method @NonNull public String getPackageName(); method public int getProposedRotation(); method public long getTimeoutMillis(); + method public boolean shouldUseCamera(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.service.rotationresolver.RotationResolutionRequest> CREATOR; } @@ -10207,6 +10251,7 @@ package android.service.voice { ctor public HotwordDetectionService(); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method public void onDetectFromDspSource(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, long, @NonNull android.service.voice.HotwordDetectionService.DspHotwordDetectionCallback); + method public void onUpdateState(@Nullable android.os.Bundle, @Nullable android.os.SharedMemory); field public static final String SERVICE_INTERFACE = "android.service.voice.HotwordDetectionService"; } @@ -10217,10 +10262,8 @@ package android.service.voice { public class VoiceInteractionService extends android.app.Service { method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); + method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.Bundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback); method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager(); - method public final int setHotwordDetectionConfig(@Nullable android.os.Bundle); - field public static final int HOTWORD_CONFIG_FAILURE = 1; // 0x1 - field public static final int HOTWORD_CONFIG_SUCCESS = 0; // 0x0 } } @@ -10320,6 +10363,7 @@ package android.telecom { public abstract class CallDiagnosticService extends android.app.Service { ctor public CallDiagnosticService(); + method @NonNull public java.util.concurrent.Executor getExecutor(); method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onBluetoothCallQualityReportReceived(@NonNull android.telecom.BluetoothCallQualityReport); method public abstract void onCallAudioStateChanged(@NonNull android.telecom.CallAudioState); @@ -10392,16 +10436,12 @@ package android.telecom { ctor public DiagnosticCall(); method public final void clearDiagnosticMessage(int); method public final void displayDiagnosticMessage(int, @NonNull CharSequence); - method @NonNull public android.telecom.Call.Details getCallDetails(); method public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details); method @Nullable public abstract CharSequence onCallDisconnected(int, int); method @Nullable public abstract CharSequence onCallDisconnected(@NonNull android.telephony.ims.ImsReasonInfo); method public abstract void onCallQualityReceived(@NonNull android.telephony.CallQuality); method public abstract void onReceiveDeviceToDeviceMessage(int, int); method public final void sendDeviceToDeviceMessage(int, int); - field public static final int AUDIO_CODEC_AMR_NB = 3; // 0x3 - field public static final int AUDIO_CODEC_AMR_WB = 2; // 0x2 - field public static final int AUDIO_CODEC_EVS = 1; // 0x1 field public static final int BATTERY_STATE_CHARGING = 3; // 0x3 field public static final int BATTERY_STATE_GOOD = 2; // 0x2 field public static final int BATTERY_STATE_LOW = 1; // 0x1 @@ -10411,9 +10451,6 @@ package android.telecom { field public static final int MESSAGE_CALL_NETWORK_TYPE = 1; // 0x1 field public static final int MESSAGE_DEVICE_BATTERY_STATE = 3; // 0x3 field public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4; // 0x4 - field public static final int NETWORK_TYPE_IWLAN = 2; // 0x2 - field public static final int NETWORK_TYPE_LTE = 1; // 0x1 - field public static final int NETWORK_TYPE_NR = 3; // 0x3 } public abstract class InCallService extends android.app.Service { @@ -11435,7 +11472,7 @@ package android.telephony { } public static interface TelephonyCallback.AllowedNetworkTypesListener { - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onAllowedNetworkTypesChanged(@NonNull java.util.Map<java.lang.Integer,java.lang.Long>); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onAllowedNetworkTypesChanged(int, long); } public static interface TelephonyCallback.CallAttributesListener { @@ -11919,7 +11956,7 @@ package android.telephony.data { method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV4(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV6(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setPcscfAddresses(@NonNull java.util.List<java.net.InetAddress>); - method @NonNull public android.telephony.data.DataCallResponse.Builder setPduSessionId(int); + method @NonNull public android.telephony.data.DataCallResponse.Builder setPduSessionId(@IntRange(from=android.telephony.data.DataCallResponse.PDU_SESSION_ID_NOT_SET, to=15) int); method @NonNull public android.telephony.data.DataCallResponse.Builder setProtocolType(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setRetryDurationMillis(long); method @NonNull public android.telephony.data.DataCallResponse.Builder setSliceInfo(@Nullable android.telephony.data.SliceInfo); @@ -12014,7 +12051,6 @@ package android.telephony.data { } public final class EpsBearerQosSessionAttributes implements android.os.Parcelable android.net.QosSessionAttributes { - method @NonNull public static android.telephony.data.EpsBearerQosSessionAttributes create(@NonNull android.os.Parcel); method public int describeContents(); method public long getGuaranteedDownlinkBitRate(); method public long getGuaranteedUplinkBitRate(); @@ -12997,6 +13033,7 @@ package android.telephony.ims { method public void onAutoConfigurationErrorReceived(int, @NonNull String); method public void onConfigurationChanged(@NonNull byte[]); method public void onConfigurationReset(); + method public void onPreProvisioningReceived(@NonNull byte[]); method public void onRemoved(); } @@ -13466,6 +13503,7 @@ package android.telephony.ims.stub { method public int getConfigInt(int); method public String getConfigString(int); method public final void notifyAutoConfigurationErrorReceived(int, @NonNull String); + method public final void notifyPreProvisioningReceived(@NonNull byte[]); method public final void notifyProvisionedValueChanged(int, int); method public final void notifyProvisionedValueChanged(int, String); method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean); @@ -13797,6 +13835,7 @@ package android.util { package android.uwb { public final class AngleMeasurement implements android.os.Parcelable { + ctor public AngleMeasurement(double, double, 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(); @@ -13805,14 +13844,6 @@ package android.uwb { field @NonNull public static final android.os.Parcelable.Creator<android.uwb.AngleMeasurement> CREATOR; } - public static final class AngleMeasurement.Builder { - ctor public AngleMeasurement.Builder(); - method @NonNull public android.uwb.AngleMeasurement build(); - method @NonNull public android.uwb.AngleMeasurement.Builder setConfidenceLevel(double); - method @NonNull public android.uwb.AngleMeasurement.Builder setErrorRadians(double); - method @NonNull public android.uwb.AngleMeasurement.Builder setRadians(double); - } - public final class AngleOfArrivalMeasurement implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.uwb.AngleMeasurement getAltitude(); @@ -13822,10 +13853,9 @@ package android.uwb { } public static final class AngleOfArrivalMeasurement.Builder { - ctor public AngleOfArrivalMeasurement.Builder(); + ctor public AngleOfArrivalMeasurement.Builder(@NonNull android.uwb.AngleMeasurement); method @NonNull public android.uwb.AngleOfArrivalMeasurement build(); method @NonNull public android.uwb.AngleOfArrivalMeasurement.Builder setAltitude(@NonNull android.uwb.AngleMeasurement); - method @NonNull public android.uwb.AngleOfArrivalMeasurement.Builder setAzimuth(@NonNull android.uwb.AngleMeasurement); } public final class DistanceMeasurement implements android.os.Parcelable { @@ -13925,7 +13955,7 @@ package android.uwb { public final class UwbManager { method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public long elapsedRealtimeResolutionNanos(); method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public android.os.PersistableBundle getSpecificationInfo(); - method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public AutoCloseable openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public android.os.CancellationSignal openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback); method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void registerAdapterStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdapterStateCallback); method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void unregisterAdapterStateCallback(@NonNull android.uwb.UwbManager.AdapterStateCallback); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 86949e05ba71..9fde79171cf6 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -414,6 +414,7 @@ package android.app.admin { method public long getLastNetworkLogRetrievalTime(); method public long getLastSecurityLogRetrievalTime(); method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle); + method @NonNull @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public java.util.Set<java.lang.String> getPolicyExemptApps(); method public boolean isCurrentInputMethodSetByOwner(); method public boolean isFactoryResetProtectionPolicySupported(); method @RequiresPermission(anyOf={"android.permission.MARK_DEVICE_ORGANIZATION_OWNED", "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName); @@ -698,7 +699,8 @@ package android.content { field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle"; field public static final String DREAM_SERVICE = "dream"; field public static final String FONT_SERVICE = "font"; - field public static final String POWER_WHITELIST_MANAGER = "power_whitelist"; + field public static final String POWER_EXEMPTION_SERVICE = "power_exemption"; + field @Deprecated public static final String POWER_WHITELIST_MANAGER = "power_whitelist"; field public static final String TEST_NETWORK_SERVICE = "test_network"; } @@ -706,6 +708,10 @@ package android.content { method public int getDisplayId(); } + public class SyncAdapterType implements android.os.Parcelable { + method @Nullable public String getPackageName(); + } + } package android.content.integrity { @@ -940,6 +946,12 @@ package android.graphics { method public void splitVertically(@NonNull android.graphics.Rect...); } + public class Typeface { + method @NonNull public static java.util.Map<java.lang.String,android.graphics.Typeface> deserializeFontMap(@NonNull java.nio.ByteBuffer) throws java.io.IOException; + method @Nullable public static android.os.SharedMemory getSystemFontMapSharedMemory(); + method @NonNull public static android.os.SharedMemory serializeFontMap(@NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws android.system.ErrnoException, java.io.IOException; + } + } package android.graphics.drawable { @@ -962,7 +974,7 @@ package android.graphics.drawable { package android.graphics.fonts { public class FontManager { - method @NonNull public android.text.FontConfig getFontConfig(); + method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public android.text.FontConfig getFontConfig(); method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int); field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff @@ -1485,6 +1497,14 @@ package android.net { package android.os { + public final class BatteryStatsManager { + method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void resetBattery(boolean); + method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setBatteryLevel(int, boolean); + method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setChargerAcOnline(boolean, boolean); + method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void suspendBatteryInput(); + method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void unplugBattery(boolean); + } + public class Build { method public static boolean is64BitAbi(String); field public static final boolean IS_EMULATOR; @@ -2125,6 +2145,14 @@ package android.service.watchdog { } +package android.speech { + + public class SpeechRecognizer { + method public void setTemporaryOnDeviceRecognizer(@Nullable android.content.ComponentName); + } + +} + package android.telecom { public static class Call.Details { @@ -2530,6 +2558,7 @@ package android.view { method public default int getDisplayImePolicy(int); method public default void holdLock(android.os.IBinder, int); method public default void setDisplayImePolicy(int, int); + method public default void setForceCrossWindowBlurDisabled(boolean); method public default void setShouldShowSystemDecors(int, boolean); method public default void setShouldShowWithInsecureKeyguard(int, boolean); method public default boolean shouldShowSystemDecors(int); @@ -2705,10 +2734,6 @@ package android.view.inputmethod { method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>); } - public final class InputMethodInfo implements android.os.Parcelable { - ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int); - } - public final class InputMethodManager { method public int getDisplayId(); method public boolean hasActiveInputConnection(@Nullable android.view.View); @@ -2907,7 +2932,7 @@ package android.window { method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo); method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo); method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer(); - method @BinderThread public void removeStartingWindow(int); + method @BinderThread public void removeStartingWindow(int, @Nullable android.view.SurfaceControl, @Nullable android.graphics.Rect, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean); method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void unregisterOrganizer(); } diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 233f737b8e0f..a24f8716b1cc 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -394,6 +394,20 @@ public class ActivityTaskManager { } /** + * Whether to allow non-resizable apps to be shown in multi-window. The app will be letterboxed + * if the request orientation is not met, and will be shown in size-compat mode if the container + * size has changed. + * @hide + */ + public static boolean supportsNonResizableMultiWindow() { + try { + return ActivityTaskManager.getService().supportsNonResizableMultiWindow(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @return whether the UI mode of the given config supports error dialogs (ANR, crash, etc). * @hide */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 0b5958695dff..8977ba774d0b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5775,11 +5775,14 @@ public final class ActivityThread extends ClientTransactionHandler // ResourcesImpl constructions. final int diff = activity.mCurrentConfig.diffPublicOnly(newConfig); - if (diff == 0 && !shouldUpdateWindowMetricsBounds(activity.mCurrentConfig, newConfig) - && !movedToDifferentDisplay && mResourcesManager.isSameResourcesOverrideConfig( - activityToken, amOverrideConfig)) { - // Nothing significant, don't proceed with updating and reporting. - return null; + if (diff == 0) { + if (!shouldUpdateWindowMetricsBounds(activity.mCurrentConfig, newConfig) + && !movedToDifferentDisplay + && mResourcesManager.isSameResourcesOverrideConfig( + activityToken, amOverrideConfig)) { + // Nothing significant, don't proceed with updating and reporting. + return null; + } } else if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) { // If this activity doesn't handle any of the config changes, then don't bother // calling onConfigurationChanged. Otherwise, report to the activity for the diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index dd1bc7c61547..d310e8f0ef5c 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -56,6 +56,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserManager; +import android.provider.DeviceConfig; import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; @@ -200,9 +201,12 @@ public class AppOpsManager { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) public static final long SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE = 151105954L; + private static final String FULL_LOG = "privacy_attribution_tag_full_log_enabled"; private static final int MAX_UNFORWARDED_OPS = 10; + private static Boolean sFullLog = null; + final Context mContext; @UnsupportedAppUsage @@ -6972,6 +6976,26 @@ public class AppOpsManager { AppOpsManager(Context context, IAppOpsService service) { mContext = context; mService = service; + + if (mContext != null) { + final PackageManager pm = mContext.getPackageManager(); + try { + if (pm != null && pm.checkPermission(Manifest.permission.READ_DEVICE_CONFIG, + mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED) { + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY, + mContext.getMainExecutor(), properties -> { + if (properties.getKeyset().contains(FULL_LOG)) { + sFullLog = properties.getBoolean(FULL_LOG, false); + } + }); + return; + } + } catch (Exception e) { + // This manager was made before DeviceConfig is ready, so it's a low-level + // system app. We likely don't care about its logs. + } + } + sFullLog = false; } /** @@ -9110,10 +9134,20 @@ public class AppOpsManager { StringBuilder sb = new StringBuilder(); for (int i = firstInteresting; i <= lastInteresting; i++) { + if (sFullLog == null) { + try { + sFullLog = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + FULL_LOG, false); + } catch (SecurityException e) { + // This should not happen, but it may, in rare cases + sFullLog = false; + } + } + if (i != firstInteresting) { sb.append('\n'); } - if (sb.length() + trace[i].toString().length() > 600) { + if (!sFullLog && sb.length() + trace[i].toString().length() > 600) { break; } sb.append(trace[i]); diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 542f754ce364..3bfddf7db015 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -289,6 +289,13 @@ interface IActivityTaskManager { void setSplitScreenResizing(boolean resizing); boolean supportsLocalVoiceInteraction(); + /** + * Whether to allow non-resizable apps to be shown in multi-window. The app will be letterboxed + * if the request orientation is not met, and will be shown in size-compat mode if the container + * size has changed. + */ + boolean supportsNonResizableMultiWindow(); + // Get device configuration ConfigurationInfo getDeviceConfigurationInfo(); diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 8e53b5ba1c9f..7dbbc54665e9 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -57,6 +57,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.DeadSystemException; +import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; @@ -120,6 +121,8 @@ public class WallpaperManager { /** {@hide} */ private static final String VALUE_CMF_COLOR = android.os.SystemProperties.get("ro.boot.hardware.color"); + /** {@hide} */ + private static final String WALLPAPER_CMF_PATH = "/wallpaper/image/"; /** * Activity Action: Show settings for choosing wallpaper. Do not use directly to construct @@ -2066,31 +2069,45 @@ public class WallpaperManager { return null; } else { whichProp = PROP_WALLPAPER; - final int defaultColorResId = context.getResources().getIdentifier( - "default_wallpaper_" + VALUE_CMF_COLOR, "drawable", "android"); - defaultResId = - defaultColorResId == 0 ? com.android.internal.R.drawable.default_wallpaper - : defaultColorResId; + defaultResId = com.android.internal.R.drawable.default_wallpaper; } final String path = SystemProperties.get(whichProp); + final InputStream wallpaperInputStream = getWallpaperInputStream(path); + if (wallpaperInputStream != null) { + return wallpaperInputStream; + } + final String cmfPath = getCmfWallpaperPath(); + final InputStream cmfWallpaperInputStream = getWallpaperInputStream(cmfPath); + if (cmfWallpaperInputStream != null) { + return cmfWallpaperInputStream; + } + try { + return context.getResources().openRawResource(defaultResId); + } catch (NotFoundException e) { + // no default defined for this device; this is not a failure + } + return null; + } + + private static InputStream getWallpaperInputStream(String path) { if (!TextUtils.isEmpty(path)) { final File file = new File(path); if (file.exists()) { try { return new FileInputStream(file); } catch (IOException e) { - // Ignored, fall back to platform default below + // Ignored, fall back to platform default } } } - try { - return context.getResources().openRawResource(defaultResId); - } catch (NotFoundException e) { - // no default defined for this device; this is not a failure - } return null; } + private static String getCmfWallpaperPath() { + return Environment.getProductDirectory() + WALLPAPER_CMF_PATH + "default_wallpaper_" + + VALUE_CMF_COLOR; + } + /** * Return {@link ComponentName} of the default live wallpaper, or * {@code null} if none is defined. diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ccf41e5f3063..930717b97555 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -987,7 +987,8 @@ public class DevicePolicyManager { * The default for this extra is {@code false} - by default, the admin of a fully-managed * device has the ability to grant sensors-related permissions. * - * <p>Use with {@link #ACTION_PROVISION_MANAGED_DEVICE} only. + * <p>Use only for device owner provisioning. + * @see #ACTION_GET_PROVISIONING_MODE */ public static final String EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT = "android.app.extra.PROVISIONING_PERMISSION_GRANT_OPT_OUT"; @@ -11838,7 +11839,7 @@ public class DevicePolicyManager { /** * @hide - * Force update user setup completed status. This API has no effect on user build. + * Force update user setup completed status. * @throws {@link SecurityException} if the caller has no * {@code android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS} or the caller is * not {@link UserHandle#SYSTEM_USER} @@ -13725,4 +13726,22 @@ public class DevicePolicyManager { throw re.rethrowFromSystemServer(); } } + + /** + * Lists apps that are exempt from policies (such as + * {@link #setPackagesSuspended(ComponentName, String[], boolean)}). + * + * @hide + */ + @TestApi + @RequiresPermission(value = android.Manifest.permission.MANAGE_DEVICE_ADMINS) + public @NonNull Set<String> getPolicyExemptApps() { + if (mService == null) return Collections.emptySet(); + + try { + return new HashSet<>(mService.listPolicyExemptApps()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 25ca59963d4b..e98720c0d96c 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -177,6 +177,7 @@ interface IDevicePolicyManager { String[] setPackagesSuspended(in ComponentName admin, in String callerPackage, in String[] packageNames, boolean suspended); boolean isPackageSuspended(in ComponentName admin, in String callerPackage, String packageName); + List<String> listPolicyExemptApps(); boolean installCaCert(in ComponentName admin, String callerPackage, in byte[] certBuffer); void uninstallCaCerts(in ComponentName admin, String callerPackage, in String[] aliases); diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index 6ac1c1ae61ec..1cbb2fb3a8a3 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -121,7 +121,7 @@ public class AppWidgetProviderInfo implements Parcelable { * * @see #widgetFeatures */ - public static final int WIDGET_FEATURE_CONFIGURATION_OPTIONAL = 3; + public static final int WIDGET_FEATURE_CONFIGURATION_OPTIONAL = 4; /** @hide */ @IntDef(flag = true, prefix = { "FLAG_" }, value = { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index df5c58c2634f..25234592f4c4 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3713,6 +3713,9 @@ public abstract class Context { * usage statistics. * <dt> {@link #HARDWARE_PROPERTIES_SERVICE} ("hardware_properties") * <dd> A {@link android.os.HardwarePropertiesManager} for accessing hardware properties. + * <dt> {@link #DOMAIN_VERIFICATION_SERVICE} ("domain_verification") + * <dd> A {@link android.content.pm.verify.domain.DomainVerificationManager} for accessing + * web domain approval state. * </dl> * * <p>Note: System services obtained via this API may be closely associated with @@ -3794,6 +3797,8 @@ public abstract class Context { * @see android.app.usage.NetworkStatsManager * @see android.os.HardwarePropertiesManager * @see #HARDWARE_PROPERTIES_SERVICE + * @see #DOMAIN_VERIFICATION_SERVICE + * @see android.content.pm.verify.domain.DomainVerificationManager */ public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name); @@ -3813,7 +3818,8 @@ public abstract class Context { * {@link android.view.inputmethod.InputMethodManager}, * {@link android.app.UiModeManager}, {@link android.app.DownloadManager}, * {@link android.os.BatteryManager}, {@link android.app.job.JobScheduler}, - * {@link android.app.usage.NetworkStatsManager}. + * {@link android.app.usage.NetworkStatsManager}, + * {@link android.content.pm.verify.domain.DomainVerificationManager}. * </p> * * <p> @@ -4833,10 +4839,20 @@ public abstract class Context { * @hide */ @TestApi - @SuppressLint("ServiceName") // TODO: This should be renamed to POWER_WHITELIST_SERVICE + @Deprecated + @SuppressLint("ServiceName") public static final String POWER_WHITELIST_MANAGER = "power_whitelist"; /** + * System service name for the PowerExemptionManager. + * + * @see #getSystemService(String) + * @hide + */ + @TestApi + public static final String POWER_EXEMPTION_SERVICE = "power_exemption"; + + /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.app.admin.DevicePolicyManager} for working with global * device policy management. @@ -5535,12 +5551,13 @@ public abstract class Context { public static final String GAME_SERVICE = "game"; /** - * Use with {@link #getSystemService(String)} to access domain verification service. + * Use with {@link #getSystemService(String)} to access + * {@link android.content.pm.verify.domain.DomainVerificationManager} to retrieve approval and + * user state for declared web domains. * * @see #getSystemService(String) - * @hide + * @see android.content.pm.verify.domain.DomainVerificationManager */ - @SystemApi public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification"; /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index de17fda82d71..c601aabb582b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2172,6 +2172,29 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.REVIEW_PERMISSION_USAGE"; /** + * Activity action: Launch UI to review the timeline history of permissions. + * <p> + * Input: {@link #EXTRA_PERMISSION_GROUP_NAME} specifies the permission group name + * that will be displayed by the launched UI. + * </p> + * <p> + * Output: Nothing. + * </p> + * <p class="note"> + * This requires {@link android.Manifest.permission#GRANT_RUNTIME_PERMISSIONS} permission. + * </p> + * + * @see #EXTRA_PERMISSION_GROUP_NAME + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REVIEW_PERMISSION_HISTORY = + "android.intent.action.REVIEW_PERMISSION_HISTORY"; + + /** * Activity action: Launch UI to review ongoing app uses of permissions. * <p> * Input: {@link #EXTRA_DURATION_MILLIS} specifies the minimum number of milliseconds of recent @@ -2331,6 +2354,7 @@ public class Intent implements Parcelable, Cloneable { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String ACTION_CLEAR_DNS_CACHE = "android.intent.action.CLEAR_DNS_CACHE"; /** * Alarm Changed Action: This is broadcast when the AlarmClock diff --git a/core/java/android/content/SyncAdapterType.java b/core/java/android/content/SyncAdapterType.java index 1c21b2aa73a5..47c333ceb931 100644 --- a/core/java/android/content/SyncAdapterType.java +++ b/core/java/android/content/SyncAdapterType.java @@ -17,6 +17,7 @@ package android.content; import android.annotation.Nullable; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -168,6 +169,7 @@ public class SyncAdapterType implements Parcelable { * * @hide */ + @TestApi public @Nullable String getPackageName() { return packageName; } diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 5a17753bf9ad..58f83a73ff16 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1086,6 +1086,13 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { */ public WindowLayout windowLayout; + /** + * Attribution tags for finer grained calls if a {@android.content.Context#sendBroadcast(Intent, + * String)} is used with a permission. + * @hide + */ + public String[] attributionTags; + public ActivityInfo() { } @@ -1114,6 +1121,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { maxAspectRatio = orig.maxAspectRatio; minAspectRatio = orig.minAspectRatio; supportsSizeChanges = orig.supportsSizeChanges; + attributionTags = orig.attributionTags; } /** @@ -1361,6 +1369,15 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { if (supportsSizeChanges) { pw.println(prefix + "supportsSizeChanges=true"); } + if (attributionTags != null && attributionTags.length > 0) { + StringBuilder tags = new StringBuilder(); + tags.append(attributionTags[0]); + for (int i = 1; i < attributionTags.length; i++) { + tags.append(", "); + tags.append(attributionTags[i]); + } + pw.println(prefix + "attributionTags=[" + tags + "]"); + } super.dumpBack(pw, prefix, dumpFlags); } @@ -1406,6 +1423,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { dest.writeFloat(maxAspectRatio); dest.writeFloat(minAspectRatio); dest.writeBoolean(supportsSizeChanges); + dest.writeString8Array(attributionTags); } /** @@ -1525,6 +1543,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { maxAspectRatio = source.readFloat(); minAspectRatio = source.readFloat(); supportsSizeChanges = source.readBoolean(); + attributionTags = source.createString8Array(); } /** diff --git a/core/java/android/content/pm/IDataLoaderStatusListener.aidl b/core/java/android/content/pm/IDataLoaderStatusListener.aidl index 745c39b460fa..79b70f2bd5ee 100644 --- a/core/java/android/content/pm/IDataLoaderStatusListener.aidl +++ b/core/java/android/content/pm/IDataLoaderStatusListener.aidl @@ -23,32 +23,34 @@ package android.content.pm; oneway interface IDataLoaderStatusListener { /** The DataLoader process died, binder disconnected or class destroyed. */ const int DATA_LOADER_DESTROYED = 0; + /** The system is in process of binding to the DataLoader. */ + const int DATA_LOADER_BINDING = 1; /** DataLoader process is running and bound to. */ - const int DATA_LOADER_BOUND = 1; + const int DATA_LOADER_BOUND = 2; /** DataLoader has handled onCreate(). */ - const int DATA_LOADER_CREATED = 2; + const int DATA_LOADER_CREATED = 3; /** DataLoader can receive missing pages and read pages notifications, * and ready to provide data. */ - const int DATA_LOADER_STARTED = 3; + const int DATA_LOADER_STARTED = 4; /** DataLoader no longer ready to provide data and is not receiving * any notifications from IncFS. */ - const int DATA_LOADER_STOPPED = 4; + const int DATA_LOADER_STOPPED = 5; /** DataLoader streamed everything necessary to continue installation. */ - const int DATA_LOADER_IMAGE_READY = 5; + const int DATA_LOADER_IMAGE_READY = 6; /** Installation can't continue as DataLoader failed to stream necessary data. */ - const int DATA_LOADER_IMAGE_NOT_READY = 6; + const int DATA_LOADER_IMAGE_NOT_READY = 7; /** DataLoader instance can't run at the moment, but might recover later. * It's up to system to decide if the app is still usable. */ - const int DATA_LOADER_UNAVAILABLE = 7; + const int DATA_LOADER_UNAVAILABLE = 8; /** DataLoader reports that this instance is invalid and can never be restored. * Warning: this is a terminal status that data loader should use carefully and * the system should almost never use - e.g. only if all recovery attempts * fail and all retry limits are exceeded. */ - const int DATA_LOADER_UNRECOVERABLE = 8; + const int DATA_LOADER_UNRECOVERABLE = 9; /** There are no known issues with the data stream. */ const int STREAM_HEALTHY = 0; diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java index 0d5b33cd8672..8f9a0d79f33b 100644 --- a/core/java/android/content/pm/dex/DexMetadataHelper.java +++ b/core/java/android/content/pm/dex/DexMetadataHelper.java @@ -29,6 +29,7 @@ import android.util.Log; import android.util.jar.StrictJarFile; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.security.VerityUtils; import java.io.File; import java.io.IOException; @@ -76,7 +77,8 @@ public class DexMetadataHelper { * Returns whether fs-verity is required to install a dex metadata */ public static boolean isFsVerityRequired() { - return SystemProperties.getBoolean(PROPERTY_DM_FSVERITY_REQUIRED, false); + return VerityUtils.isFsVeritySupported() + && SystemProperties.getBoolean(PROPERTY_DM_FSVERITY_REQUIRED, false); } /** diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java index 9a84ded99c67..b660a00443a4 100644 --- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java +++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java @@ -482,6 +482,7 @@ public class PackageInfoWithoutStateUtils { ai.rotationAnimation = a.getRotationAnimation(); ai.colorMode = a.getColorMode(); ai.windowLayout = a.getWindowLayout(); + ai.attributionTags = a.getAttributionTags(); if ((flags & PackageManager.GET_META_DATA) != 0) { ai.metaData = a.getMetaData(); } diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java index 6f478accedd7..9285ccb3cf0c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivity.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java @@ -82,6 +82,9 @@ public class ParsedActivity extends ParsedMainComponent { @Nullable ActivityInfo.WindowLayout windowLayout; + @Nullable + String[] attributionTags; + public ParsedActivity(ParsedActivity other) { super(other); this.theme = other.theme; @@ -107,6 +110,7 @@ public class ParsedActivity extends ParsedMainComponent { this.rotationAnimation = other.rotationAnimation; this.colorMode = other.colorMode; this.windowLayout = other.windowLayout; + this.attributionTags = other.attributionTags; } /** @@ -172,6 +176,7 @@ public class ParsedActivity extends ParsedMainComponent { alias.requestedVrComponent = target.requestedVrComponent; alias.directBootAware = target.directBootAware; alias.setProcessName(target.getProcessName()); + alias.attributionTags = target.attributionTags; return alias; // Not all attributes from the target ParsedActivity are copied to the alias. @@ -299,6 +304,7 @@ public class ParsedActivity extends ParsedMainComponent { } else { dest.writeBoolean(false); } + dest.writeString8Array(this.attributionTags); } public ParsedActivity() { @@ -332,6 +338,7 @@ public class ParsedActivity extends ParsedMainComponent { if (in.readBoolean()) { windowLayout = new ActivityInfo.WindowLayout(in); } + this.attributionTags = in.createString8Array(); } public static final Parcelable.Creator<ParsedActivity> CREATOR = new Creator<ParsedActivity>() { @@ -445,4 +452,9 @@ public class ParsedActivity extends ParsedMainComponent { public ActivityInfo.WindowLayout getWindowLayout() { return windowLayout; } + + @Nullable + public String[] getAttributionTags() { + return attributionTags; + } } diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java index 0f4aa061b72d..d99c4109e5ad 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java @@ -210,6 +210,11 @@ public class ParsedActivityUtils { pkg.setVisibleToInstantApps(true); } + String attributionTags = sa.getString(R.styleable.AndroidManifestActivity_attributionTags); + if (attributionTags != null) { + activity.attributionTags = attributionTags.split("\\|"); + } + return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver, false /*isAlias*/, visibleToEphemeral, input, R.styleable.AndroidManifestActivity_parentActivityName, diff --git a/core/java/android/ddm/DdmHandleHeap.java b/core/java/android/ddm/DdmHandleHeap.java index e24aeb2af3a1..8fa235294451 100644 --- a/core/java/android/ddm/DdmHandleHeap.java +++ b/core/java/android/ddm/DdmHandleHeap.java @@ -30,15 +30,7 @@ import java.nio.ByteBuffer; */ public class DdmHandleHeap extends ChunkHandler { - public static final int CHUNK_HPIF = type("HPIF"); - public static final int CHUNK_HPSG = type("HPSG"); - public static final int CHUNK_HPDU = type("HPDU"); - public static final int CHUNK_HPDS = type("HPDS"); - public static final int CHUNK_NHSG = type("NHSG"); public static final int CHUNK_HPGC = type("HPGC"); - public static final int CHUNK_REAE = type("REAE"); - public static final int CHUNK_REAQ = type("REAQ"); - public static final int CHUNK_REAL = type("REAL"); private static DdmHandleHeap mInstance = new DdmHandleHeap(); @@ -50,15 +42,7 @@ public class DdmHandleHeap extends ChunkHandler { * Register for the messages we're interested in. */ public static void register() { - DdmServer.registerHandler(CHUNK_HPIF, mInstance); - DdmServer.registerHandler(CHUNK_HPSG, mInstance); - DdmServer.registerHandler(CHUNK_HPDU, mInstance); - DdmServer.registerHandler(CHUNK_HPDS, mInstance); - DdmServer.registerHandler(CHUNK_NHSG, mInstance); DdmServer.registerHandler(CHUNK_HPGC, mInstance); - DdmServer.registerHandler(CHUNK_REAE, mInstance); - DdmServer.registerHandler(CHUNK_REAQ, mInstance); - DdmServer.registerHandler(CHUNK_REAL, mInstance); } /** @@ -81,24 +65,8 @@ public class DdmHandleHeap extends ChunkHandler { Log.v("ddm-heap", "Handling " + name(request.type) + " chunk"); int type = request.type; - if (type == CHUNK_HPIF) { - return handleHPIF(request); - } else if (type == CHUNK_HPSG) { - return handleHPSGNHSG(request, false); - } else if (type == CHUNK_HPDU) { - return handleHPDU(request); - } else if (type == CHUNK_HPDS) { - return handleHPDS(request); - } else if (type == CHUNK_NHSG) { - return handleHPSGNHSG(request, true); - } else if (type == CHUNK_HPGC) { + if (type == CHUNK_HPGC) { return handleHPGC(request); - } else if (type == CHUNK_REAE) { - return handleREAE(request); - } else if (type == CHUNK_REAQ) { - return handleREAQ(request); - } else if (type == CHUNK_REAL) { - return handleREAL(request); } else { throw new RuntimeException("Unknown packet " + ChunkHandler.name(type)); @@ -106,112 +74,6 @@ public class DdmHandleHeap extends ChunkHandler { } /* - * Handle a "HeaP InFo" request. - */ - private Chunk handleHPIF(Chunk request) { - ByteBuffer in = wrapChunk(request); - - int when = in.get(); - if (false) - Log.v("ddm-heap", "Heap segment enable: when=" + when); - - boolean ok = DdmVmInternal.heapInfoNotify(when); - if (!ok) { - return createFailChunk(1, "Unsupported HPIF what"); - } else { - return null; // empty response - } - } - - /* - * Handle a "HeaP SeGment" or "Native Heap SeGment" request. - */ - private Chunk handleHPSGNHSG(Chunk request, boolean isNative) { - ByteBuffer in = wrapChunk(request); - - int when = in.get(); - int what = in.get(); - if (false) - Log.v("ddm-heap", "Heap segment enable: when=" + when - + ", what=" + what + ", isNative=" + isNative); - - boolean ok = DdmVmInternal.heapSegmentNotify(when, what, isNative); - if (!ok) { - return createFailChunk(1, "Unsupported HPSG what/when"); - } else { - // TODO: if "when" is non-zero and we want to see a dump - // right away, initiate a GC. - return null; // empty response - } - } - - /* - * Handle a "HeaP DUmp" request. - * - * This currently just returns a result code. We could pull up - * the entire contents of the file and return them, but hprof dump - * files can be a few megabytes. - */ - private Chunk handleHPDU(Chunk request) { - ByteBuffer in = wrapChunk(request); - byte result; - - /* get the filename for the output file */ - int len = in.getInt(); - String fileName = getString(in, len); - if (false) - Log.d("ddm-heap", "Heap dump: file='" + fileName + "'"); - - try { - Debug.dumpHprofData(fileName); - result = 0; - } catch (UnsupportedOperationException uoe) { - Log.w("ddm-heap", "hprof dumps not supported in this VM"); - result = -1; - } catch (IOException ioe) { - result = -1; - } catch (RuntimeException re) { - result = -1; - } - - /* create a non-empty reply so the handler fires on completion */ - byte[] reply = { result }; - return new Chunk(CHUNK_HPDU, reply, 0, reply.length); - } - - /* - * Handle a "HeaP Dump Streaming" request. - * - * This tells the VM to create a heap dump and send it directly to - * DDMS. The dumps are large enough that we don't want to copy the - * data into a byte[] and send it from here. - */ - private Chunk handleHPDS(Chunk request) { - ByteBuffer in = wrapChunk(request); - byte result; - - /* get the filename for the output file */ - if (false) - Log.d("ddm-heap", "Heap dump: [DDMS]"); - - String failMsg = null; - try { - Debug.dumpHprofDataDdms(); - } catch (UnsupportedOperationException uoe) { - failMsg = "hprof dumps not supported in this VM"; - } catch (RuntimeException re) { - failMsg = "Exception: " + re.getMessage(); - } - - if (failMsg != null) { - Log.w("ddm-heap", failMsg); - return createFailChunk(1, failMsg); - } else { - return null; - } - } - - /* * Handle a "HeaP Garbage Collection" request. */ private Chunk handleHPGC(Chunk request) { @@ -223,47 +85,4 @@ public class DdmHandleHeap extends ChunkHandler { return null; // empty response } - - /* - * Handle a "REcent Allocation Enable" request. - */ - private Chunk handleREAE(Chunk request) { - ByteBuffer in = wrapChunk(request); - boolean enable; - - enable = (in.get() != 0); - - if (false) - Log.d("ddm-heap", "Recent allocation enable request: " + enable); - - DdmVmInternal.enableRecentAllocations(enable); - - return null; // empty response - } - - /* - * Handle a "REcent Allocation Query" request. - */ - private Chunk handleREAQ(Chunk request) { - //ByteBuffer in = wrapChunk(request); - - byte[] reply = new byte[1]; - reply[0] = DdmVmInternal.getRecentAllocationStatus() ? (byte)1 :(byte)0; - return new Chunk(CHUNK_REAQ, reply, 0, reply.length); - } - - /* - * Handle a "REcent ALlocations" request. - */ - private Chunk handleREAL(Chunk request) { - //ByteBuffer in = wrapChunk(request); - - if (false) - Log.d("ddm-heap", "Recent allocations request"); - - /* generate the reply in a ready-to-go format */ - byte[] reply = DdmVmInternal.getRecentAllocations(); - return new Chunk(CHUNK_REAL, reply, 0, reply.length); - } } - diff --git a/core/java/android/ddm/DdmHandleThread.java b/core/java/android/ddm/DdmHandleThread.java deleted file mode 100644 index 613ab75f9c1b..000000000000 --- a/core/java/android/ddm/DdmHandleThread.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.ddm; - -import org.apache.harmony.dalvik.ddmc.Chunk; -import org.apache.harmony.dalvik.ddmc.ChunkHandler; -import org.apache.harmony.dalvik.ddmc.DdmServer; -import org.apache.harmony.dalvik.ddmc.DdmVmInternal; -import android.util.Log; -import java.nio.ByteBuffer; - -/** - * Handle thread-related traffic. - */ -public class DdmHandleThread extends ChunkHandler { - - public static final int CHUNK_THEN = type("THEN"); - public static final int CHUNK_THCR = type("THCR"); - public static final int CHUNK_THDE = type("THDE"); - public static final int CHUNK_THST = type("THST"); - public static final int CHUNK_STKL = type("STKL"); - - private static DdmHandleThread mInstance = new DdmHandleThread(); - - - /* singleton, do not instantiate */ - private DdmHandleThread() {} - - /** - * Register for the messages we're interested in. - */ - public static void register() { - DdmServer.registerHandler(CHUNK_THEN, mInstance); - DdmServer.registerHandler(CHUNK_THST, mInstance); - DdmServer.registerHandler(CHUNK_STKL, mInstance); - } - - /** - * Called when the DDM server connects. The handler is allowed to - * send messages to the server. - */ - public void connected() {} - - /** - * Called when the DDM server disconnects. Can be used to disable - * periodic transmissions or clean up saved state. - */ - public void disconnected() {} - - /** - * Handle a chunk of data. - */ - public Chunk handleChunk(Chunk request) { - if (false) - Log.v("ddm-thread", "Handling " + name(request.type) + " chunk"); - int type = request.type; - - if (type == CHUNK_THEN) { - return handleTHEN(request); - } else if (type == CHUNK_THST) { - return handleTHST(request); - } else if (type == CHUNK_STKL) { - return handleSTKL(request); - } else { - throw new RuntimeException("Unknown packet " - + ChunkHandler.name(type)); - } - } - - /* - * Handle a "THread notification ENable" request. - */ - private Chunk handleTHEN(Chunk request) { - ByteBuffer in = wrapChunk(request); - - boolean enable = (in.get() != 0); - //Log.i("ddm-thread", "Thread notify enable: " + enable); - - DdmVmInternal.threadNotify(enable); - return null; // empty response - } - - /* - * Handle a "THread STatus" request. This is constructed by the VM. - */ - private Chunk handleTHST(Chunk request) { - ByteBuffer in = wrapChunk(request); - // currently nothing to read from "in" - - //Log.d("ddm-thread", "Thread status request"); - - byte[] status = DdmVmInternal.getThreadStats(); - if (status != null) - return new Chunk(CHUNK_THST, status, 0, status.length); - else - return createFailChunk(1, "Can't build THST chunk"); - } - - /* - * Handle a STacK List request. - * - * This is done by threadId, which isn't great since those are - * recycled. We need a thread serial ID. The Linux tid is an okay - * answer as it's unlikely to recycle at the exact wrong moment. - * However, we're using the short threadId in THST messages, so we - * use them here for consistency. (One thought is to keep the current - * thread ID in the low 16 bits and somehow serialize the top 16 bits.) - */ - private Chunk handleSTKL(Chunk request) { - ByteBuffer in = wrapChunk(request); - int threadId; - - threadId = in.getInt(); - - //Log.d("ddm-thread", "Stack list request " + threadId); - - StackTraceElement[] trace = DdmVmInternal.getStackTraceById(threadId); - if (trace == null) { - return createFailChunk(1, "Stack trace unavailable"); - } else { - return createStackChunk(trace, threadId); - } - } - - /* - * Serialize a StackTraceElement[] into an STKL chunk. - * - * We include the threadId in the response so the other side doesn't have - * to match up requests and responses as carefully. - */ - private Chunk createStackChunk(StackTraceElement[] trace, int threadId) { - int bufferSize = 0; - - bufferSize += 4; // version, flags, whatever - bufferSize += 4; // thread ID - bufferSize += 4; // frame count - for (StackTraceElement elem : trace) { - bufferSize += 4 + elem.getClassName().length() * 2; - bufferSize += 4 + elem.getMethodName().length() * 2; - bufferSize += 4; - if (elem.getFileName() != null) - bufferSize += elem.getFileName().length() * 2; - bufferSize += 4; // line number - } - - ByteBuffer out = ByteBuffer.allocate(bufferSize); - out.putInt(0); - out.putInt(threadId); - out.putInt(trace.length); - for (StackTraceElement elem : trace) { - out.putInt(elem.getClassName().length()); - putString(out, elem.getClassName()); - out.putInt(elem.getMethodName().length()); - putString(out, elem.getMethodName()); - if (elem.getFileName() != null) { - out.putInt(elem.getFileName().length()); - putString(out, elem.getFileName()); - } else { - out.putInt(0); - } - out.putInt(elem.getLineNumber()); - } - - return new Chunk(CHUNK_STKL, out); - } -} - diff --git a/core/java/android/ddm/DdmRegister.java b/core/java/android/ddm/DdmRegister.java index e0faa51a938e..ca1031287e3e 100644 --- a/core/java/android/ddm/DdmRegister.java +++ b/core/java/android/ddm/DdmRegister.java @@ -16,9 +16,10 @@ package android.ddm; -import org.apache.harmony.dalvik.ddmc.DdmServer; import android.util.Log; +import org.apache.harmony.dalvik.ddmc.DdmServer; + /** * Just a place to stick handler registrations, instead of scattering * them around. @@ -46,7 +47,6 @@ public class DdmRegister { if (false) Log.v("ddm", "Registering DDM message handlers"); DdmHandleHello.register(); - DdmHandleThread.register(); DdmHandleHeap.register(); DdmHandleNativeHeap.register(); DdmHandleProfiling.register(); diff --git a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java index 25758e9d9a61..8c7695ad5a5a 100644 --- a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java +++ b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java @@ -72,6 +72,45 @@ public final class FontFamilyUpdateRequest { * A font family definition. */ public static final class FontFamily { + + /** + * Builds a {@link FontFamily}. + */ + public static final class Builder { + @NonNull private final String mName; + @NonNull private final List<Font> mFonts; + + /** + * Constructs a {@link FontFamily.Builder}. + */ + public Builder(@NonNull String name, @NonNull List<Font> fonts) { + Objects.requireNonNull(name); + Preconditions.checkStringNotEmpty(name); + Objects.requireNonNull(fonts); + Preconditions.checkCollectionElementsNotNull(fonts, "fonts"); + Preconditions.checkCollectionNotEmpty(fonts, "fonts"); + mName = name; + mFonts = new ArrayList<>(fonts); + } + + /** + * Adds a {@link Font} to the builder. + * + * @return This builder object. + */ + public @NonNull Builder addFont(@NonNull Font font) { + mFonts.add(font); + return this; + } + + /** + * Builds a {@link FontFamily}. + */ + public @NonNull FontFamily build() { + return new FontFamily(mName, mFonts); + } + } + @NonNull private final String mName; @NonNull @@ -90,12 +129,7 @@ public final class FontFamilyUpdateRequest { * @see android.graphics.Typeface#create(String, int) * @see Font */ - public FontFamily(@NonNull String name, @NonNull List<Font> fonts) { - Objects.requireNonNull(name); - Preconditions.checkStringNotEmpty(name); - Objects.requireNonNull(fonts); - Preconditions.checkCollectionElementsNotNull(fonts, "fonts"); - Preconditions.checkCollectionNotEmpty(fonts, "fonts"); + private FontFamily(@NonNull String name, @NonNull List<Font> fonts) { mName = name; mFonts = fonts; } diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java index 7bf692f1d318..fa2ccbc189ad 100644 --- a/core/java/android/graphics/fonts/FontManager.java +++ b/core/java/android/graphics/fonts/FontManager.java @@ -195,6 +195,7 @@ public class FontManager { * @return The current font configuration. null if failed to fetch information from the system * service. */ + @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @NonNull FontConfig getFontConfig() { try { return mIFontManager.getFontConfig(); diff --git a/core/java/android/hardware/SensorPrivacyManagerInternal.java b/core/java/android/hardware/SensorPrivacyManagerInternal.java new file mode 100644 index 000000000000..d12e9f8418bc --- /dev/null +++ b/core/java/android/hardware/SensorPrivacyManagerInternal.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware; + +/** + * SensorPrivacyManager calls for within the system server + * @hide + */ +public abstract class SensorPrivacyManagerInternal { + + /** + * A class implementing this interface can register to receive a callback when state changes. + */ + public interface OnSensorPrivacyChangedListener { + /** + * The callback invoked when the state changes. + */ + void onSensorPrivacyChanged(boolean enabled); + } + + /** + * A class implementing this interface can register to receive a callback when state changes for + * any user. + */ + public interface OnUserSensorPrivacyChangedListener { + /** + * The callback invoked when the state changes. + */ + void onSensorPrivacyChanged(int userId, boolean enabled); + } + + /** + * Get the individual sensor privacy state for a given user. + */ + public abstract boolean isSensorPrivacyEnabled(int userId, int sensor); + + /** + * Registers a new listener to receive notification when the state of sensor privacy + * changes. + */ + public abstract void addSensorPrivacyListener(int userId, int sensor, + OnSensorPrivacyChangedListener listener); + + /** + * Registers a new listener to receive notification when the state of sensor privacy + * changes for any user. + */ + public abstract void addSensorPrivacyListenerForAllUsers(int sensor, + OnUserSensorPrivacyChangedListener listener); +} diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 788afe3bdb8e..365dea691489 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -65,7 +65,7 @@ public class SystemSensorManager extends SensorManager { private static final int CAPPED_SAMPLING_RATE_LEVEL = SensorDirectChannel.RATE_NORMAL; private static final String HIGH_SAMPLING_RATE_SENSORS_PERMISSION = - "android.permisison.HIGH_SAMPLING_RATE_SENSORS"; + "android.permission.HIGH_SAMPLING_RATE_SENSORS"; /** * For apps targeting S and above, a SecurityException is thrown when they do not have * HIGH_SAMPLING_RATE_SENSORS permission, run in debug mode, and request sampling rates that diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java index 7dc1eaabdc9c..93e5a0ea18f3 100644 --- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java +++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java @@ -52,7 +52,8 @@ public class AmbientDisplayConfiguration { || wakeDisplayGestureEnabled(user) || pickupGestureEnabled(user) || tapGestureEnabled(user) - || doubleTapGestureEnabled(user); + || doubleTapGestureEnabled(user) + || quickPickupSensorEnabled(user); } /** {@hide} */ @@ -100,6 +101,13 @@ public class AmbientDisplayConfiguration { } /** {@hide} */ + public boolean quickPickupSensorEnabled(int user) { + return boolSettingDefaultOff(Settings.Secure.DOZE_QUICK_PICKUP_GESTURE, user) + && !TextUtils.isEmpty(quickPickupSensorType()) + && !alwaysOnEnabled(user); + } + + /** {@hide} */ public boolean wakeScreenGestureAvailable() { return mContext.getResources() .getBoolean(R.bool.config_dozeWakeLockScreenSensorAvailable); @@ -143,6 +151,11 @@ public class AmbientDisplayConfiguration { } /** {@hide} */ + public String quickPickupSensorType() { + return mContext.getResources().getString(R.string.config_quickPickupSensorType); + } + + /** {@hide} */ public boolean pulseOnLongPressEnabled(int user) { return pulseOnLongPressAvailable() && boolSettingDefaultOff( Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, user); diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index b90c72832d36..ad71f15f6e26 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -461,6 +461,7 @@ public final class HdmiControlManager { * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE * @hide */ + @SystemApi public static final int VOLUME_CONTROL_ENABLED = 1; /** * HDMI CEC disabled. @@ -468,6 +469,7 @@ public final class HdmiControlManager { * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE * @hide */ + @SystemApi public static final int VOLUME_CONTROL_DISABLED = 0; /** * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE @@ -486,12 +488,14 @@ public final class HdmiControlManager { * * @hide */ + @SystemApi public static final int TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED = 1; /** * TV Wake on One Touch Play disabled. * * @hide */ + @SystemApi public static final int TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED = 0; /** * @hide @@ -509,12 +513,14 @@ public final class HdmiControlManager { * * @hide */ + @SystemApi public static final int TV_SEND_STANDBY_ON_SLEEP_ENABLED = 1; /** * Not sending <Standby> on sleep. * * @hide */ + @SystemApi public static final int TV_SEND_STANDBY_ON_SLEEP_DISABLED = 0; /** * @hide @@ -759,6 +765,7 @@ public final class HdmiControlManager { * @hide * @see android.hardware.hdmi.HdmiControlManager#setHdmiCecVolumeControlEnabled(int) */ + @SystemApi public static final String CEC_SETTING_NAME_VOLUME_CONTROL_MODE = "volume_control_enabled"; /** @@ -767,6 +774,7 @@ public final class HdmiControlManager { * * @hide */ + @SystemApi public static final String CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY = "tv_wake_on_one_touch_play"; /** @@ -775,6 +783,7 @@ public final class HdmiControlManager { * * @hide */ + @SystemApi public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP = "tv_send_standby_on_sleep"; /** @@ -1259,6 +1268,7 @@ public final class HdmiControlManager { * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setHdmiCecVolumeControlEnabled( @VolumeControl int hdmiCecVolumeControlEnabled) { @@ -1274,6 +1284,7 @@ public final class HdmiControlManager { * Returns whether volume changes via HDMI CEC are enabled. * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.HDMI_CEC) @VolumeControl public int getHdmiCecVolumeControlEnabled() { @@ -2155,6 +2166,7 @@ public final class HdmiControlManager { * * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setTvWakeOnOneTouchPlay(@NonNull @TvWakeOnOneTouchPlay int value) { if (mService == null) { @@ -2176,6 +2188,7 @@ public final class HdmiControlManager { * * @hide */ + @SystemApi @NonNull @TvWakeOnOneTouchPlay @RequiresPermission(android.Manifest.permission.HDMI_CEC) @@ -2199,6 +2212,7 @@ public final class HdmiControlManager { * * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setTvSendStandbyOnSleep(@NonNull @TvSendStandbyOnSleep int value) { if (mService == null) { @@ -2220,6 +2234,7 @@ public final class HdmiControlManager { * * @hide */ + @SystemApi @NonNull @TvSendStandbyOnSleep @RequiresPermission(android.Manifest.permission.HDMI_CEC) diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index a65f36b14f13..eaa8bd403e24 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -60,7 +60,8 @@ public final class ContextHubManager { private static final String TAG = "ContextHubManager"; /** - * An extra of type int describing the client's authorization state. + * An extra containing an int from {@link AuthorizationState} describing the client's + * authorization state. */ public static final String EXTRA_CLIENT_AUTHORIZATION_STATE = "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE"; @@ -115,11 +116,9 @@ public final class ContextHubManager { /** * Indicates the {@link ContextHubClient} will soon lose its authorization to communicate with a - * nanoapp. The {@link ContextHubClient} must perform any cleanup with the nanoapp as soon as - * possible. - * - * Note that the time between this state event and {@link AUTHORIZATION_DENIED} must be enough - * for the {@link ContextHubClient} to send at least one message to the nanoapp. + * nanoapp. After receiving this state event, the {@link ContextHubClient} has one minute to + * perform any cleanup with the nanoapp such that the nanoapp is no longer performing work on + * behalf of the {@link ContextHubClient}. */ public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 9198eb74d1f8..5cfcd667632b 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -171,7 +171,7 @@ class IInputMethodWrapper extends IInputMethod.Stub SomeArgs args = (SomeArgs) msg.obj; try { inputMethod.initializeInternal((IBinder) args.arg1, msg.arg1, - (IInputMethodPrivilegedOperations) args.arg2, (int) args.arg3); + (IInputMethodPrivilegedOperations) args.arg2); } finally { args.recycle(); } @@ -280,10 +280,9 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void initializeInternal(IBinder token, int displayId, - IInputMethodPrivilegedOperations privOps, int configChanges) { + IInputMethodPrivilegedOperations privOps) { mCaller.executeOrSendMessage( - mCaller.obtainMessageIOOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps, - configChanges)); + mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 40a0fc4e8339..7e2be01feb01 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -70,7 +70,6 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -132,7 +131,6 @@ import android.widget.TextView; import android.window.WindowMetricsHelper; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodPrivilegedOperations; @@ -515,8 +513,6 @@ public class InputMethodService extends AbstractInputMethodService { private boolean mIsAutomotive; private Handler mHandler; private boolean mImeSurfaceScheduledForRemoval; - private Configuration mLastKnownConfig; - private int mHandledConfigChanges; /** * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput} @@ -592,13 +588,12 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public final void initializeInternal(@NonNull IBinder token, int displayId, - IInputMethodPrivilegedOperations privilegedOperations, int configChanges) { + IInputMethodPrivilegedOperations privilegedOperations) { if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) { Log.w(TAG, "The token has already registered, ignore this initialization."); return; } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal"); - mHandledConfigChanges = configChanges; mPrivOps.set(privilegedOperations); InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps); updateInputMethodDisplay(displayId); @@ -826,9 +821,6 @@ public class InputMethodService extends AbstractInputMethodService { setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); } final boolean isVisible = isInputViewShown(); - if (isVisible && getResources() != null) { - mLastKnownConfig = new Configuration(getResources().getConfiguration()); - } final boolean visibilityChanged = isVisible != wasVisible; if (resultReceiver != null) { resultReceiver.send(visibilityChanged @@ -1436,30 +1428,10 @@ public class InputMethodService extends AbstractInputMethodService { * state: {@link #onStartInput} if input is active, and * {@link #onCreateInputView} and {@link #onStartInputView} and related * appropriate functions if the UI is displayed. - * <p>Starting with {@link Build.VERSION_CODES#S}, IMEs can opt into handling configuration - * changes themselves instead of being restarted with - * {@link android.R.styleable#InputMethod_configChanges}. */ @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - if (shouldImeRestartForConfig(newConfig)) { - resetStateForNewConfiguration(); - } - } - - /** - * @return {@code true} if {@link InputMethodService} needs to restart to handle - * .{@link #onConfigurationChanged(Configuration)} - */ - @VisibleForTesting - boolean shouldImeRestartForConfig(@NonNull Configuration newConfig) { - if (mLastKnownConfig == null) { - return true; - } - // If the new config is the same as the config this Service is already running with, - // then don't bother calling resetStateForNewConfiguration. - int unhandledDiff = (mLastKnownConfig.diffPublicOnly(newConfig) & ~mHandledConfigChanges); - return unhandledDiff != 0; + resetStateForNewConfiguration(); } private void resetStateForNewConfiguration() { @@ -3209,17 +3181,7 @@ public class InputMethodService extends AbstractInputMethodService { requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS); } } - - @VisibleForTesting - void setLastKnownConfig(@NonNull Configuration config) { - mLastKnownConfig = config; - } - - @VisibleForTesting - void setHandledConfigChanges(int configChanges) { - mHandledConfigChanges = configChanges; - } - + void startExtractingText(boolean inputChanged) { final ExtractEditText eet = mExtractEditText; if (eet != null && getCurrentInputStarted() diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 8ebf757760c3..062438c6e5db 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -73,7 +73,8 @@ import java.util.concurrent.Executor; public class VcnManager { @NonNull private static final String TAG = VcnManager.class.getSimpleName(); - private static final Map<VcnNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder> + private static final Map< + VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder> REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>(); @NonNull private final Context mContext; @@ -93,13 +94,13 @@ public class VcnManager { } /** - * Get all currently registered VcnNetworkPolicyListeners for testing purposes. + * Get all currently registered VcnNetworkPolicyChangeListeners for testing purposes. * * @hide */ @VisibleForTesting(visibility = Visibility.PRIVATE) @NonNull - public static Map<VcnNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder> + public static Map<VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder> getAllPolicyListeners() { return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS); } @@ -162,14 +163,14 @@ public class VcnManager { } // TODO(b/180537630): remove all VcnUnderlyingNetworkPolicyListener refs once Telephony is using - // the new VcnNetworkPolicyListener API + // the new VcnNetworkPolicyChangeListener API /** * VcnUnderlyingNetworkPolicyListener is the interface through which internal system components * can register to receive updates for VCN-underlying Network policies from the System Server. * * @hide */ - public interface VcnUnderlyingNetworkPolicyListener extends VcnNetworkPolicyListener {} + public interface VcnUnderlyingNetworkPolicyListener extends VcnNetworkPolicyChangeListener {} /** * Add a listener for VCN-underlying network policy updates. @@ -185,7 +186,7 @@ public class VcnManager { @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnUnderlyingNetworkPolicyListener( @NonNull Executor executor, @NonNull VcnUnderlyingNetworkPolicyListener listener) { - addVcnNetworkPolicyListener(executor, listener); + addVcnNetworkPolicyChangeListener(executor, listener); } /** @@ -198,7 +199,7 @@ public class VcnManager { */ public void removeVcnUnderlyingNetworkPolicyListener( @NonNull VcnUnderlyingNetworkPolicyListener listener) { - removeVcnNetworkPolicyListener(listener); + removeVcnNetworkPolicyChangeListener(listener); } /** @@ -233,20 +234,20 @@ public class VcnManager { } /** - * VcnNetworkPolicyListener is the interface through which internal system components (e.g. - * Network Factories) can register to receive updates for VCN-underlying Network policies from - * the System Server. + * VcnNetworkPolicyChangeListener is the interface through which internal system components + * (e.g. Network Factories) can register to receive updates for VCN-underlying Network policies + * from the System Server. * * <p>Any Network Factory that brings up Networks capable of being VCN-underlying Networks - * should register a VcnNetworkPolicyListener. VcnManager will then use this listener to notify - * the registrant when VCN Network policies change. Upon receiving this signal, the listener - * must check {@link VcnManager} for the current Network policy result for each of its Networks - * via {@link #applyVcnNetworkPolicy(NetworkCapabilities, LinkProperties)}. + * should register a VcnNetworkPolicyChangeListener. VcnManager will then use this listener to + * notify the registrant when VCN Network policies change. Upon receiving this signal, the + * listener must check {@link VcnManager} for the current Network policy result for each of its + * Networks via {@link #applyVcnNetworkPolicy(NetworkCapabilities, LinkProperties)}. * * @hide */ @SystemApi - public interface VcnNetworkPolicyListener { + public interface VcnNetworkPolicyChangeListener { /** * Notifies the implementation that the VCN's underlying Network policy has changed. * @@ -260,20 +261,21 @@ public class VcnManager { /** * Add a listener for VCN-underlying Network policy updates. * - * <p>A {@link VcnNetworkPolicyListener} is eligible to begin receiving callbacks once it is - * registered. No callbacks are guaranteed upon registration. + * <p>A {@link VcnNetworkPolicyChangeListener} is eligible to begin receiving callbacks once it + * is registered. No callbacks are guaranteed upon registration. * * @param executor the Executor that will be used for invoking all calls to the specified * Listener - * @param listener the VcnNetworkPolicyListener to be added + * @param listener the VcnNetworkPolicyChangeListener to be added * @throws SecurityException if the caller does not have permission NETWORK_FACTORY - * @throws IllegalStateException if the specified VcnNetworkPolicyListener is already registered + * @throws IllegalStateException if the specified VcnNetworkPolicyChangeListener is already + * registered * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) - public void addVcnNetworkPolicyListener( - @NonNull Executor executor, @NonNull VcnNetworkPolicyListener listener) { + public void addVcnNetworkPolicyChangeListener( + @NonNull Executor executor, @NonNull VcnNetworkPolicyChangeListener listener) { requireNonNull(executor, "executor must not be null"); requireNonNull(listener, "listener must not be null"); @@ -292,15 +294,18 @@ public class VcnManager { } /** - * Remove the specified VcnNetworkPolicyListener from VcnManager. + * Remove the specified VcnNetworkPolicyChangeListener from VcnManager. * * <p>If the specified listener is not currently registered, this is a no-op. * - * @param listener the VcnNetworkPolicyListener that will be removed + * @param listener the VcnNetworkPolicyChangeListener that will be removed + * @throws SecurityException if the caller does not have permission NETWORK_FACTORY * @hide */ @SystemApi - public void removeVcnNetworkPolicyListener(@NonNull VcnNetworkPolicyListener listener) { + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void removeVcnNetworkPolicyChangeListener( + @NonNull VcnNetworkPolicyChangeListener listener) { requireNonNull(listener, "listener must not be null"); VcnUnderlyingNetworkPolicyListenerBinder binder = @@ -320,8 +325,9 @@ public class VcnManager { * Applies the network policy for a {@link android.net.Network} with the given parameters. * * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy - * may have changed via {@link VcnNetworkPolicyListener#onPolicyChanged()}, a Network Provider - * MUST poll for the updated Network policy based on that Network's capabilities and properties. + * may have changed via {@link VcnNetworkPolicyChangeListener#onPolicyChanged()}, a Network + * Provider MUST poll for the updated Network policy based on that Network's capabilities and + * properties. * * @param networkCapabilities the NetworkCapabilities to be used in determining the Network * policy result for this Network. @@ -532,17 +538,18 @@ public class VcnManager { } /** - * Binder wrapper for added VcnNetworkPolicyListeners to receive signals from System Server. + * Binder wrapper for added VcnNetworkPolicyChangeListeners to receive signals from System + * Server. * * @hide */ private static class VcnUnderlyingNetworkPolicyListenerBinder extends IVcnUnderlyingNetworkPolicyListener.Stub { @NonNull private final Executor mExecutor; - @NonNull private final VcnNetworkPolicyListener mListener; + @NonNull private final VcnNetworkPolicyChangeListener mListener; private VcnUnderlyingNetworkPolicyListenerBinder( - Executor executor, VcnNetworkPolicyListener listener) { + Executor executor, VcnNetworkPolicyChangeListener listener) { mExecutor = executor; mListener = listener; } diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java index a86237dd271f..97ec5940ccb0 100644 --- a/core/java/android/os/BatteryManagerInternal.java +++ b/core/java/android/os/BatteryManagerInternal.java @@ -83,4 +83,29 @@ public abstract class BatteryManagerInternal { * wait on the battery service lock. */ public abstract int getInvalidCharger(); + + /** + * Sets battery AC charger to enabled/disabled, and freezes the battery state. + */ + public abstract void setChargerAcOnline(boolean online, boolean forceUpdate); + + /** + * Sets battery level, and freezes the battery state. + */ + public abstract void setBatteryLevel(int level, boolean forceUpdate); + + /** + * Unplugs battery, and freezes the battery state. + */ + public abstract void unplugBattery(boolean forceUpdate); + + /** + * Unfreezes battery state, returning to current hardware values. + */ + public abstract void resetBattery(boolean forceUpdate); + + /** + * Suspend charging even if plugged in. + */ + public abstract void suspendBatteryInput(); } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 66f7bd9d8dee..4c26e2f33fb2 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -995,6 +995,15 @@ public abstract class BatteryStats implements Parcelable { public abstract long getScreenOnMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the uid's cpu usage, derived from + * on device power measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getCpuMeasuredBatteryConsumptionUC(); + + /** * Returns the battery consumption (in microcoulombs) used by this uid for each * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}). @@ -2521,6 +2530,15 @@ public abstract class BatteryStats implements Parcelable { public abstract long getScreenDozeMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the cpu, derived from on device power + * measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getCpuMeasuredBatteryConsumptionUC(); + + /** * Returns the battery consumption (in microcoulombs) that each * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}) consumed. diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index 1905d708d6d3..e47478abf439 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.net.NetworkStack; import android.os.connectivity.CellularBatteryStats; @@ -487,4 +488,74 @@ public final class BatteryStatsManager { return isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; } -} + + /** + * Sets battery AC charger to enabled/disabled, and freezes the battery state. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public void setChargerAcOnline(boolean online, boolean forceUpdate) { + try { + mBatteryStats.setChargerAcOnline(online, forceUpdate); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Sets battery level, and freezes the battery state. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public void setBatteryLevel(int level, boolean forceUpdate) { + try { + mBatteryStats.setBatteryLevel(level, forceUpdate); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Unplugs battery, and freezes the battery state. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public void unplugBattery(boolean forceUpdate) { + try { + mBatteryStats.unplugBattery(forceUpdate); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Unfreezes battery state, returning to current hardware values. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public void resetBattery(boolean forceUpdate) { + try { + mBatteryStats.resetBattery(forceUpdate); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Suspend charging even if plugged in. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public void suspendBatteryInput() { + try { + mBatteryStats.suspendBatteryInput(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } +}
\ No newline at end of file diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 83f78a56487a..0d9f715c11d4 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -27,6 +27,7 @@ import android.app.ActivityThread; import android.app.Application; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.sysprop.DeviceProperties; import android.sysprop.SocProperties; import android.sysprop.TelephonyProperties; import android.text.TextUtils; @@ -298,6 +299,19 @@ public class Build { "ro.build.version.security_patch", ""); /** + * The media performance class of the device or 0 if none. + * <p> + * If this value is not <code>0</code>, the device conforms to the media performance class + * definition of the SDK version of this value. This value never changes while a device is + * booted, but it may increase when the hardware manufacturer provides an OTA update. + * <p> + * Possible non-zero values are defined in {@link Build.VERSION_CODES} starting with + * {@link Build.VERSION_CODES#S}. + */ + public static final int MEDIA_PERFORMANCE_CLASS = + DeviceProperties.media_performance_class().orElse(0); + + /** * The user-visible SDK version of the framework in its raw String * representation; use {@link #SDK_INT} instead. * diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 124c0b00b2b2..21bf8b8c30e4 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -728,11 +728,11 @@ public class Environment { /** * Standard directory in which to place any audio files that should be * in the regular list of music for the user. - * This may be combined with + * This may be combined with {@link #DIRECTORY_AUDIOBOOKS}, * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, - * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series - * of directories to categories a particular audio file as more than one - * type. + * {@link #DIRECTORY_ALARMS}, {@link #DIRECTORY_RINGTONES}, and + * {@link #DIRECTORY_RECORDINGS} as a series of directories to + * categorize a particular audio file as more than one type. */ public static String DIRECTORY_MUSIC = "Music"; @@ -741,10 +741,10 @@ public class Environment { * in the list of podcasts that the user can select (not as regular * music). * This may be combined with {@link #DIRECTORY_MUSIC}, - * {@link #DIRECTORY_NOTIFICATIONS}, - * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series - * of directories to categories a particular audio file as more than one - * type. + * {@link #DIRECTORY_AUDIOBOOKS}, {@link #DIRECTORY_NOTIFICATIONS}, + * {@link #DIRECTORY_ALARMS}, {@link #DIRECTORY_RINGTONES}, and + * {@link #DIRECTORY_RECORDINGS} as a series of directories to + * categorize a particular audio file as more than one type. */ public static String DIRECTORY_PODCASTS = "Podcasts"; @@ -753,10 +753,10 @@ public class Environment { * in the list of ringtones that the user can select (not as regular * music). * This may be combined with {@link #DIRECTORY_MUSIC}, - * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, and - * {@link #DIRECTORY_ALARMS} as a series - * of directories to categories a particular audio file as more than one - * type. + * {@link #DIRECTORY_AUDIOBOOKS}, {@link #DIRECTORY_PODCASTS}, + * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_ALARMS}, + * and {@link #DIRECTORY_RECORDINGS} as a series of directories + * to categorize a particular audio file as more than one type. */ public static String DIRECTORY_RINGTONES = "Ringtones"; @@ -765,10 +765,10 @@ public class Environment { * in the list of alarms that the user can select (not as regular * music). * This may be combined with {@link #DIRECTORY_MUSIC}, - * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, - * and {@link #DIRECTORY_RINGTONES} as a series - * of directories to categories a particular audio file as more than one - * type. + * {@link #DIRECTORY_AUDIOBOOKS}, {@link #DIRECTORY_PODCASTS}, + * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_RINGTONES}, + * and {@link #DIRECTORY_RECORDINGS} as a series of directories + * to categorize a particular audio file as more than one type. */ public static String DIRECTORY_ALARMS = "Alarms"; @@ -777,10 +777,10 @@ public class Environment { * in the list of notifications that the user can select (not as regular * music). * This may be combined with {@link #DIRECTORY_MUSIC}, - * {@link #DIRECTORY_PODCASTS}, - * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series - * of directories to categories a particular audio file as more than one - * type. + * {@link #DIRECTORY_AUDIOBOOKS}, {@link #DIRECTORY_PODCASTS}, + * {@link #DIRECTORY_ALARMS}, {@link #DIRECTORY_RINGTONES}, and + * {@link #DIRECTORY_RECORDINGS} as a series of directories to + * categorize a particular audio file as more than one type. */ public static String DIRECTORY_NOTIFICATIONS = "Notifications"; @@ -831,14 +831,26 @@ public class Environment { public static String DIRECTORY_SCREENSHOTS = "Screenshots"; /** - * Standard directory in which to place any audio files which are - * audiobooks. + * Standard directory in which to place any audio files that should be + * in the list of audiobooks that the user can select (not as regular + * music). + * This may be combined with {@link #DIRECTORY_MUSIC}, + * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, + * {@link #DIRECTORY_ALARMS}, {@link #DIRECTORY_RINGTONES}, + * and {@link #DIRECTORY_RECORDINGS} as a series of directories + * to categorize a particular audio file as more than one type. */ public static String DIRECTORY_AUDIOBOOKS = "Audiobooks"; /** - * Standard directory in which to place any audio files which are - * recordings. + * Standard directory in which to place any audio files that should be + * in the list of voice recordings recorded by voice recorder apps that + * the user can select (not as regular music). + * This may be combined with {@link #DIRECTORY_MUSIC}, + * {@link #DIRECTORY_AUDIOBOOKS}, {@link #DIRECTORY_PODCASTS}, + * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_ALARMS}, + * and {@link #DIRECTORY_RINGTONES} as a series of directories + * to categorize a particular audio file as more than one type. */ @NonNull // The better way is that expose a static method getRecordingDirectories. diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 786a7d08047e..a19728c5c498 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -323,6 +323,13 @@ public final class PowerManager { public static final int USER_ACTIVITY_EVENT_ATTENTION = 4; /** + * User activity event type: {@link com.android.server.power.FaceDownDetector} taking action + * on behalf of user. + * @hide + */ + public static final int USER_ACTIVITY_EVENT_FACE_DOWN = 5; + + /** * User activity flag: If already dimmed, extend the dim timeout * but do not brighten. This flag is useful for keeping the screen on * a little longer without causing a visible change such as when diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index b003d238c268..b90d438ffb93 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -208,6 +208,28 @@ public abstract class Vibrator { public abstract boolean hasAmplitudeControl(); /** + * Gets the resonant frequency of the vibrator. + * + * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or + * this vibrator is a composite of multiple physical devices. + * @hide + */ + public float getResonantFrequency() { + return Float.NaN; + } + + /** + * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator. + * + * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or + * this vibrator is a composite of multiple physical devices. + * @hide + */ + public float getQFactor() { + return Float.NaN; + } + + /** * Configure an always-on haptics effect. * * @param alwaysOnId The board-specific always-on ID to configure. diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index 50d2de3da965..3121b952281e 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -42,21 +42,27 @@ public final class VibratorInfo implements Parcelable { private final SparseBooleanArray mSupportedEffects; @Nullable private final SparseBooleanArray mSupportedPrimitives; + private final float mResonantFrequency; + private final float mQFactor; VibratorInfo(Parcel in) { mId = in.readInt(); mCapabilities = in.readLong(); mSupportedEffects = in.readSparseBooleanArray(); mSupportedPrimitives = in.readSparseBooleanArray(); + mResonantFrequency = in.readFloat(); + mQFactor = in.readFloat(); } /** @hide */ public VibratorInfo(int id, long capabilities, int[] supportedEffects, - int[] supportedPrimitives) { + int[] supportedPrimitives, float resonantFrequency, float qFactor) { mId = id; mCapabilities = capabilities; mSupportedEffects = toSparseBooleanArray(supportedEffects); mSupportedPrimitives = toSparseBooleanArray(supportedPrimitives); + mResonantFrequency = resonantFrequency; + mQFactor = qFactor; } @Override @@ -65,6 +71,8 @@ public final class VibratorInfo implements Parcelable { dest.writeLong(mCapabilities); dest.writeSparseBooleanArray(mSupportedEffects); dest.writeSparseBooleanArray(mSupportedPrimitives); + dest.writeFloat(mResonantFrequency); + dest.writeFloat(mQFactor); } @Override @@ -83,12 +91,15 @@ public final class VibratorInfo implements Parcelable { VibratorInfo that = (VibratorInfo) o; return mId == that.mId && mCapabilities == that.mCapabilities && Objects.equals(mSupportedEffects, that.mSupportedEffects) - && Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives); + && Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives) + && Objects.equals(mResonantFrequency, that.mResonantFrequency) + && Objects.equals(mQFactor, that.mQFactor); } @Override public int hashCode() { - return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives); + return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives, + mResonantFrequency, mQFactor); } @Override @@ -99,6 +110,8 @@ public final class VibratorInfo implements Parcelable { + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities) + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames()) + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames()) + + ", mResonantFrequency=" + mResonantFrequency + + ", mQFactor=" + mQFactor + '}'; } @@ -156,6 +169,26 @@ public final class VibratorInfo implements Parcelable { return (mCapabilities & capability) == capability; } + /** + * Gets the resonant frequency of the vibrator. + * + * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or + * this vibrator is a composite of multiple physical devices. + */ + public float getResonantFrequency() { + return mResonantFrequency; + } + + /** + * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator. + * + * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or + * this vibrator is a composite of multiple physical devices. + */ + public float getQFactor() { + return mQFactor; + } + private String[] getCapabilitiesNames() { List<String> names = new ArrayList<>(); if (hasCapability(IVibrator.CAP_ON_CALLBACK)) { diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java index 73520e07d118..2a42b981ac26 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -51,6 +51,8 @@ import java.util.UUID; public final class IncrementalFileStorages { private static final String TAG = "IncrementalFileStorages"; + private static final String SYSTEM_DATA_LOADER_PACKAGE = "android"; + private @NonNull final IncrementalManager mIncrementalManager; private @NonNull final File mStageDir; private @Nullable IncrementalStorage mInheritedStorage; @@ -116,7 +118,10 @@ public final class IncrementalFileStorages { mInheritedStorage = mIncrementalManager.openStorage( inheritedDir.getAbsolutePath()); if (mInheritedStorage != null) { - if (!mInheritedStorage.isFullyLoaded()) { + boolean systemDataLoader = SYSTEM_DATA_LOADER_PACKAGE.equals( + dataLoaderParams.getComponentName().getPackageName()); + if (systemDataLoader && !mInheritedStorage.isFullyLoaded()) { + // System data loader does not support incomplete storages. throw new IOException("Inherited storage has missing pages."); } diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index dc6f63a94685..047c05a8734b 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -30,6 +30,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -265,6 +266,13 @@ public final class IncrementalManager { } /** + * Checks if an fd corresponds to a file on a mounted Incremental File System. + */ + public static boolean isIncrementalFileFd(@NonNull FileDescriptor fd) { + return nativeIsIncrementalFd(fd.getInt$()); + } + + /** * Returns raw signature for file if it's on Incremental File System. * Unsafe, use only if you are sure what you are doing. */ @@ -421,9 +429,22 @@ public final class IncrementalManager { storage.unregisterStorageHealthListener(); } + /** + * Returns the metrics of an Incremental Storage. + */ + public IncrementalMetrics getMetrics(@NonNull String codePath) { + final IncrementalStorage storage = openStorage(codePath); + if (storage == null) { + // storage does not exist, package not installed + return null; + } + return new IncrementalMetrics(storage.getMetrics()); + } + /* Native methods */ private static native boolean nativeIsEnabled(); private static native boolean nativeIsV2Available(); private static native boolean nativeIsIncrementalPath(@NonNull String path); + private static native boolean nativeIsIncrementalFd(@NonNull int fd); private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path); } diff --git a/core/java/android/os/incremental/IncrementalMetrics.java b/core/java/android/os/incremental/IncrementalMetrics.java new file mode 100644 index 000000000000..44dea1be50f0 --- /dev/null +++ b/core/java/android/os/incremental/IncrementalMetrics.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.incremental; + +import android.annotation.NonNull; +import android.os.PersistableBundle; + +/** + * Provides methods to access metrics about an app installed via Incremental + * @hide + */ +public class IncrementalMetrics { + @NonNull private final PersistableBundle mData; + + public IncrementalMetrics(@NonNull PersistableBundle data) { + mData = data; + } + + /** + * @return Milliseconds between now and when the oldest pending read happened + */ + public long getMillisSinceOldestPendingRead() { + return mData.getLong(IIncrementalService.METRICS_MILLIS_SINCE_OLDEST_PENDING_READ, -1); + } +} diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index e6ce8cd56d28..7cf0144d71f7 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.DataLoaderParams; import android.content.pm.IDataLoaderStatusListener; +import android.os.PersistableBundle; import android.os.RemoteException; import java.io.File; @@ -601,4 +602,17 @@ public final class IncrementalStorage { return; } } + + /** + * Returns the metrics of the current storage. + * {@see IIncrementalService} for metrics keys. + */ + public PersistableBundle getMetrics() { + try { + return mService.getMetrics(mId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } } diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java index 396ba2d3cea5..82c4c715f4b0 100644 --- a/core/java/android/os/storage/StorageManagerInternal.java +++ b/core/java/android/os/storage/StorageManagerInternal.java @@ -52,6 +52,11 @@ public abstract class StorageManagerInternal { } /** + * Return true if fuse is mounted. + */ + public abstract boolean isFuseMounted(int userId); + + /** * Create storage directories if it does not exist. * Return true if the directories were setup correctly, otherwise false. */ diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 4c9e77c35135..7e3a0f30e75c 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -111,8 +111,7 @@ public class PermissionUsageHelper { private static boolean shouldShowLocationIndicator() { return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_LOCATION_INDICATORS_ENABLED, false) - || shouldShowPermissionsHub(); + PROPERTY_LOCATION_INDICATORS_ENABLED, false); } private static long getRecentThreshold(Long now) { @@ -326,10 +325,10 @@ public class PermissionUsageHelper { } if (packageName.equals(SYSTEM_PKG) - || (!isUserSensitive(packageName, user, op) + || (!shouldShowPermissionsHub() + && !isUserSensitive(packageName, user, op) && !isLocationProvider(packageName, user) - && !isAppPredictor(packageName, user)) - && !isSpeechRecognizerUsage(op, packageName)) { + && !isSpeechRecognizerUsage(op, packageName))) { continue; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 4dfbb6fa2d05..6865041a5037 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8369,6 +8369,14 @@ public final class Settings { public static final String DOZE_WAKE_DISPLAY_GESTURE = "doze_wake_display_gesture"; /** + * Gesture that wakes up the display on quick pickup, toggling between + * {@link Display.STATE_OFF} and {@link Display.STATE_DOZE}. + * @hide + */ + @Readable + public static final String DOZE_QUICK_PICKUP_GESTURE = "doze_quick_pickup_gesture"; + + /** * Whether the device should suppress the current doze configuration and disable dozing. * @hide */ diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 8a4812a42c8a..374de9ccb2f4 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -5310,5 +5310,12 @@ public final class Telephony { * @hide */ public static final String COLUMN_VOIMS_OPT_IN_STATUS = "voims_opt_in_status"; + + /** + * TelephonyProvider column name for device to device sharing status. + * + * @hide + */ + public static final String COLUMN_D2D_STATUS_SHARING = "d2d_sharing_status"; } } diff --git a/core/java/android/service/rotationresolver/RotationResolutionRequest.java b/core/java/android/service/rotationresolver/RotationResolutionRequest.java index 8e76e2fc9202..8dec0922b097 100644 --- a/core/java/android/service/rotationresolver/RotationResolutionRequest.java +++ b/core/java/android/service/rotationresolver/RotationResolutionRequest.java @@ -16,12 +16,15 @@ package android.service.rotationresolver; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.view.Surface; +import com.android.internal.util.DataClass; + /** * This class represents a request to an {@link RotationResolverService}. The request contains * information from the system that can help RotationResolverService to determine the appropriate @@ -33,68 +36,209 @@ import android.view.Surface; * @hide */ @SystemApi +@DataClass ( + genParcelable = true, + genToString = true +) public final class RotationResolutionRequest implements Parcelable { - private final @NonNull String mPackageName; - private final int mProposedRotation; - private final int mCurrentRotation; - private final long mTimeoutMillis; + /** The Name of the package of the current fore-ground activity. */ + @NonNull private final String mPackageName; + + /** The current rotation of the screen. */ + @Surface.Rotation private final int mCurrentRotation; + + /** The proposed screen rotation in the system. */ + @Surface.Rotation private final int mProposedRotation; + + /** Whether should use camera signal to resolver rotation. */ + private final boolean mShouldUseCamera; + + /** The timeout of the request. */ + @DurationMillisLong private final long mTimeoutMillis; + + + + + // 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/rotationresolver/RotationResolutionRequest.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + /** - * @param proposedRotation The system proposed screen rotation. - * @param currentRotation The current screen rotation of the phone. - * @param packageName The current package name of the activity that is running in - * foreground. - * @param timeoutMillis The timeout in millisecond for the rotation request. - * @hide + * Creates a new RotationResolutionRequest. + * + * @param packageName + * The Name of the package of the current fore-ground activity. + * @param currentRotation + * The current rotation of the screen. + * @param proposedRotation + * The proposed screen rotation in the system. + * @param shouldUseCamera + * Whether should use camera signal to resolver rotation. + * @param timeoutMillis + * The timeout of the request. */ - public RotationResolutionRequest(int proposedRotation, int currentRotation, - @NonNull String packageName, long timeoutMillis) { - mProposedRotation = proposedRotation; - mCurrentRotation = currentRotation; - mPackageName = packageName; - mTimeoutMillis = timeoutMillis; + @DataClass.Generated.Member + public RotationResolutionRequest( + @NonNull String packageName, + @Surface.Rotation int currentRotation, + @Surface.Rotation int proposedRotation, + boolean shouldUseCamera, + @DurationMillisLong long timeoutMillis) { + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mCurrentRotation = currentRotation; + com.android.internal.util.AnnotationValidations.validate( + Surface.Rotation.class, null, mCurrentRotation); + this.mProposedRotation = proposedRotation; + com.android.internal.util.AnnotationValidations.validate( + Surface.Rotation.class, null, mProposedRotation); + this.mShouldUseCamera = shouldUseCamera; + this.mTimeoutMillis = timeoutMillis; + com.android.internal.util.AnnotationValidations.validate( + DurationMillisLong.class, null, mTimeoutMillis); + + // onConstructed(); // You can define this method to get a callback } - @Surface.Rotation public int getProposedRotation() { - return mProposedRotation; + /** + * The Name of the package of the current fore-ground activity. + */ + @DataClass.Generated.Member + public @NonNull String getPackageName() { + return mPackageName; } - public int getCurrentRotation() { + /** + * The current rotation of the screen. + */ + @DataClass.Generated.Member + public @Surface.Rotation int getCurrentRotation() { return mCurrentRotation; } - public @NonNull String getPackageName() { - return mPackageName; + /** + * The proposed screen rotation in the system. + */ + @DataClass.Generated.Member + public @Surface.Rotation int getProposedRotation() { + return mProposedRotation; } - public long getTimeoutMillis() { + /** + * Whether should use camera signal to resolver rotation. + */ + @DataClass.Generated.Member + public boolean shouldUseCamera() { + return mShouldUseCamera; + } + + /** + * The timeout of the request. + */ + @DataClass.Generated.Member + public @DurationMillisLong long getTimeoutMillis() { return mTimeoutMillis; } @Override - public int describeContents() { - return 0; + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "RotationResolutionRequest { " + + "packageName = " + mPackageName + ", " + + "currentRotation = " + mCurrentRotation + ", " + + "proposedRotation = " + mProposedRotation + ", " + + "shouldUseCamera = " + mShouldUseCamera + ", " + + "timeoutMillis = " + mTimeoutMillis + + " }"; } @Override - public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mProposedRotation); - parcel.writeInt(mCurrentRotation); - parcel.writeString(mPackageName); - parcel.writeLong(mTimeoutMillis); + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mShouldUseCamera) flg |= 0x8; + dest.writeByte(flg); + dest.writeString(mPackageName); + dest.writeInt(mCurrentRotation); + dest.writeInt(mProposedRotation); + dest.writeLong(mTimeoutMillis); } - public static final @NonNull Creator<RotationResolutionRequest> CREATOR = - new Creator<RotationResolutionRequest>() { - @Override - public RotationResolutionRequest createFromParcel(Parcel source) { - return new RotationResolutionRequest(source.readInt(), source.readInt(), - source.readString(), source.readLong()); - } + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ RotationResolutionRequest(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + byte flg = in.readByte(); + boolean shouldUseCamera = (flg & 0x8) != 0; + String packageName = in.readString(); + int currentRotation = in.readInt(); + int proposedRotation = in.readInt(); + long timeoutMillis = in.readLong(); + + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mCurrentRotation = currentRotation; + com.android.internal.util.AnnotationValidations.validate( + Surface.Rotation.class, null, mCurrentRotation); + this.mProposedRotation = proposedRotation; + com.android.internal.util.AnnotationValidations.validate( + Surface.Rotation.class, null, mProposedRotation); + this.mShouldUseCamera = shouldUseCamera; + this.mTimeoutMillis = timeoutMillis; + com.android.internal.util.AnnotationValidations.validate( + DurationMillisLong.class, null, mTimeoutMillis); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<RotationResolutionRequest> CREATOR + = new Parcelable.Creator<RotationResolutionRequest>() { @Override public RotationResolutionRequest[] newArray(int size) { return new RotationResolutionRequest[size]; } + + @Override + public RotationResolutionRequest createFromParcel(@NonNull Parcel in) { + return new RotationResolutionRequest(in); + } }; + + @DataClass.Generated( + time = 1615402421314L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/service/rotationresolver/RotationResolutionRequest.java", + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.view.Surface.Rotation int mCurrentRotation\nprivate final @android.view.Surface.Rotation int mProposedRotation\nprivate final boolean mShouldUseCamera\nprivate final @android.annotation.DurationMillisLong long mTimeoutMillis\nclass RotationResolutionRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index c1d9d5816c9d..def13db41559 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -41,10 +41,12 @@ import android.media.permission.Identity; import android.os.AsyncTask; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; +import android.os.SharedMemory; import android.util.Slog; import com.android.internal.app.IHotwordRecognitionStatusCallback; @@ -287,6 +289,7 @@ public class AlwaysOnHotwordDetector { private final Handler mHandler; private final IBinder mBinder = new Binder(); private final int mTargetSdkVersion; + private final boolean mSupportHotwordDetectionService; private int mAvailability = STATE_NOT_READY; @@ -488,11 +491,22 @@ public class AlwaysOnHotwordDetector { * @param callback A non-null Callback for receiving the recognition events. * @param modelManagementService A service that allows management of sound models. * @param targetSdkVersion The target SDK version. + * @param supportHotwordDetectionService {@code true} if hotword detection service should be + * triggered, otherwise {@code false}. + * @param options Application configuration data provided by the + * {@link VoiceInteractionService}. The system strips out 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 + * such data to the trusted process. + * * @hide */ public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback, KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, - IVoiceInteractionManagerService modelManagementService, int targetSdkVersion) { + IVoiceInteractionManagerService modelManagementService, int targetSdkVersion, + boolean supportHotwordDetectionService, @Nullable Bundle options, + @Nullable SharedMemory sharedMemory) { mText = text; mLocale = locale; mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; @@ -501,6 +515,10 @@ public class AlwaysOnHotwordDetector { mInternalCallback = new SoundTriggerListener(mHandler); mModelManagementService = modelManagementService; mTargetSdkVersion = targetSdkVersion; + mSupportHotwordDetectionService = supportHotwordDetectionService; + if (mSupportHotwordDetectionService) { + setHotwordDetectionServiceConfig(options, sharedMemory); + } try { Identity identity = new Identity(); identity.packageName = ActivityThread.currentOpPackageName(); @@ -513,6 +531,38 @@ public class AlwaysOnHotwordDetector { } /** + * Set configuration and pass read-only data to hotword detection service. + * + * @param options Application configuration data provided by the + * {@link VoiceInteractionService}. The system strips out 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 + * such data to the trusted process. + * + * @throws IllegalStateException if it doesn't support hotword detection service. + * + * @hide + */ + public final void setHotwordDetectionServiceConfig(@Nullable Bundle options, + @Nullable SharedMemory sharedMemory) { + if (DBG) { + Slog.d(TAG, "setHotwordDetectionServiceConfig()"); + } + if (!mSupportHotwordDetectionService) { + throw new IllegalStateException( + "setHotwordDetectionServiceConfig called, but it doesn't support hotword" + + " detection service"); + } + + try { + mModelManagementService.setHotwordDetectionServiceConfig(options, sharedMemory); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Gets the recognition modes supported by the associated keyphrase. * * @see #RECOGNITION_MODE_USER_IDENTIFICATION @@ -839,6 +889,14 @@ public class AlwaysOnHotwordDetector { synchronized (mLock) { mAvailability = STATE_INVALID; notifyStateChangedLocked(); + + if (mSupportHotwordDetectionService) { + try { + mModelManagementService.shutdownHotwordDetectionService(); + } 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 7f1c5ff96636..fcef26f13dd0 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -27,13 +27,17 @@ import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; import android.media.AudioFormat; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.SharedMemory; import android.util.Log; +import java.util.Locale; + /** * Implemented by an application that wants to offer detection for hotword. The system will * start the service after calling {@link VoiceInteractionService#setHotwordDetectionConfig}. @@ -76,6 +80,17 @@ public abstract class HotwordDetectionService extends Service { timeoutMillis, new DspHotwordDetectionCallback(callback))); } + + @Override + public void setConfig(Bundle options, SharedMemory sharedMemory) throws RemoteException { + if (DBG) { + Log.d(TAG, "#setConfig"); + } + mHandler.sendMessage(obtainMessage(HotwordDetectionService::onUpdateState, + HotwordDetectionService.this, + options, + sharedMemory)); + } }; @CallSuper @@ -121,6 +136,25 @@ public abstract class HotwordDetectionService extends Service { } /** + * Called when the {@link VoiceInteractionService#createAlwaysOnHotwordDetector(String, Locale, + * Bundle, SharedMemory, AlwaysOnHotwordDetector.Callback)} or {@link AlwaysOnHotwordDetector# + * setHotwordDetectionServiceConfig(Bundle, SharedMemory)} requests an update of the hotword + * detection parameters. + * + * @param options Application configuration data provided by the + * {@link VoiceInteractionService}. The system strips out 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 + * such data to the trusted process. + * + * @hide + */ + @SystemApi + public void onUpdateState(@Nullable Bundle options, @Nullable SharedMemory sharedMemory) { + } + + /** * Callback for returning the detected result. * * @hide diff --git a/core/java/android/service/voice/IHotwordDetectionService.aidl b/core/java/android/service/voice/IHotwordDetectionService.aidl index cbe76e4bf69f..8f0874a5cb2e 100644 --- a/core/java/android/service/voice/IHotwordDetectionService.aidl +++ b/core/java/android/service/voice/IHotwordDetectionService.aidl @@ -17,7 +17,9 @@ package android.service.voice; import android.media.AudioFormat; +import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.SharedMemory; import android.service.voice.IDspHotwordDetectionCallback; /** @@ -31,4 +33,6 @@ oneway interface IHotwordDetectionService { in AudioFormat audioFormat, long timeoutMillis, in IDspHotwordDetectionCallback callback); + + void setConfig(in Bundle options, in SharedMemory sharedMemory); } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 25f80900f1cf..048d9f57aded 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -17,7 +17,6 @@ package android.service.voice; import android.Manifest; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -36,6 +35,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SharedMemory; import android.provider.Settings; import android.util.ArraySet; import android.util.Log; @@ -47,8 +47,6 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -73,32 +71,6 @@ public class VoiceInteractionService extends Service { static final String TAG = VoiceInteractionService.class.getSimpleName(); /** - * Indicates that the given configs have been set successfully after calling - * {@link VoiceInteractionService#setHotwordDetectionConfig}. - * - * @hide - */ - @SystemApi - public static final int HOTWORD_CONFIG_SUCCESS = 0; - - /** - * Indicates that the given configs have been set unsuccessfully after calling - * {@link VoiceInteractionService#setHotwordDetectionConfig}. - * - * @hide - */ - @SystemApi - public static final int HOTWORD_CONFIG_FAILURE = 1; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, prefix = { "HOTWORD_CONFIG_" }, value = { - HOTWORD_CONFIG_SUCCESS, - HOTWORD_CONFIG_FAILURE, - }) - public @interface HotwordConfigResult {} - - /** * The {@link Intent} that must be declared as handled by the service. * To be supported, the service must also require the * {@link android.Manifest.permission#BIND_VOICE_INTERACTION} permission so @@ -330,42 +302,51 @@ public class VoiceInteractionService extends Service { } /** - * Set hotword detection configuration. - * - * Note: Currently it will trigger hotword detection service after calling this function when - * all conditions meet the requirements. - * - * @param options Config data. - * @return {@link VoiceInteractionService#HOTWORD_CONFIG_SUCCESS} in case of success, - * {@link VoiceInteractionService#HOTWORD_CONFIG_FAILURE} in case of failure. + * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale. + * This instance must be retained and used by the client. + * Calling this a second time invalidates the previously created hotword detector + * which can no longer be used to manage recognition. * - * @throws IllegalStateException if the function is called before onReady() is called. + * @param keyphrase The keyphrase that's being used, for example "Hello Android". + * @param locale The locale for which the enrollment needs to be performed. + * @param callback The callback to notify of detection events. + * @return An always-on hotword detector for the given keyphrase and locale. * * @hide */ @SystemApi - @HotwordConfigResult - public final int setHotwordDetectionConfig( - @SuppressLint("NullableCollection") @Nullable Bundle options) { - if (mSystemService == null) { - throw new IllegalStateException("Not available until onReady() is called"); - } - - try { - return mSystemService.setHotwordDetectionConfig(options); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + @NonNull + public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( + @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly + @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, + @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { + return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, + /* supportHotwordDetectionService= */ false, /* options= */ null, + /* sharedMemory= */ null, callback); } /** - * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale. - * This instance must be retained and used by the client. - * Calling this a second time invalidates the previously created hotword detector - * which can no longer be used to manage recognition. + * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService} + * service, then it will also pass the read-only data to hotword detection service. + * + * Like {@see #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback) + * }. Before calling this function, you should set a valid hotword detection service with + * android:hotwordDetectionService in an android.voice_interaction metadata file and set + * android:isolatedProcess="true" in the AndroidManifest.xml of hotword detection service. + * Otherwise it will throw IllegalStateException. After calling this function, the system will + * also trigger a hotword detection service and pass the read-only data back to it. + * + * <p>Note: The system will trigger hotword detection service after calling this function when + * all conditions meet the requirements. * * @param keyphrase The keyphrase that's being used, for example "Hello Android". * @param locale The locale for which the enrollment needs to be performed. + * @param options Application configuration data provided by the + * {@link VoiceInteractionService}. The system strips out 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 + * such data to the trusted process. * @param callback The callback to notify of detection events. * @return An always-on hotword detector for the given keyphrase and locale. * @@ -374,8 +355,22 @@ public class VoiceInteractionService extends Service { @SystemApi @NonNull public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( - @SuppressLint("MissingNullability") String keyphrase, // TODO: annotate nullability properly + @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly + @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, + @Nullable Bundle options, + @Nullable SharedMemory sharedMemory, + @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { + return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, + /* supportHotwordDetectionService= */ true, options, + sharedMemory, callback); + } + + private AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorInternal( + @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, + boolean supportHotwordDetectionService, + @Nullable Bundle options, + @Nullable SharedMemory sharedMemory, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { if (mSystemService == null) { throw new IllegalStateException("Not available until onReady() is called"); @@ -385,7 +380,8 @@ public class VoiceInteractionService extends Service { safelyShutdownHotwordDetector(); mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback, mKeyphraseEnrollmentInfo, mSystemService, - getApplicationContext().getApplicationInfo().targetSdkVersion); + getApplicationContext().getApplicationInfo().targetSdkVersion, + supportHotwordDetectionService, options, sharedMemory); } return mHotwordDetector; } @@ -432,7 +428,6 @@ public class VoiceInteractionService extends Service { } private void safelyShutdownHotwordDetector() { - // TODO (b/178171906): Need to check if the HotwordDetectionService should be unbound. synchronized (mLock) { if (mHotwordDetector == null) { return; diff --git a/core/java/android/speech/IRecognitionServiceManager.aidl b/core/java/android/speech/IRecognitionServiceManager.aidl index 8e5292d1ddf1..ad402262878d 100644 --- a/core/java/android/speech/IRecognitionServiceManager.aidl +++ b/core/java/android/speech/IRecognitionServiceManager.aidl @@ -31,4 +31,6 @@ oneway interface IRecognitionServiceManager { in IBinder clientToken, boolean onDevice, in IRecognitionServiceManagerCallback callback); + + void setTemporaryComponent(in ComponentName componentName); } diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 850f997a2d2f..9b93a64e48a3 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -17,6 +17,8 @@ package android.speech; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -35,6 +37,8 @@ import android.util.Log; import android.util.Slog; import java.util.List; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; /** * This class provides access to the speech recognition service. This service allows access to the @@ -52,7 +56,7 @@ import java.util.List; */ public class SpeechRecognizer { /** DEBUG value to enable verbose debug prints */ - private final static boolean DBG = false; + private static final boolean DBG = false; /** Log messages identifier */ private static final String TAG = "SpeechRecognizer"; @@ -113,10 +117,11 @@ public class SpeechRecognizer { public static final int ERROR_SERVER_DISCONNECTED = 11; /** action codes */ - private final static int MSG_START = 1; - private final static int MSG_STOP = 2; - private final static int MSG_CANCEL = 3; - private final static int MSG_CHANGE_LISTENER = 4; + private static final int MSG_START = 1; + private static final int MSG_STOP = 2; + private static final int MSG_CANCEL = 3; + private static final int MSG_CHANGE_LISTENER = 4; + private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5; /** The actual RecognitionService endpoint */ private IRecognitionService mService; @@ -134,6 +139,7 @@ public class SpeechRecognizer { /** Handler that will execute the main tasks */ private Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -149,10 +155,19 @@ public class SpeechRecognizer { case MSG_CHANGE_LISTENER: handleChangeListener((RecognitionListener) msg.obj); break; + case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT: + handleSetTemporaryComponent((ComponentName) msg.obj); + break; } } }; + /** + * Temporary queue, saving the messages until the connection will be established, afterwards, + * only mHandler will receive the messages + */ + private final Queue<Message> mPendingTasks = new LinkedBlockingQueue<>(); + /** The Listener that will receive all the callbacks */ private final InternalListener mListener = new InternalListener(); @@ -287,11 +302,9 @@ public class SpeechRecognizer { if (mService == null) { // First time connection: first establish a connection, then dispatch #startListening. - connectToSystemService( - () -> putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent))); - } else { - putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)); + connectToSystemService(); } + putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)); } /** @@ -336,6 +349,22 @@ public class SpeechRecognizer { putMessage(Message.obtain(mHandler, MSG_CANCEL)); } + /** + * Sets a temporary component to power on-device speech recognizer. + * + * <p>This is only expected to be called in tests, system would reject calls from client apps. + * + * @param componentName name of the component to set temporary replace speech recognizer. {@code + * null} value resets the recognizer to default. + * + * @hide + */ + @TestApi + public void setTemporaryOnDeviceRecognizer(@Nullable ComponentName componentName) { + mHandler.sendMessage( + Message.obtain(mHandler, MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT, componentName)); + } + private static void checkIsCalledFromMainThread() { if (Looper.myLooper() != Looper.getMainLooper()) { throw new RuntimeException( @@ -344,7 +373,11 @@ public class SpeechRecognizer { } private void putMessage(Message msg) { - mHandler.sendMessage(msg); + if (mService == null) { + mPendingTasks.offer(msg); + } else { + mHandler.sendMessage(msg); + } } /** sends the actual message to the service */ @@ -395,6 +428,22 @@ public class SpeechRecognizer { } } + private void handleSetTemporaryComponent(ComponentName componentName) { + if (DBG) { + Log.d(TAG, "handleSetTemporaryComponent, componentName=" + componentName); + } + + if (!maybeInitializeManagerService()) { + return; + } + + try { + mManagerService.setTemporaryComponent(componentName); + } catch (final RemoteException e) { + e.rethrowFromSystemServer(); + } + } + private boolean checkOpenConnection() { if (mService != null) { return true; @@ -422,16 +471,13 @@ public class SpeechRecognizer { } mService = null; + mPendingTasks.clear(); mListener.mInternalListener = null; } /** Establishes a connection to system server proxy and initializes the session. */ - private void connectToSystemService(Runnable onSuccess) { - mManagerService = IRecognitionServiceManager.Stub.asInterface( - ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE)); - - if (mManagerService == null) { - mListener.onError(ERROR_CLIENT); + private void connectToSystemService() { + if (!maybeInitializeManagerService()) { return; } @@ -450,13 +496,19 @@ public class SpeechRecognizer { new IRecognitionServiceManagerCallback.Stub(){ @Override public void onSuccess(IRecognitionService service) throws RemoteException { + if (DBG) { + Log.i(TAG, "Connected to speech recognition service"); + } mService = service; - onSuccess.run(); + while (!mPendingTasks.isEmpty()) { + mHandler.sendMessage(mPendingTasks.poll()); + } } @Override public void onError(int errorCode) throws RemoteException { - Log.e(TAG, "Bind to system recognition service failed"); + Log.e(TAG, "Bind to system recognition service failed with error " + + errorCode); mListener.onError(errorCode); } }); @@ -465,6 +517,21 @@ public class SpeechRecognizer { } } + private boolean maybeInitializeManagerService() { + if (mManagerService != null) { + return true; + } + + mManagerService = IRecognitionServiceManager.Stub.asInterface( + ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE)); + + if (mManagerService == null && mListener != null) { + mListener.onError(ERROR_CLIENT); + return false; + } + return true; + } + /** * Returns the component name to be used for establishing a connection, based on the parameters * used during initialization. @@ -505,15 +572,15 @@ public class SpeechRecognizer { private static class InternalListener extends IRecognitionListener.Stub { private RecognitionListener mInternalListener; - private final static int MSG_BEGINNING_OF_SPEECH = 1; - private final static int MSG_BUFFER_RECEIVED = 2; - private final static int MSG_END_OF_SPEECH = 3; - private final static int MSG_ERROR = 4; - private final static int MSG_READY_FOR_SPEECH = 5; - private final static int MSG_RESULTS = 6; - private final static int MSG_PARTIAL_RESULTS = 7; - private final static int MSG_RMS_CHANGED = 8; - private final static int MSG_ON_EVENT = 9; + private static final int MSG_BEGINNING_OF_SPEECH = 1; + private static final int MSG_BUFFER_RECEIVED = 2; + private static final int MSG_END_OF_SPEECH = 3; + private static final int MSG_ERROR = 4; + private static final int MSG_READY_FOR_SPEECH = 5; + private static final int MSG_RESULTS = 6; + private static final int MSG_PARTIAL_RESULTS = 7; + private static final int MSG_RMS_CHANGED = 8; + private static final int MSG_ON_EVENT = 9; private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) { @Override diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index bbe887f500a9..e9a79e70fd74 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -20,7 +20,6 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; -import android.compat.annotation.ChangeId; import android.compat.annotation.UnsupportedAppUsage; import android.os.Binder; import android.os.Build; @@ -1577,7 +1576,7 @@ public class PhoneStateListener { // default implementation empty } - public void onAllowedNetworkTypesChanged(Map allowedNetworkTypesList) { + public void onAllowedNetworkTypesChanged(int reason, long allowedNetworkType) { // default implementation empty } } diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index 2cadda25a9d3..e3d3dec60151 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -546,9 +546,6 @@ public class TelephonyCallback { /** * Event for changes to allowed network list based on all active subscriptions. * - * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling - * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). - * * @hide * @see AllowedNetworkTypesListener#onAllowedNetworkTypesChanged */ @@ -1265,30 +1262,34 @@ public class TelephonyCallback { public interface AllowedNetworkTypesListener { /** * Callback invoked when the current allowed network type list has changed on the - * registered subscription. + * registered subscription for a specified reason. * Note, the registered subscription is associated with {@link TelephonyManager} object - * on which - * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)} + * on which {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)} * was called. * If this TelephonyManager object was created with * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the * given subscription ID. Otherwise, this callback applies to * {@link SubscriptionManager#getDefaultSubscriptionId()}. * - * @param allowedNetworkTypesList Map associating all allowed network type reasons - * ({@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER}, - * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER}, - * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}, and - * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}) with reason's allowed - * network type values. + * @param reason an allowed network type reasons. + * @see TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER + * @see TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER + * @see TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER + * @see TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G + * + * @param allowedNetworkType an allowed network type bitmask value. (for example, + * the long bitmask value is {{@link TelephonyManager#NETWORK_TYPE_BITMASK_NR}| + * {@link TelephonyManager#NETWORK_TYPE_BITMASK_LTE}}) + * * For example: - * map{{TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER, long type value}, - * {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER, long type value}, - * {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER, long type value}, - * {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, long type value}} + * If the latest allowed network type is changed by user, then the system + * notifies the {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER} and + * long type value}. */ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - void onAllowedNetworkTypesChanged(@NonNull Map<Integer, Long> allowedNetworkTypesList); + void onAllowedNetworkTypesChanged( + @TelephonyManager.AllowedNetworkTypesReason int reason, + @TelephonyManager.NetworkTypeBitMask long allowedNetworkType); } /** @@ -1707,14 +1708,15 @@ public class TelephonyCallback { enabled, reason))); } - public void onAllowedNetworkTypesChanged(Map allowedNetworkTypesList) { + public void onAllowedNetworkTypesChanged(int reason, long allowedNetworkType) { AllowedNetworkTypesListener listener = (AllowedNetworkTypesListener) mTelephonyCallbackWeakRef.get(); if (listener == null) return; Binder.withCleanCallingIdentity( () -> mExecutor.execute( - () -> listener.onAllowedNetworkTypesChanged(allowedNetworkTypesList))); + () -> listener.onAllowedNetworkTypesChanged(reason, + allowedNetworkType))); } } } diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 9cda4ae79335..3fa63d8c1a9c 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -825,16 +825,18 @@ public class TelephonyRegistryManager { } /** - * Notify emergency number list changed on certain subscription. - * - * @param slotIndex for which emergency number list changed. Can be derived from subId except - * when subId is invalid. - * @param subId for which emergency number list changed. + * Notify the allowed network types has changed for a specific subscription and the specific + * reason. + * @param slotIndex for which allowed network types changed. + * @param subId for which allowed network types changed. + * @param reason an allowed network type reasons. + * @param allowedNetworkType an allowed network type bitmask value. */ public void notifyAllowedNetworkTypesChanged(int slotIndex, int subId, - Map<Integer, Long> allowedNetworkTypeList) { + int reason, long allowedNetworkType) { try { - sRegistry.notifyAllowedNetworkTypesChanged(slotIndex, subId, allowedNetworkTypeList); + sRegistry.notifyAllowedNetworkTypesChanged(slotIndex, subId, reason, + allowedNetworkType); } catch (RemoteException ex) { // system process is dead } diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java index 3880131324fc..f61ab2985163 100644 --- a/core/java/android/util/Slog.java +++ b/core/java/android/util/Slog.java @@ -16,14 +16,26 @@ package android.util; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +import com.android.internal.annotations.GuardedBy; + +import java.util.Formatter; +import java.util.Locale; + /** * @hide */ public final class Slog { + @GuardedBy("sMessageBuilder") + private static final StringBuilder sMessageBuilder = new StringBuilder(); + + @GuardedBy("sMessageBuilder") + private static final Formatter sFormatter = new Formatter(sMessageBuilder, Locale.ENGLISH); + private Slog() { } @@ -37,6 +49,15 @@ public final class Slog { msg + '\n' + Log.getStackTraceString(tr)); } + /** + * Logs a {@link Log.VERBOSE} message. + */ + public static void v(String tag, String format, @Nullable Object... args) { + if (!Log.isLoggable(tag, Log.VERBOSE)) return; + + v(tag, getMessage(format, args)); + } + @UnsupportedAppUsage public static int d(String tag, String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg); @@ -48,6 +69,15 @@ public final class Slog { msg + '\n' + Log.getStackTraceString(tr)); } + /** + * Logs a {@link Log.DEBUG} message. + */ + public static void d(String tag, String format, @Nullable Object... args) { + if (!Log.isLoggable(tag, Log.DEBUG)) return; + + d(tag, getMessage(format, args)); + } + @UnsupportedAppUsage public static int i(String tag, String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg); @@ -58,6 +88,15 @@ public final class Slog { msg + '\n' + Log.getStackTraceString(tr)); } + /** + * Logs a {@link Log.INFO} message. + */ + public static void i(String tag, String format, @Nullable Object... args) { + if (!Log.isLoggable(tag, Log.INFO)) return; + + i(tag, getMessage(format, args)); + } + @UnsupportedAppUsage public static int w(String tag, String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg); @@ -73,6 +112,24 @@ public final class Slog { return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, Log.getStackTraceString(tr)); } + /** + * Logs a {@link Log.WARN} message. + */ + public static void w(String tag, String format, @Nullable Object... args) { + if (!Log.isLoggable(tag, Log.WARN)) return; + + w(tag, getMessage(format, args)); + } + + /** + * Logs a {@link Log.WARN} message with an exception + */ + public static void w(String tag, Exception exception, String format, @Nullable Object... args) { + if (!Log.isLoggable(tag, Log.WARN)) return; + + w(tag, getMessage(format, args), exception); + } + @UnsupportedAppUsage public static int e(String tag, String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg); @@ -85,6 +142,24 @@ public final class Slog { } /** + * Logs a {@link Log.ERROR} message. + */ + public static void e(String tag, String format, @Nullable Object... args) { + if (!Log.isLoggable(tag, Log.ERROR)) return; + + e(tag, getMessage(format, args)); + } + + /** + * Logs a {@link Log.ERROR} message with an exception + */ + public static void e(String tag, Exception exception, String format, @Nullable Object... args) { + if (!Log.isLoggable(tag, Log.ERROR)) return; + + e(tag, getMessage(format, args), exception); + } + + /** * Like {@link Log#wtf(String, String)}, but will never cause the caller to crash, and * will always be handled asynchronously. Primarily for use by coding running within * the system process. @@ -95,6 +170,21 @@ public final class Slog { } /** + * Logs a {@code wtf} message. + */ + public static void wtf(String tag, String format, @Nullable Object... args) { + wtf(tag, getMessage(format, args)); + } + + /** + * Logs a {@code wtf} message with an exception. + */ + public static void wtf(String tag, Exception exception, String format, + @Nullable Object... args) { + wtf(tag, getMessage(format, args), exception); + } + + /** * Like {@link #wtf(String, String)}, but does not output anything to the log. */ public static void wtfQuiet(String tag, String msg) { @@ -134,5 +224,13 @@ public final class Slog { public static int println(int priority, String tag, String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, priority, tag, msg); } -} + private static String getMessage(String format, @Nullable Object... args) { + synchronized (sMessageBuilder) { + sFormatter.format(format, args); + String message = sMessageBuilder.toString(); + sMessageBuilder.setLength(0); + return message; + } + } +} diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index c97c995641d1..7e6175c03d35 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -200,10 +200,9 @@ public final class ApkSigningBlockUtils { // physical memory. DataSource beforeApkSigningBlock = - new MemoryMappedFileDataSource(apkFileDescriptor, 0, - signatureInfo.apkSigningBlockOffset); + DataSource.create(apkFileDescriptor, 0, signatureInfo.apkSigningBlockOffset); DataSource centralDir = - new MemoryMappedFileDataSource( + DataSource.create( apkFileDescriptor, signatureInfo.centralDirOffset, signatureInfo.eocdOffset - signatureInfo.centralDirOffset); diff --git a/core/java/android/util/apk/DataSource.java b/core/java/android/util/apk/DataSource.java index 82f3800aa6d3..dd6389d012bc 100644 --- a/core/java/android/util/apk/DataSource.java +++ b/core/java/android/util/apk/DataSource.java @@ -16,6 +16,10 @@ package android.util.apk; +import android.annotation.NonNull; +import android.os.incremental.IncrementalManager; + +import java.io.FileDescriptor; import java.io.IOException; import java.security.DigestException; @@ -35,4 +39,22 @@ interface DataSource { */ void feedIntoDataDigester(DataDigester md, long offset, int size) throws IOException, DigestException; + + /** + * Creates a DataSource that can handle the passed fd in the most efficient and safe manner. + * @param fd file descriptor to read from + * @param pos starting offset + * @param size size of the region + * @return created DataSource object + */ + static @NonNull DataSource create(@NonNull FileDescriptor fd, long pos, long size) { + if (IncrementalManager.isIncrementalFileFd(fd)) { + // IncFS-based files may have missing pages, and reading those via mmap() results + // in a SIGBUS signal. Java doesn't have a good way of catching it, ending up killing + // the process by default. Going back to read() is the safest option for these files. + return new ReadFileDataSource(fd, pos, size); + } else { + return new MemoryMappedFileDataSource(fd, pos, size); + } + } } diff --git a/core/java/android/util/apk/MemoryMappedFileDataSource.java b/core/java/android/util/apk/MemoryMappedFileDataSource.java index 8d2b1e328862..69a526d09ad9 100644 --- a/core/java/android/util/apk/MemoryMappedFileDataSource.java +++ b/core/java/android/util/apk/MemoryMappedFileDataSource.java @@ -40,6 +40,7 @@ class MemoryMappedFileDataSource implements DataSource { /** * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file. * + * @param fd file descriptor to read from. * @param position start position of the region in the file. * @param size size (in bytes) of the region. */ diff --git a/core/java/android/util/apk/ReadFileDataSource.java b/core/java/android/util/apk/ReadFileDataSource.java new file mode 100644 index 000000000000..d0e1140c0eb4 --- /dev/null +++ b/core/java/android/util/apk/ReadFileDataSource.java @@ -0,0 +1,73 @@ +/* + * 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.util.apk; + +import android.system.ErrnoException; +import android.system.Os; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.DigestException; + +/** + * {@link DataSource} which provides data from a file descriptor by reading the sections + * of the file via raw read() syscall. This is slower than memory-mapping but safer. + */ +class ReadFileDataSource implements DataSource { + private final FileDescriptor mFd; + private final long mFilePosition; + private final long mSize; + + private static final int CHUNK_SIZE = 1024 * 1024; + + /** + * Constructs a new {@code ReadFileDataSource} for the specified region of the file. + * + * @param fd file descriptor to read from. + * @param position start position of the region in the file. + * @param size size (in bytes) of the region. + */ + ReadFileDataSource(FileDescriptor fd, long position, long size) { + mFd = fd; + mFilePosition = position; + mSize = size; + } + + @Override + public long size() { + return mSize; + } + + @Override + public void feedIntoDataDigester(DataDigester md, long offset, int size) + throws IOException, DigestException { + try { + final byte[] buffer = new byte[Math.min(size, CHUNK_SIZE)]; + final long start = mFilePosition + offset; + final long end = start + size; + for (long pos = start, curSize = Math.min(size, CHUNK_SIZE); + pos < end; curSize = Math.min(end - pos, CHUNK_SIZE)) { + final int readSize = Os.pread(mFd, buffer, 0, (int) curSize, pos); + md.consume(ByteBuffer.wrap(buffer, 0, readSize)); + pos += readSize; + } + } catch (ErrnoException e) { + throw new IOException(e); + } + } +} diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING index 8544e82e04e0..4598b4ffe4f6 100644 --- a/core/java/android/util/apk/TEST_MAPPING +++ b/core/java/android/util/apk/TEST_MAPPING @@ -1,6 +1,17 @@ { "presubmit": [ { + "name": "CtsContentTestCases", + "options": [ + { + "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest" + }, + { + "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest" + } + ] + }, + { "name": "FrameworksCoreTests", "options": [ { diff --git a/core/java/android/util/apk/VerityBuilder.java b/core/java/android/util/apk/VerityBuilder.java index 4596c6e8f83d..b0a5992230bd 100644 --- a/core/java/android/util/apk/VerityBuilder.java +++ b/core/java/android/util/apk/VerityBuilder.java @@ -294,7 +294,7 @@ public abstract class VerityBuilder { // 1. Digest the whole file by chunks. consumeByChunk(digester, - new MemoryMappedFileDataSource(file.getFD(), 0, file.length()), + DataSource.create(file.getFD(), 0, file.length()), MMAP_REGION_SIZE_BYTES); // 2. Pad 0s up to the nearest 4096-byte block before hashing. @@ -315,7 +315,7 @@ public abstract class VerityBuilder { // 1. Digest from the beginning of the file, until APK Signing Block is reached. consumeByChunk(digester, - new MemoryMappedFileDataSource(apk.getFD(), 0, signatureInfo.apkSigningBlockOffset), + DataSource.create(apk.getFD(), 0, signatureInfo.apkSigningBlockOffset), MMAP_REGION_SIZE_BYTES); // 2. Skip APK Signing Block and continue digesting, until the Central Directory offset @@ -323,7 +323,7 @@ public abstract class VerityBuilder { long eocdCdOffsetFieldPosition = signatureInfo.eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET; consumeByChunk(digester, - new MemoryMappedFileDataSource(apk.getFD(), signatureInfo.centralDirOffset, + DataSource.create(apk.getFD(), signatureInfo.centralDirOffset, eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset), MMAP_REGION_SIZE_BYTES); @@ -338,7 +338,7 @@ public abstract class VerityBuilder { long offsetAfterEocdCdOffsetField = eocdCdOffsetFieldPosition + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE; consumeByChunk(digester, - new MemoryMappedFileDataSource(apk.getFD(), offsetAfterEocdCdOffsetField, + DataSource.create(apk.getFD(), offsetAfterEocdCdOffsetField, apk.length() - offsetAfterEocdCdOffsetField), MMAP_REGION_SIZE_BYTES); diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java index 49ff237403b2..b28cfb87e28d 100644 --- a/core/java/android/util/imetracing/ImeTracing.java +++ b/core/java/android/util/imetracing/ImeTracing.java @@ -23,7 +23,6 @@ import android.inputmethodservice.AbstractInputMethodService; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; -import android.os.ShellCommand; import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.inputmethod.InputMethodManager; @@ -104,12 +103,6 @@ public abstract class ImeTracing { public abstract void addToBuffer(ProtoOutputStream proto, int source); /** - * @param shell The shell command to process - * @return {@code 0} if the command was successfully processed, {@code -1} otherwise - */ - public abstract int onShellCommand(ShellCommand shell); - - /** * Starts a proto dump of the client side information. * * @param where Place where the trace was triggered. diff --git a/core/java/android/util/imetracing/ImeTracingClientImpl.java b/core/java/android/util/imetracing/ImeTracingClientImpl.java index 2c2763988d14..35a81b7aeea5 100644 --- a/core/java/android/util/imetracing/ImeTracingClientImpl.java +++ b/core/java/android/util/imetracing/ImeTracingClientImpl.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.inputmethodservice.AbstractInputMethodService; import android.os.RemoteException; import android.os.ServiceManager.ServiceNotFoundException; -import android.os.ShellCommand; import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.inputmethod.InputMethodManager; @@ -45,11 +44,6 @@ class ImeTracingClientImpl extends ImeTracing { } @Override - public int onShellCommand(ShellCommand shell) { - return -1; - } - - @Override public void triggerClientDump(String where, @NonNull InputMethodManager immInstance, ProtoOutputStream icProto) { if (!isEnabled() || !isAvailable()) { diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java index e793c280afbc..77f017a4654a 100644 --- a/core/java/android/util/imetracing/ImeTracingServerImpl.java +++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java @@ -22,7 +22,6 @@ import android.annotation.Nullable; import android.inputmethodservice.AbstractInputMethodService; import android.os.RemoteException; import android.os.ServiceManager.ServiceNotFoundException; -import android.os.ShellCommand; import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto; @@ -106,32 +105,6 @@ class ImeTracingServerImpl extends ImeTracing { } } - /** - * Responds to a shell command of the format "adb shell cmd input_method ime tracing <command>" - * - * @param shell The shell command to process - * @return {@code 0} if the command was valid and successfully processed, {@code -1} otherwise - */ - @Override - public int onShellCommand(ShellCommand shell) { - PrintWriter pw = shell.getOutPrintWriter(); - String cmd = shell.getNextArgRequired(); - switch (cmd) { - case "start": - startTrace(pw); - return 0; - case "stop": - stopTrace(pw); - return 0; - default: - pw.println("Unknown command: " + cmd); - pw.println("Input method trace options:"); - pw.println(" start: Start tracing"); - pw.println(" stop: Stop tracing"); - return -1; - } - } - @Override public void triggerClientDump(String where, InputMethodManager immInstance, ProtoOutputStream icProto) { diff --git a/core/java/android/uwb/AngleMeasurement.java b/core/java/android/uwb/AngleMeasurement.java index 9df213b2092f..8c771baaea37 100644 --- a/core/java/android/uwb/AngleMeasurement.java +++ b/core/java/android/uwb/AngleMeasurement.java @@ -38,9 +38,30 @@ public final class AngleMeasurement implements Parcelable { private final double mErrorRadians; private final double mConfidenceLevel; - private AngleMeasurement(double radians, double errorRadians, double confidenceLevel) { + /** + * Constructs a new {@link AngleMeasurement} object + * + * @param radians the angle in radians + * @param errorRadians the error of the angle measurement in radians + * @param confidenceLevel confidence level of the angle measurement + * + * @throws IllegalArgumentException if the radians, errorRadians, or confidenceLevel is out of + * allowed range + */ + public AngleMeasurement(double radians, double errorRadians, double confidenceLevel) { + if (radians < -Math.PI || radians > Math.PI) { + throw new IllegalArgumentException("Invalid radians: " + radians); + } mRadians = radians; + + if (errorRadians < 0.0 || errorRadians > Math.PI) { + throw new IllegalArgumentException("Invalid error radians: " + errorRadians); + } mErrorRadians = errorRadians; + + if (confidenceLevel < 0.0 || confidenceLevel > 1.0) { + throw new IllegalArgumentException("Invalid confidence level: " + confidenceLevel); + } mConfidenceLevel = confidenceLevel; } @@ -122,11 +143,7 @@ public final class AngleMeasurement implements Parcelable { new Creator<AngleMeasurement>() { @Override public AngleMeasurement createFromParcel(Parcel in) { - Builder builder = new Builder(); - builder.setRadians(in.readDouble()); - builder.setErrorRadians(in.readDouble()); - builder.setConfidenceLevel(in.readDouble()); - return builder.build(); + return new AngleMeasurement(in.readDouble(), in.readDouble(), in.readDouble()); } @Override @@ -134,82 +151,4 @@ public final class AngleMeasurement implements Parcelable { return new AngleMeasurement[size]; } }; - - /** - * Builder class for {@link AngleMeasurement}. - */ - public static final class Builder { - private double mRadians = Double.NaN; - private double mErrorRadians = Double.NaN; - private double mConfidenceLevel = Double.NaN; - - /** - * Set the angle in radians - * - * @param radians angle in radians - * @throws IllegalArgumentException if angle exceeds allowed limits of [-Math.PI, +Math.PI] - */ - @NonNull - public Builder setRadians(double radians) { - if (radians < -Math.PI || radians > Math.PI) { - throw new IllegalArgumentException("Invalid radians: " + radians); - } - mRadians = radians; - return this; - } - - /** - * Set the angle error in radians - * - * @param errorRadians error of the angle in radians - * @throws IllegalArgumentException if the error exceeds the allowed limits of [0, +Math.PI] - */ - @NonNull - public Builder setErrorRadians(double errorRadians) { - if (errorRadians < 0.0 || errorRadians > Math.PI) { - throw new IllegalArgumentException( - "Invalid error radians: " + errorRadians); - } - mErrorRadians = errorRadians; - return this; - } - - /** - * Set the angle confidence level - * - * @param confidenceLevel level of confidence of the angle measurement - * @throws IllegalArgumentException if the error exceeds the allowed limits of [0.0, 1.0] - */ - @NonNull - public Builder setConfidenceLevel(double confidenceLevel) { - if (confidenceLevel < 0.0 || confidenceLevel > 1.0) { - throw new IllegalArgumentException( - "Invalid confidence level: " + confidenceLevel); - } - mConfidenceLevel = confidenceLevel; - return this; - } - - /** - * Build the {@link AngleMeasurement} object - * - * @throws IllegalStateException if angle, error, or confidence values are missing - */ - @NonNull - public AngleMeasurement build() { - if (Double.isNaN(mRadians)) { - throw new IllegalStateException("Angle is not set"); - } - - if (Double.isNaN(mErrorRadians)) { - throw new IllegalStateException("Angle error is not set"); - } - - if (Double.isNaN(mConfidenceLevel)) { - throw new IllegalStateException("Angle confidence level is not set"); - } - - return new AngleMeasurement(mRadians, mErrorRadians, mConfidenceLevel); - } - } } diff --git a/core/java/android/uwb/AngleOfArrivalMeasurement.java b/core/java/android/uwb/AngleOfArrivalMeasurement.java index 3d8626b98bed..db04ad16c191 100644 --- a/core/java/android/uwb/AngleOfArrivalMeasurement.java +++ b/core/java/android/uwb/AngleOfArrivalMeasurement.java @@ -116,9 +116,8 @@ public final class AngleOfArrivalMeasurement implements Parcelable { new Creator<AngleOfArrivalMeasurement>() { @Override public AngleOfArrivalMeasurement createFromParcel(Parcel in) { - Builder builder = new Builder(); - - builder.setAzimuth(in.readParcelable(AngleMeasurement.class.getClassLoader())); + Builder builder = + new Builder(in.readParcelable(AngleMeasurement.class.getClassLoader())); builder.setAltitude(in.readParcelable(AngleMeasurement.class.getClassLoader())); @@ -135,18 +134,16 @@ public final class AngleOfArrivalMeasurement implements Parcelable { * Builder class for {@link AngleOfArrivalMeasurement}. */ public static final class Builder { - private AngleMeasurement mAzimuthAngleMeasurement = null; + private final AngleMeasurement mAzimuthAngleMeasurement; private AngleMeasurement mAltitudeAngleMeasurement = null; /** - * Set the azimuth angle + * Constructs an {@link AngleOfArrivalMeasurement} object * - * @param azimuthAngle azimuth angle + * @param azimuthAngle the azimuth angle of the measurement */ - @NonNull - public Builder setAzimuth(@NonNull AngleMeasurement azimuthAngle) { + public Builder(@NonNull AngleMeasurement azimuthAngle) { mAzimuthAngleMeasurement = azimuthAngle; - return this; } /** @@ -162,15 +159,9 @@ public final class AngleOfArrivalMeasurement implements Parcelable { /** * Build the {@link AngleOfArrivalMeasurement} object - * - * @throws IllegalStateException if the required azimuth angle is not provided */ @NonNull public AngleOfArrivalMeasurement build() { - if (mAzimuthAngleMeasurement == null) { - throw new IllegalStateException("Azimuth angle measurement is not set"); - } - return new AngleOfArrivalMeasurement(mAzimuthAngleMeasurement, mAltitudeAngleMeasurement); } diff --git a/core/java/android/uwb/IUwbAdapter.aidl b/core/java/android/uwb/IUwbAdapter.aidl index 468a69c7bddb..4036892fb9e7 100644 --- a/core/java/android/uwb/IUwbAdapter.aidl +++ b/core/java/android/uwb/IUwbAdapter.aidl @@ -62,9 +62,6 @@ interface IUwbAdapter { /** * Request to open a new ranging session * - * This function must return before calling any functions in - * IUwbAdapterCallbacks. - * * This function does not start the ranging session, but all necessary * components must be initialized and ready to start a new ranging * session prior to calling IUwbAdapterCallback#onRangingOpened. @@ -77,12 +74,16 @@ interface IUwbAdapter { * RANGING_SESSION_OPEN_THRESHOLD_MS milliseconds of #openRanging being called * if the ranging session fails to be opened. * + * If the provided sessionHandle is already open for the calling client, then + * #onRangingOpenFailed must be called and the new session must not be opened. + * + * @param sessionHandle the session handle to open ranging for * @param rangingCallbacks the callbacks used to deliver ranging information * @param parameters the configuration to use for ranging - * @return a SessionHandle used to identify this ranging request */ - SessionHandle openRanging(in IUwbRangingCallbacks rangingCallbacks, - in PersistableBundle parameters); + void openRanging(in SessionHandle sessionHandle, + in IUwbRangingCallbacks rangingCallbacks, + in PersistableBundle parameters); /** * Request to start ranging diff --git a/core/java/android/uwb/RangingManager.java b/core/java/android/uwb/RangingManager.java index c0d818774ba0..85f2c1ccc180 100644 --- a/core/java/android/uwb/RangingManager.java +++ b/core/java/android/uwb/RangingManager.java @@ -17,6 +17,7 @@ package android.uwb; import android.annotation.NonNull; +import android.os.CancellationSignal; import android.os.PersistableBundle; import android.os.RemoteException; import android.util.Log; @@ -32,6 +33,7 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { private final IUwbAdapter mAdapter; private final Hashtable<SessionHandle, RangingSession> mRangingSessionTable = new Hashtable<>(); + private int mNextSessionId = 1; public RangingManager(IUwbAdapter adapter) { mAdapter = adapter; @@ -44,29 +46,26 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { * @param executor {@link Executor} to run callbacks * @param callbacks {@link RangingSession.Callback} to associate with the {@link RangingSession} * that is being opened. - * @return a new {@link RangingSession} + * @return a {@link CancellationSignal} that may be used to cancel the opening of the + * {@link RangingSession}. */ - public RangingSession openSession(@NonNull PersistableBundle params, @NonNull Executor executor, + public CancellationSignal openSession(@NonNull PersistableBundle params, + @NonNull Executor executor, @NonNull RangingSession.Callback callbacks) { - SessionHandle sessionHandle; - try { - sessionHandle = mAdapter.openRanging(this, params); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - synchronized (this) { - if (hasSession(sessionHandle)) { - Log.w(TAG, "Newly created session unexpectedly reuses an active SessionHandle"); - executor.execute(() -> callbacks.onClosed( - RangingSession.Callback.REASON_GENERIC_ERROR, - new PersistableBundle())); - } - + SessionHandle sessionHandle = new SessionHandle(mNextSessionId++); RangingSession session = new RangingSession(executor, callbacks, mAdapter, sessionHandle); mRangingSessionTable.put(sessionHandle, session); - return session; + try { + mAdapter.openRanging(sessionHandle, this, params); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + CancellationSignal cancellationSignal = new CancellationSignal(); + cancellationSignal.setOnCancelListener(() -> session.close()); + return cancellationSignal; } } diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java index 63a6d058f358..844bbbe7970b 100644 --- a/core/java/android/uwb/UwbManager.java +++ b/core/java/android/uwb/UwbManager.java @@ -25,6 +25,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; +import android.os.CancellationSignal; import android.os.IBinder; import android.os.PersistableBundle; import android.os.RemoteException; @@ -228,14 +229,14 @@ public final class UwbManager { * @param callbacks {@link RangingSession.Callback} to associate with the * {@link RangingSession} that is being opened. * - * @return an {@link AutoCloseable} that is able to be used to close or cancel the opening of a + * @return an {@link CancellationSignal} that is able to be used to cancel the opening of a * {@link RangingSession} that has been requested through {@link #openRangingSession} * but has not yet been made available by * {@link RangingSession.Callback#onOpened(RangingSession)}. */ @NonNull @RequiresPermission(Manifest.permission.UWB_PRIVILEGED) - public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters, + public CancellationSignal openRangingSession(@NonNull PersistableBundle parameters, @NonNull @CallbackExecutor Executor executor, @NonNull RangingSession.Callback callbacks) { return mRangingManager.openSession(parameters, executor, callbacks); diff --git a/core/java/android/view/CrossWindowBlurListeners.java b/core/java/android/view/CrossWindowBlurListeners.java index 5a1b850133cb..55fc4f41f5eb 100644 --- a/core/java/android/view/CrossWindowBlurListeners.java +++ b/core/java/android/view/CrossWindowBlurListeners.java @@ -16,13 +16,19 @@ package android.view; +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.os.Binder; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.SystemProperties; -import android.util.ArraySet; +import android.util.ArrayMap; import android.util.Log; +import com.android.internal.util.Preconditions; + +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -42,7 +48,7 @@ public final class CrossWindowBlurListeners { private static final Object sLock = new Object(); private final BlurEnabledListenerInternal mListenerInternal = new BlurEnabledListenerInternal(); - private final ArraySet<Consumer<Boolean>> mListeners = new ArraySet(); + private final ArrayMap<Consumer<Boolean>, Executor> mListeners = new ArrayMap(); private final Handler mMainHandler = new Handler(Looper.getMainLooper()); private boolean mInternalListenerAttached = false; private boolean mCrossWindowBlurEnabled; @@ -74,20 +80,22 @@ public final class CrossWindowBlurListeners { } } - void addListener(Consumer<Boolean> listener) { - if (listener == null) return; + void addListener(@NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<Boolean> listener) { + Preconditions.checkNotNull(listener, "listener cannot be null"); + Preconditions.checkNotNull(executor, "executor cannot be null"); synchronized (sLock) { attachInternalListenerIfNeededLocked(); - mListeners.add(listener); - notifyListenerOnMain(listener, mCrossWindowBlurEnabled); + mListeners.put(listener, executor); + notifyListener(listener, executor, mCrossWindowBlurEnabled); } } void removeListener(Consumer<Boolean> listener) { - if (listener == null) return; + Preconditions.checkNotNull(listener, "listener cannot be null"); synchronized (sLock) { mListeners.remove(listener); @@ -116,10 +124,8 @@ public final class CrossWindowBlurListeners { } } - private void notifyListenerOnMain(Consumer<Boolean> listener, boolean enabled) { - mMainHandler.post(() -> { - listener.accept(enabled); - }); + private void notifyListener(Consumer<Boolean> listener, Executor executor, boolean enabled) { + executor.execute(() -> listener.accept(enabled)); } private final class BlurEnabledListenerInternal extends ICrossWindowBlurEnabledListener.Stub { @@ -128,8 +134,13 @@ public final class CrossWindowBlurListeners { synchronized (sLock) { mCrossWindowBlurEnabled = enabled; - for (int i = 0; i < mListeners.size(); i++) { - notifyListenerOnMain(mListeners.valueAt(i), enabled); + final long token = Binder.clearCallingIdentity(); + try { + for (int i = 0; i < mListeners.size(); i++) { + notifyListener(mListeners.keyAt(i), mListeners.valueAt(i), enabled); + } + } finally { + Binder.restoreCallingIdentity(token); } } } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index b98ef1410ebf..d484f4d47e99 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -84,6 +84,7 @@ public final class Display { private static final String TAG = "Display"; private static final boolean DEBUG = false; + private final Object mLock = new Object(); private final DisplayManagerGlobal mGlobal; private final int mDisplayId; private final int mFlags; @@ -569,7 +570,7 @@ public final class Display { * @return True if the display is still valid. */ public boolean isValid() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mIsValid; } @@ -584,7 +585,7 @@ public final class Display { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public boolean getDisplayInfo(DisplayInfo outDisplayInfo) { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); outDisplayInfo.copyFrom(mDisplayInfo); return mIsValid; @@ -601,7 +602,7 @@ public final class Display { * @hide */ public int getLayerStack() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.layerStack; } @@ -648,7 +649,7 @@ public final class Display { * @hide */ public DisplayAddress getAddress() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.address; } @@ -708,7 +709,7 @@ public final class Display { * @return The display's name. */ public String getName() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.name; } @@ -721,7 +722,7 @@ public final class Display { * @hide */ public float getBrightnessDefault() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.brightnessDefault; } @@ -760,7 +761,7 @@ public final class Display { */ @Deprecated public void getSize(Point outSize) { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments()); outSize.x = mTempMetrics.widthPixels; @@ -777,7 +778,7 @@ public final class Display { */ @Deprecated public void getRectSize(Rect outSize) { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments()); outSize.set(0, 0, mTempMetrics.widthPixels, mTempMetrics.heightPixels); @@ -815,7 +816,7 @@ public final class Display { * for example, screen decorations like the status bar are being hidden. */ public void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); outSmallestSize.x = mDisplayInfo.smallestNominalAppWidth; outSmallestSize.y = mDisplayInfo.smallestNominalAppHeight; @@ -831,7 +832,7 @@ public final class Display { */ @UnsupportedAppUsage public int getMaximumSizeDimension() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return Math.max(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); } @@ -842,7 +843,7 @@ public final class Display { */ @Deprecated public int getWidth() { - synchronized (this) { + synchronized (mLock) { updateCachedAppSizeIfNeededLocked(); return mCachedAppWidthCompat; } @@ -853,7 +854,7 @@ public final class Display { */ @Deprecated public int getHeight() { - synchronized (this) { + synchronized (mLock) { updateCachedAppSizeIfNeededLocked(); return mCachedAppHeightCompat; } @@ -878,7 +879,7 @@ public final class Display { */ @Surface.Rotation public int getRotation() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mMayAdjustByFixedRotation ? getDisplayAdjustments().getRotation(mDisplayInfo.rotation) @@ -904,7 +905,7 @@ public final class Display { */ @Nullable public DisplayCutout getCutout() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mMayAdjustByFixedRotation ? getDisplayAdjustments().getDisplayCutout(mDisplayInfo.displayCutout) @@ -922,7 +923,7 @@ public final class Display { @SuppressLint("VisiblySynchronized") @Nullable public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); RoundedCorners roundedCorners; if (mMayAdjustByFixedRotation) { @@ -954,7 +955,7 @@ public final class Display { * Gets the refresh rate of this display in frames per second. */ public float getRefreshRate() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.getRefreshRate(); } @@ -970,7 +971,7 @@ public final class Display { */ @Deprecated public float[] getSupportedRefreshRates() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.getDefaultRefreshRates(); } @@ -980,7 +981,7 @@ public final class Display { * Returns the active mode of the display. */ public Mode getMode() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.getMode(); } @@ -990,7 +991,7 @@ public final class Display { * Gets the supported modes of this display. */ public Mode[] getSupportedModes() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); final Display.Mode[] modes = mDisplayInfo.supportedModes; return Arrays.copyOf(modes, modes.length); @@ -1016,7 +1017,7 @@ public final class Display { */ @SuppressLint("VisiblySynchronized") public boolean isMinimalPostProcessingSupported() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.minimalPostProcessingSupported; } @@ -1036,7 +1037,7 @@ public final class Display { * @hide */ public int getColorMode() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.colorMode; } @@ -1063,7 +1064,7 @@ public final class Display { * @see #isHdr() */ public HdrCapabilities getHdrCapabilities() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.hdrCapabilities; } @@ -1076,7 +1077,7 @@ public final class Display { * @see HdrCapabilities#getSupportedHdrTypes() */ public boolean isHdr() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.isHdr(); } @@ -1089,7 +1090,7 @@ public final class Display { * {@link Configuration#isScreenWideColorGamut()}. */ public boolean isWideColorGamut() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.isWideColorGamut(); } @@ -1104,7 +1105,7 @@ public final class Display { */ @Nullable public ColorSpace getPreferredWideGamutColorSpace() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); if (mDisplayInfo.isWideColorGamut()) { return mGlobal.getPreferredWideGamutColorSpace(); @@ -1118,7 +1119,7 @@ public final class Display { * @hide */ public int[] getSupportedColorModes() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); int[] colorModes = mDisplayInfo.supportedColorModes; return Arrays.copyOf(colorModes, colorModes.length); @@ -1135,7 +1136,7 @@ public final class Display { @NonNull @TestApi public @ColorMode ColorSpace[] getSupportedWideColorGamut() { - synchronized (this) { + synchronized (mLock) { final ColorSpace[] defaultColorSpaces = new ColorSpace[0]; updateDisplayInfoLocked(); if (!isWideColorGamut()) { @@ -1169,7 +1170,7 @@ public final class Display { * A/V synchronization. */ public long getAppVsyncOffsetNanos() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.appVsyncOffsetNanos; } @@ -1187,7 +1188,7 @@ public final class Display { * ({@link System#nanoTime}). */ public long getPresentationDeadlineNanos() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mDisplayInfo.presentationDeadlineNanos; } @@ -1202,7 +1203,10 @@ public final class Display { */ @Nullable public DeviceProductInfo getDeviceProductInfo() { - return mDisplayInfo.deviceProductInfo; + synchronized (mLock) { + updateDisplayInfoLocked(); + return mDisplayInfo.deviceProductInfo; + } } /** @@ -1235,7 +1239,7 @@ public final class Display { */ @Deprecated public void getMetrics(DisplayMetrics outMetrics) { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); mDisplayInfo.getAppMetrics(outMetrics, getDisplayAdjustments()); } @@ -1288,7 +1292,7 @@ public final class Display { */ @Deprecated public void getRealSize(Point outSize) { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); if (shouldReportMaxBounds()) { final Rect bounds = mResources.getConfiguration() @@ -1358,7 +1362,7 @@ public final class Display { */ @Deprecated public void getRealMetrics(DisplayMetrics outMetrics) { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); if (shouldReportMaxBounds()) { mDisplayInfo.getMaxBoundsMetrics(outMetrics, @@ -1434,7 +1438,7 @@ public final class Display { * {@link #STATE_UNKNOWN}. */ public int getState() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); return mIsValid ? mDisplayInfo.state : STATE_UNKNOWN; } @@ -1518,7 +1522,7 @@ public final class Display { // For debugging purposes @Override public String toString() { - synchronized (this) { + synchronized (mLock) { updateDisplayInfoLocked(); final DisplayAdjustments adjustments = getDisplayAdjustments(); mDisplayInfo.getAppMetrics(mTempMetrics, adjustments); diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index a8aaeb7846a2..9aaf5c066073 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -636,7 +636,9 @@ public final class DisplayInfo implements Parcelable { public void getMaxBoundsMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, Configuration configuration) { Rect bounds = configuration.windowConfiguration.getMaxBounds(); - getMetricsWithSize(outMetrics, compatInfo, configuration, bounds.width(), bounds.height()); + // Pass in null configuration to ensure width and height are not overridden to app bounds. + getMetricsWithSize(outMetrics, compatInfo, /* configuration= */ null, + bounds.width(), bounds.height()); } public int getNaturalWidth() { diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl index afbd2493bad4..ddb49786dce6 100644 --- a/core/java/android/view/IRecentsAnimationController.aidl +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -142,7 +142,13 @@ interface IRecentsAnimationController { * * The system reparents the leash of navigation bar to the app when the recents animation starts * and Launcher should call this method to let system restore the navigation bar to its - * original position when the quick switch gesture is finished. + * original position when the quick switch gesture is finished and will run the fade-in + * animation If {@param moveHomeToTop} is {@code true}. Otherwise, restore the navigtation bar + * without animation. + * + * @param moveHomeToTop if {@code true}, the home activity should be moved to the top. + * Otherwise, the home activity is hidden and the user is returned to the + * app. */ - void detachNavigationBarFromApp(); + void detachNavigationBarFromApp(boolean moveHomeToTop); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 54778007c6ff..b345b2e58252 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -816,4 +816,6 @@ interface IWindowManager * @param listener the listener to be unregistered */ void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener); + + void setForceCrossWindowBlurDisabled(boolean disable); } diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java index 02a97888cffd..aa1acc1217df 100644 --- a/core/java/android/view/KeyCharacterMap.java +++ b/core/java/android/view/KeyCharacterMap.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.input.InputManager; import android.os.Build; @@ -25,6 +26,8 @@ import android.text.method.MetaKeyKeyListener; import android.util.AndroidRuntimeException; import android.util.SparseIntArray; +import com.android.internal.annotations.VisibleForTesting; + import java.text.Normalizer; /** @@ -297,6 +300,8 @@ public class KeyCharacterMap implements Parcelable { private static native char nativeGetDisplayLabel(long ptr, int keyCode); private static native int nativeGetKeyboardType(long ptr); private static native KeyEvent[] nativeGetEvents(long ptr, char[] chars); + private static native KeyCharacterMap nativeObtainEmptyKeyCharacterMap(int deviceId); + private static native boolean nativeEquals(long ptr1, long ptr2); private KeyCharacterMap(Parcel in) { if (in == null) { @@ -323,6 +328,18 @@ public class KeyCharacterMap implements Parcelable { } /** + * Obtain empty key character map + * @param deviceId The input device ID + * @return The KeyCharacterMap object + * @hide + */ + @VisibleForTesting + @Nullable + public static KeyCharacterMap obtainEmptyMap(int deviceId) { + return nativeObtainEmptyKeyCharacterMap(deviceId); + } + + /** * Loads the key character maps for the keyboard with the specified device id. * * @param deviceId The device id of the keyboard. @@ -729,6 +746,18 @@ public class KeyCharacterMap implements Parcelable { return 0; } + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof KeyCharacterMap)) { + return false; + } + KeyCharacterMap peer = (KeyCharacterMap) obj; + if (mPtr == 0 || peer.mPtr == 0) { + return mPtr == peer.mPtr; + } + return nativeEquals(mPtr, peer.mPtr); + } + /** * Thrown by {@link KeyCharacterMap#load} when a key character map could not be loaded. */ diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index cbb86de4785f..31f6f6afdd53 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -78,3 +78,8 @@ per-file SyncRtSurfaceTransactionApplier.java = file:/services/core/java/com/and per-file ViewRootInsetsControllerHost.java = file:/services/core/java/com/android/server/wm/OWNERS per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS per-file Window*.aidl = file:/services/core/java/com/android/server/wm/OWNERS + +# Scroll Capture +per-file *ScrollCapture*.aidl = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9fc415d6401f..35726c0c1f04 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -8572,6 +8572,17 @@ public final class ViewRootImpl implements ViewParent, } @Override + public void onDragEvent(boolean isExiting, float x, float y) { + // force DRAG_EXITED_EVENT if appropriate + DragEvent event = DragEvent.obtain( + isExiting ? DragEvent.ACTION_DRAG_EXITED : DragEvent.ACTION_DRAG_LOCATION, + x, y, 0 /* offsetX */, 0 /* offsetY */, null/* localState */, + null/* description */, null /* data */, null /* dragSurface */, + null /* dragAndDropPermissions */, false /* result */); + dispatchDragEvent(event); + } + + @Override public void dispose() { unscheduleConsumeBatchedInput(); super.dispose(); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 7338c7d9a581..04512c9abc0a 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -81,6 +81,7 @@ import static android.view.WindowLayoutParamsProto.X; import static android.view.WindowLayoutParamsProto.Y; import android.Manifest.permission; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -121,6 +122,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -864,6 +866,33 @@ public interface WindowManager extends ViewManager { } /** + * Adds a listener, which will be called when cross-window blurs are enabled/disabled at + * runtime. This affects both window blur behind (see {@link LayoutParams#setBlurBehindRadius}) + * and window background blur (see {@link Window#setBackgroundBlurRadius}). + * + * Cross-window blur might not be supported by some devices due to GPU limitations. It can also + * be disabled at runtime, e.g. during battery saving mode, when multimedia tunneling is used or + * when minimal post processing is requested. In such situations, no blur will be computed or + * drawn, so the blur target area will not be blurred. To handle this, the app might want to + * change its theme to one that does not use blurs. + * + * If the listener is added successfully, it will be called immediately with the current + * cross-window blur enabled state. + * + * @param executor {@link Executor} to handle the listener callback + * @param listener the listener to be added. It will be called back with a boolean parameter, + * which is true if cross-window blur is enabled and false if it is disabled + * + * @see #removeCrossWindowBlurEnabledListener + * @see #isCrossWindowBlurEnabled + * @see LayoutParams#setBlurBehindRadius + * @see Window#setBackgroundBlurRadius + */ + default void addCrossWindowBlurEnabledListener(@NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<Boolean> listener) { + } + + /** * Removes a listener, previously added with {@link #addCrossWindowBlurEnabledListener} * * @param listener the listener to be removed @@ -873,6 +902,20 @@ public interface WindowManager extends ViewManager { default void removeCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) { } + /** + * Disables cross-window blurs device-wide. This includes window blur behind + * (see {@link LayoutParams#setBlurBehindRadius}) and window background blur + * (see {@link Window#setBackgroundBlurRadius}). + * + * @param disable specifies whether to disable the blur. Note that calling this + * with 'disable=false' will not enable blurs if there is something + * else disabling blurs. + * @hide + */ + @TestApi + default void setForceCrossWindowBlurDisabled(boolean disable) { + } + public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { /** * X position for this window. With the default gravity it is ignored. diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index b39870738d68..8dce852a2d62 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; @@ -40,6 +41,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import java.util.List; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -310,11 +312,26 @@ public final class WindowManagerImpl implements WindowManager { @Override public void addCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) { - CrossWindowBlurListeners.getInstance().addListener(listener); + addCrossWindowBlurEnabledListener(mContext.getMainExecutor(), listener); + } + + @Override + public void addCrossWindowBlurEnabledListener(@NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<Boolean> listener) { + CrossWindowBlurListeners.getInstance().addListener(executor, listener); } @Override public void removeCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) { CrossWindowBlurListeners.getInstance().removeListener(listener); } + + @Override + public void setForceCrossWindowBlurDisabled(boolean disable) { + try { + WindowManagerGlobal.getWindowManagerService() + .setForceCrossWindowBlurDisabled(disable); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 6ade5e622eab..de4554b9e624 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -105,7 +105,7 @@ public interface InputMethod { */ @MainThread default void initializeInternal(IBinder token, int displayId, - IInputMethodPrivilegedOperations privilegedOperations, int configChanges) { + IInputMethodPrivilegedOperations privilegedOperations) { updateInputMethodDisplay(displayId); attachToken(token); } diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 25712f8bf9b8..5d876a6f62d3 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -18,23 +18,19 @@ package android.view.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; -import android.inputmethodservice.InputMethodService; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; @@ -64,7 +60,6 @@ import java.util.List; * @attr ref android.R.styleable#InputMethod_isDefault * @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod * @attr ref android.R.styleable#InputMethod_supportsInlineSuggestions - * @attr ref android.R.styleable#InputMethod_configChanges */ public final class InputMethodInfo implements Parcelable { static final String TAG = "InputMethodInfo"; @@ -123,12 +118,6 @@ public final class InputMethodInfo implements Parcelable { private final boolean mInlineSuggestionsEnabled; /** - * The flag for configurations IME assumes the responsibility for handling in - * {@link InputMethodService#onConfigurationChanged(Configuration)}}. - */ - private final int mHandledConfigChanges; - - /** * @param service the {@link ResolveInfo} corresponds in which the IME is implemented. * @return a unique ID to be returned by {@link #getId()}. We have used * {@link ComponentName#flattenToShortString()} for this purpose (and it is already @@ -214,8 +203,6 @@ public final class InputMethodInfo implements Parcelable { false); inlineSuggestionsEnabled = sa.getBoolean( com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, false); - mHandledConfigChanges = sa.getInt( - com.android.internal.R.styleable.InputMethod_configChanges, 0); sa.recycle(); final int depth = parser.getDepth(); @@ -300,7 +287,6 @@ public final class InputMethodInfo implements Parcelable { mIsVrOnly = source.readBoolean(); mService = ResolveInfo.CREATOR.createFromParcel(source); mSubtypes = new InputMethodSubtypeArray(source); - mHandledConfigChanges = source.readInt(); mForceDefault = false; } @@ -312,22 +298,7 @@ public final class InputMethodInfo implements Parcelable { this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, settingsActivity, null /* subtypes */, 0 /* isDefaultResId */, false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, - false /* inlineSuggestionsEnabled */, false /* isVrOnly */, - 0 /* handledConfigChanges */); - } - - /** - * Temporary API for creating a built-in input method for test. - * @hide - */ - @TestApi - public InputMethodInfo(@NonNull String packageName, @NonNull String className, - @NonNull CharSequence label, @NonNull String settingsActivity, - int handledConfigChanges) { - this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, - settingsActivity, null /* subtypes */, 0 /* isDefaultResId */, - false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, - false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges); + false /* inlineSuggestionsEnabled */, false /* isVrOnly */); } /** @@ -339,7 +310,7 @@ public final class InputMethodInfo implements Parcelable { boolean forceDefault) { this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault, true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, - false /* isVrOnly */, 0 /* handledconfigChanges */); + false /* isVrOnly */); } /** @@ -350,8 +321,7 @@ public final class InputMethodInfo implements Parcelable { List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault, boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) { this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault, - supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly, - 0 /* handledConfigChanges */); + supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly); } /** @@ -361,7 +331,7 @@ public final class InputMethodInfo implements Parcelable { public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault, boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled, - boolean isVrOnly, int handledConfigChanges) { + boolean isVrOnly) { final ServiceInfo si = ri.serviceInfo; mService = ri; mId = new ComponentName(si.packageName, si.name).flattenToShortString(); @@ -373,7 +343,6 @@ public final class InputMethodInfo implements Parcelable { mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; mInlineSuggestionsEnabled = inlineSuggestionsEnabled; mIsVrOnly = isVrOnly; - mHandledConfigChanges = handledConfigChanges; } private static ResolveInfo buildFakeResolveInfo(String packageName, String className, @@ -520,17 +489,6 @@ public final class InputMethodInfo implements Parcelable { } } - /** - * Returns the bit mask of kinds of configuration changes that this IME - * can handle itself (without being restarted by the system). - * - * @attr ref android.R.styleable#InputMethod_configChanges - */ - @ActivityInfo.Config - public int getConfigChanges() { - return mHandledConfigChanges; - } - public void dump(Printer pw, String prefix) { pw.println(prefix + "mId=" + mId + " mSettingsActivityName=" + mSettingsActivityName @@ -621,7 +579,6 @@ public final class InputMethodInfo implements Parcelable { dest.writeBoolean(mIsVrOnly); mService.writeToParcel(dest, flags); mSubtypes.writeToParcel(dest); - dest.writeInt(mHandledConfigChanges); } /** diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index a8fff8bb6a43..53bbc0ab1a02 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -106,7 +106,6 @@ import com.android.internal.view.InputBindResult; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collections; @@ -413,7 +412,7 @@ public final class InputMethodManager { * The InputConnection that was last retrieved from the served view. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - ControlledInputConnectionWrapper mServedInputConnectionWrapper; + IInputConnectionWrapper mServedInputConnectionWrapper; /** * The completions that were last provided by the served view. */ @@ -740,8 +739,7 @@ public final class InputMethodManager { /** * Checks whether the active input connection (if any) is for the given view. * - * TODO(b/160968797): Remove this method and move mServedInputConnectionWrapper to - * ImeFocusController. + * TODO(b/182259171): Clean-up hasActiveConnection to simplify the logic. * * Note that this method is only intended for restarting input after focus gain * (e.g. b/160391516), DO NOT leverage this method to do another check. @@ -755,7 +753,7 @@ public final class InputMethodManager { return mServedInputConnectionWrapper != null && mServedInputConnectionWrapper.isActive() - && mServedInputConnectionWrapper.mServedView.get() == view; + && mServedInputConnectionWrapper.getServedView() == view; } } } @@ -1022,77 +1020,6 @@ public final class InputMethodManager { } } - private static class ControlledInputConnectionWrapper extends IInputConnectionWrapper { - private final InputMethodManager mParentInputMethodManager; - private final WeakReference<View> mServedView; - - ControlledInputConnectionWrapper(Looper icLooper, InputConnection conn, - InputMethodManager inputMethodManager, View servedView) { - super(icLooper, conn); - mParentInputMethodManager = inputMethodManager; - mServedView = new WeakReference<>(servedView); - } - - @Override - public boolean isActive() { - return mParentInputMethodManager.mActive && !isFinished(); - } - - @Override - public InputMethodManager getIMM() { - return mParentInputMethodManager; - } - - void deactivate() { - if (isFinished()) { - // This is a small performance optimization. Still only the 1st call of - // reportFinish() will take effect. - return; - } - closeConnection(); - - // Notify the app that the InputConnection was closed. - final View servedView = mServedView.get(); - if (servedView != null) { - final Handler handler = servedView.getHandler(); - // The handler is null if the view is already detached. When that's the case, for - // now, we simply don't dispatch this callback. - if (handler != null) { - if (DEBUG) { - Log.v(TAG, "Calling View.onInputConnectionClosed: view=" + servedView); - } - if (handler.getLooper().isCurrentThread()) { - servedView.onInputConnectionClosedInternal(); - } else { - handler.post(servedView::onInputConnectionClosedInternal); - } - } - } - } - - @Override - public String toString() { - return "ControlledInputConnectionWrapper{" - + "connection=" + getInputConnection() - + " finished=" + isFinished() - + " mParentInputMethodManager.mActive=" + mParentInputMethodManager.mActive - + " mServedView=" + mServedView.get() - + "}"; - } - - void dumpDebug(ProtoOutputStream proto, long fieldId) { - // Check that the call is initiated in the main thread of the current InputConnection - // {@link InputConnection#getHandler} since the messages to IInputConnectionWrapper are - // executed on this thread. Otherwise the messages are dispatched to the correct thread - // in IInputConnectionWrapper, but this is not wanted while dumpng, for performance - // reasons. - if (getInputConnection() instanceof DumpableInputConnection && Looper.myLooper() - == getLooper()) { - ((DumpableInputConnection) getInputConnection()).dumpDebug(proto, fieldId); - } - } - } - final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { @@ -1256,8 +1183,7 @@ public final class InputMethodManager { mMainLooper = looper; mH = new H(looper); mDisplayId = displayId; - mIInputContext = new ControlledInputConnectionWrapper(looper, mDummyInputConnection, this, - null); + mIInputContext = new IInputConnectionWrapper(looper, mDummyInputConnection, this, null); } /** @@ -2063,7 +1989,7 @@ public final class InputMethodManager { mServedInputConnectionWrapper.deactivate(); mServedInputConnectionWrapper = null; } - ControlledInputConnectionWrapper servedContext; + IInputConnectionWrapper servedContext; final int missingMethodFlags; if (ic != null) { mCursorSelStart = tba.initialSelStart; @@ -2080,7 +2006,7 @@ public final class InputMethodManager { } else { icHandler = ic.getHandler(); } - servedContext = new ControlledInputConnectionWrapper( + servedContext = new IInputConnectionWrapper( icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this, view); } else { servedContext = null; diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index cf3358b0dfbb..9f90b3bf1322 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -25,6 +25,7 @@ import android.annotation.NonNull; import android.annotation.WorkerThread; import android.app.Activity; import android.content.Context; +import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; @@ -54,6 +55,11 @@ import java.util.function.Consumer; */ public class UiTranslationController { + // TODO(b/182433547): remove Build.IS_DEBUGGABLE before ship. Enable the logging in debug build + // to help the debug during the development phase + public static final boolean DEBUG = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG) + || Build.IS_DEBUGGABLE; + private static final String TAG = "UiTranslationController"; @NonNull private final Activity mActivity; @@ -93,6 +99,8 @@ public class UiTranslationController { if (!mActivity.isResumed()) { return; } + Log.i(TAG, "updateUiTranslationState state: " + stateToString(state) + + (DEBUG ? ", views: " + views : "")); switch (state) { case STATE_UI_TRANSLATION_STARTED: final Pair<TranslationSpec, TranslationSpec> specs = @@ -149,8 +157,69 @@ public class UiTranslationController { translator.dump(outerPrefix, pw); pw.println(); } + synchronized (mLock) { + final int viewSize = mViews.size(); + pw.print(outerPrefix); pw.print("number views: "); pw.println(viewSize); + for (int i = 0; i < viewSize; i++) { + pw.print(outerPrefix); pw.print("#"); pw.println(i); + final AutofillId autofillId = mViews.keyAt(i); + final View view = mViews.valueAt(i).get(); + pw.print(pfx); pw.print("autofillId: "); pw.println(autofillId); + pw.print(pfx); pw.print("view:"); pw.println(view); + } + } + // TODO(b/182433547): we will remove debug rom condition before S release then we change + // change this back to "DEBUG" + if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) { + dumpViewByTraversal(outerPrefix, pw); + } + } + + private void dumpViewByTraversal(String outerPrefix, PrintWriter pw) { + final ArrayList<ViewRootImpl> roots = + WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken()); + pw.print(outerPrefix); pw.println("Dump views:"); + for (int rootNum = 0; rootNum < roots.size(); rootNum++) { + final View rootView = roots.get(rootNum).getView(); + if (rootView instanceof ViewGroup) { + dumpChildren((ViewGroup) rootView, outerPrefix, pw); + } else { + dumpViewInfo(rootView, outerPrefix, pw); + } + } + } + + private void dumpChildren(ViewGroup viewGroup, String outerPrefix, PrintWriter pw) { + final int childCount = viewGroup.getChildCount(); + for (int i = 0; i < childCount; ++i) { + final View child = viewGroup.getChildAt(i); + if (child instanceof ViewGroup) { + pw.print(outerPrefix); pw.println("Children: "); + pw.print(outerPrefix); pw.print(outerPrefix); pw.println(child); + dumpChildren((ViewGroup) child, outerPrefix, pw); + } else { + pw.print(outerPrefix); pw.println("End Children: "); + pw.print(outerPrefix); pw.print(outerPrefix); pw.print(child); + dumpViewInfo(child, outerPrefix, pw); + } + } } + private void dumpViewInfo(View view, String outerPrefix, PrintWriter pw) { + final AutofillId autofillId = view.getAutofillId(); + pw.print(outerPrefix); pw.print("autofillId: "); pw.print(autofillId); + // TODO: print TranslationTransformation + boolean isContainsView = false; + synchronized (mLock) { + final WeakReference<View> viewRef = mViews.get(autofillId); + if (viewRef != null && viewRef.get() != null) { + isContainsView = true; + } + } + pw.print(outerPrefix); pw.print("isContainsView: "); pw.println(isContainsView); + } + + /** * The method is used by {@link Translator}, it will be called when the translation is done. The * translation result can be get from here. @@ -171,6 +240,9 @@ public class UiTranslationController { return; } final int resultCount = translatedResult.size(); + if (DEBUG) { + Log.v(TAG, "onTranslationCompleted: receive " + resultCount + " responses."); + } synchronized (mLock) { for (int i = 0; i < resultCount; i++) { final ViewTranslationResponse response = translatedResult.get(i); @@ -180,7 +252,7 @@ public class UiTranslationController { } final View view = mViews.get(autofillId).get(); if (view == null) { - Log.w(TAG, "onTranslationCompleted: the Veiew for autofill id " + autofillId + Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId + " may be gone."); continue; } @@ -208,6 +280,10 @@ public class UiTranslationController { @WorkerThread private void sendTranslationRequest(Translator translator, List<ViewTranslationRequest> requests) { + if (requests.size() == 0) { + Log.wtf(TAG, "No ViewTranslationRequest was collected."); + return; + } final TranslationRequest request = new TranslationRequest.Builder() .setViewTranslationRequests(requests) .build(); @@ -233,7 +309,8 @@ public class UiTranslationController { requests.add(request); } if (currentCount == (foundViews.size() - 1)) { - Log.v(TAG, "onUiTranslationStarted: send " + requests.size() + " request."); + Log.v(TAG, "onUiTranslationStarted: collect " + requests.size() + + " requests."); mWorkerHandler.sendMessage(PooledLambda.obtainMessage( UiTranslationController::sendTranslationRequest, UiTranslationController.this, translator, requests)); @@ -287,6 +364,9 @@ public class UiTranslationController { for (int i = 0; i < viewCounts; i++) { final View view = views.valueAt(i).get(); if (view == null) { + if (DEBUG) { + Log.d(TAG, "View was gone for autofillid = " + views.keyAt(i)); + } continue; } action.accept(view); diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java index a3a6a2e52138..7c73e701b7c8 100644 --- a/core/java/android/view/translation/UiTranslationManager.java +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -43,6 +43,14 @@ public final class UiTranslationManager { private static final String TAG = "UiTranslationManager"; /** + * The tag which uses for enabling debug log dump. To enable it, we can use command "adb shell + * setprop log.tag.UiTranslation DEBUG". + * + * @hide + */ + public static final String LOG_TAG = "UiTranslation"; + + /** * The state caller request to disable utranslation,, it is no longer need to ui translation. * * @hide diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java index 34fe51e82e8f..42d75353e5df 100644 --- a/core/java/android/widget/AnalogClock.java +++ b/core/java/android/widget/AnalogClock.java @@ -200,6 +200,8 @@ public class AnalogClock extends View { mTimeZone = toZoneId(a.getString(com.android.internal.R.styleable.AnalogClock_timeZone)); createClock(); + a.recycle(); + mDialWidth = mDial.getIntrinsicWidth(); mDialHeight = mDial.getIntrinsicHeight(); } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 012352d0a0f5..7517b805da69 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -3790,7 +3790,7 @@ public class Editor { } public SuggestionsPopupWindow() { - mCursorWasVisibleBeforeSuggestions = mCursorVisible; + mCursorWasVisibleBeforeSuggestions = mTextView.isCursorVisibleFromAttr(); } @Override @@ -3957,7 +3957,7 @@ public class Editor { } if (updateSuggestions()) { - mCursorWasVisibleBeforeSuggestions = mCursorVisible; + mCursorWasVisibleBeforeSuggestions = mTextView.isCursorVisibleFromAttr(); mTextView.setCursorVisible(false); mIsShowingUp = true; super.show(); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 2328e589216b..d2f4cea69c88 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -120,6 +120,7 @@ import java.util.Objects; import java.util.Stack; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.function.Predicate; /** * A class that describes a view hierarchy that can be displayed in @@ -1993,22 +1994,79 @@ public class RemoteViews implements Parcelable, Filter { mIsRoot = false; } + private static boolean hasStableId(View view) { + Object tag = view.getTag(com.android.internal.R.id.remote_views_stable_id); + return tag != null; + } + + private static int getStableId(View view) { + Integer id = (Integer) view.getTag(com.android.internal.R.id.remote_views_stable_id); + return id == null ? ViewGroupActionAdd.NO_ID : id; + } + + private static void setStableId(View view, int stableId) { + view.setTagInternal(com.android.internal.R.id.remote_views_stable_id, stableId); + } + + // Returns the next recyclable child of the view group, or -1 if there are none. + private static int getNextRecyclableChild(ViewGroup vg) { + Integer tag = (Integer) vg.getTag(com.android.internal.R.id.remote_views_next_child); + return tag == null ? -1 : tag; + } + + private static int getViewLayoutId(View v) { + return (Integer) v.getTag(R.id.widget_frame); + } + + private static void setNextRecyclableChild(ViewGroup vg, int nextChild, int numChildren) { + if (nextChild < 0 || nextChild >= numChildren) { + vg.setTagInternal(com.android.internal.R.id.remote_views_next_child, -1); + } else { + vg.setTagInternal(com.android.internal.R.id.remote_views_next_child, nextChild); + } + } + + private void finalizeViewRecycling(ViewGroup root) { + // Remove any recyclable children that were not used. nextChild should either be -1 or point + // to the next recyclable child that hasn't been recycled. + int nextChild = getNextRecyclableChild(root); + if (nextChild >= 0 && nextChild < root.getChildCount()) { + root.removeViews(nextChild, root.getChildCount() - nextChild); + } + // Make sure on the next round, we don't try to recycle if removeAllViews is not called. + setNextRecyclableChild(root, -1, 0); + // Traverse the view tree. + for (int i = 0; i < root.getChildCount(); i++) { + View child = root.getChildAt(i); + if (child instanceof ViewGroup && !child.isRootNamespace()) { + finalizeViewRecycling((ViewGroup) child); + } + } + } + /** * ViewGroup methods that are related to adding Views. */ private class ViewGroupActionAdd extends Action { + static final int NO_ID = -1; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private RemoteViews mNestedViews; private int mIndex; + private int mStableId; ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews) { - this(viewId, nestedViews, -1 /* index */); + this(viewId, nestedViews, -1 /* index */, NO_ID /* nestedViewId */); } ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews, int index) { + this(viewId, nestedViews, index, NO_ID /* nestedViewId */); + } + + ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews, int index, int stableId) { this.viewId = viewId; mNestedViews = nestedViews; mIndex = index; + mStableId = stableId; if (nestedViews != null) { configureRemoteViewsAsChild(nestedViews); } @@ -2018,6 +2076,7 @@ public class RemoteViews implements Parcelable, Filter { int depth, Map<Class, Object> classCookies) { viewId = parcel.readInt(); mIndex = parcel.readInt(); + mStableId = parcel.readInt(); mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies); mNestedViews.addFlags(mApplyFlags); } @@ -2025,6 +2084,7 @@ public class RemoteViews implements Parcelable, Filter { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(viewId); dest.writeInt(mIndex); + dest.writeInt(mStableId); mNestedViews.writeToParcel(dest, flags); } @@ -2033,6 +2093,17 @@ public class RemoteViews implements Parcelable, Filter { return mNestedViews.hasSameAppInfo(parentInfo); } + private int findViewIndexToRecycle(ViewGroup target, RemoteViews newContent) { + for (int nextChild = getNextRecyclableChild(target); nextChild < target.getChildCount(); + nextChild++) { + View child = target.getChildAt(nextChild); + if (getStableId(child) == mStableId) { + return nextChild; + } + } + return -1; + } + @Override public void apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources) { @@ -2043,10 +2114,45 @@ public class RemoteViews implements Parcelable, Filter { return; } + // If removeAllViews was called, this returns the next potential recycled view. + // If there are no more views to recycle (or removeAllViews was not called), this + // will return -1. + final int nextChild = getNextRecyclableChild(target); + RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context); + if (nextChild >= 0 && mStableId != NO_ID) { + // At that point, the views starting at index nextChild are the ones recyclable but + // not yet recycled. All views added on that round of application are placed before. + // Find the next view with the same stable id, or -1. + int recycledViewIndex = findViewIndexToRecycle(target, rvToApply); + if (recycledViewIndex >= 0) { + View child = target.getChildAt(recycledViewIndex); + if (getViewLayoutId(child) == rvToApply.getLayoutId()) { + if (nextChild < recycledViewIndex) { + target.removeViews(nextChild, recycledViewIndex - nextChild); + } + setNextRecyclableChild(target, nextChild + 1, target.getChildCount()); + rvToApply.reapply(context, child, handler, null /* size */, colorResources, + false /* topLevel */); + return; + } + // If we cannot recycle the views, we still remove all views in between to + // avoid weird behaviors and insert the new view in place of the old one. + target.removeViews(nextChild, recycledViewIndex - nextChild + 1); + } + } + // If we cannot recycle, insert the new view before the next recyclable child. + // Inflate nested views and add as children - target.addView( - mNestedViews.apply(context, target, handler, null /* size */, colorResources), - mIndex); + View nestedView = rvToApply.apply(context, target, handler, null /* size */, + colorResources); + if (mStableId != NO_ID) { + setStableId(nestedView, mStableId); + } + target.addView(nestedView, mIndex >= 0 ? mIndex : nextChild); + if (nextChild >= 0) { + // If we are at the end, there is no reason to try to recycle anymore + setNextRecyclableChild(target, nextChild + 1, target.getChildCount()); + } } @Override @@ -2063,24 +2169,91 @@ public class RemoteViews implements Parcelable, Filter { // Inflate nested views and perform all the async tasks for the child remoteView. final Context context = root.mRoot.getContext(); - final AsyncApplyTask task = mNestedViews.getAsyncApplyTask(context, targetVg, - null /* listener */, handler, null /* size */, colorResources); + + // If removeAllViews was called, this returns the next potential recycled view. + // If there are no more views to recycle (or removeAllViews was not called), this + // will return -1. + final int nextChild = getNextRecyclableChild(targetVg); + if (nextChild >= 0 && mStableId != NO_ID) { + RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context); + final int recycledViewIndex = target.findChildIndex(nextChild, + view -> getStableId(view) == mStableId); + if (recycledViewIndex >= 0) { + // At that point, the views starting at index nextChild are the ones + // recyclable but not yet recycled. All views added on that round of + // application are placed before. + ViewTree recycled = target.mChildren.get(recycledViewIndex); + // We can only recycle the view if the layout id is the same. + if (getViewLayoutId(recycled.mRoot) == rvToApply.getLayoutId()) { + if (recycledViewIndex > nextChild) { + target.removeChildren(nextChild, recycledViewIndex - nextChild); + } + setNextRecyclableChild(targetVg, nextChild + 1, target.mChildren.size()); + final AsyncApplyTask reapplyTask = rvToApply.getInternalAsyncApplyTask( + context, + targetVg, null /* listener */, handler, null /* size */, + colorResources, + recycled.mRoot); + final ViewTree tree = reapplyTask.doInBackground(); + if (tree == null) { + throw new ActionException(reapplyTask.mError); + } + return new RuntimeAction() { + @Override + public void apply(View root, ViewGroup rootParent, + InteractionHandler handler, ColorResources colorResources) + throws ActionException { + reapplyTask.onPostExecute(tree); + if (recycledViewIndex > nextChild) { + targetVg.removeViews(nextChild, recycledViewIndex - nextChild); + } + } + }; + } + // If the layout id is different, still remove the children as if we recycled + // the view, to insert at the same place. + target.removeChildren(nextChild, recycledViewIndex - nextChild + 1); + return insertNewView(context, target, handler, colorResources, + () -> targetVg.removeViews(nextChild, + recycledViewIndex - nextChild + 1)); + + } + } + // If we cannot recycle, simply add the view at the same available slot. + return insertNewView(context, target, handler, colorResources, () -> {}); + } + + private Action insertNewView(Context context, ViewTree target, InteractionHandler handler, + ColorResources colorResources, Runnable finalizeAction) { + ViewGroup targetVg = (ViewGroup) target.mRoot; + int nextChild = getNextRecyclableChild(targetVg); + final AsyncApplyTask task = mNestedViews.getInternalAsyncApplyTask(context, targetVg, + null /* listener */, handler, null /* size */, colorResources, + null /* result */); final ViewTree tree = task.doInBackground(); if (tree == null) { throw new ActionException(task.mError); } + if (mStableId != NO_ID) { + setStableId(task.mResult, mStableId); + } // Update the global view tree, so that next call to findViewTreeById // goes through the subtree as well. - target.addChild(tree, mIndex); + final int insertIndex = mIndex >= 0 ? mIndex : nextChild; + target.addChild(tree, insertIndex); + if (nextChild >= 0) { + setNextRecyclableChild(targetVg, nextChild + 1, target.mChildren.size()); + } return new RuntimeAction() { @Override public void apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources) throws ActionException { task.onPostExecute(tree); - targetVg.addView(task.mResult, mIndex); + finalizeAction.run(); + targetVg.addView(task.mResult, insertIndex); } }; } @@ -2148,7 +2321,14 @@ public class RemoteViews implements Parcelable, Filter { } if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { - target.removeAllViews(); + // Remote any view without a stable id + for (int i = target.getChildCount() - 1; i >= 0; i--) { + if (!hasStableId(target.getChildAt(i))) { + target.removeViewAt(i); + } + } + // In the end, only children with a stable id (i.e. recyclable) are left. + setNextRecyclableChild(target, 0, target.getChildCount()); return; } @@ -2170,8 +2350,8 @@ public class RemoteViews implements Parcelable, Filter { final ViewGroup targetVg = (ViewGroup) target.mRoot; if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { - // Clear all children when there's no excepted view - target.mChildren = null; + target.mChildren.removeIf(childTree -> !hasStableId(childTree.mRoot)); + setNextRecyclableChild(targetVg, 0, target.mChildren.size()); } else { // Remove just the children which don't match the excepted view target.mChildren.removeIf(childTree -> childTree.mRoot.getId() != mViewIdToKeep); @@ -2184,7 +2364,11 @@ public class RemoteViews implements Parcelable, Filter { public void apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources) throws ActionException { if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { - targetVg.removeAllViews(); + for (int i = targetVg.getChildCount() - 1; i >= 0; i--) { + if (!hasStableId(targetVg.getChildAt(i))) { + targetVg.removeViewAt(i); + } + } return; } @@ -3084,6 +3268,7 @@ public class RemoteViews implements Parcelable, Filter { } mApplication = portrait.mApplication; mLayoutId = portrait.mLayoutId; + mViewId = portrait.mViewId; mLightBackgroundLayoutId = portrait.mLightBackgroundLayoutId; mLandscape = landscape; @@ -3136,6 +3321,7 @@ public class RemoteViews implements Parcelable, Filter { RemoteViews smallestView = findSmallestRemoteView(); mApplication = smallestView.mApplication; mLayoutId = smallestView.mLayoutId; + mViewId = smallestView.mViewId; mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId; } @@ -3253,6 +3439,7 @@ public class RemoteViews implements Parcelable, Filter { ApplicationInfo.CREATOR.createFromParcel(parcel); mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel); mLayoutId = parcel.readInt(); + mViewId = parcel.readInt(); mLightBackgroundLayoutId = parcel.readInt(); readActionsFromParcel(parcel, depth); @@ -3273,6 +3460,7 @@ public class RemoteViews implements Parcelable, Filter { RemoteViews smallestView = findSmallestRemoteView(); mApplication = smallestView.mApplication; mLayoutId = smallestView.mLayoutId; + mViewId = smallestView.mViewId; mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId; } else { // MODE_HAS_LANDSCAPE_AND_PORTRAIT @@ -3281,6 +3469,7 @@ public class RemoteViews implements Parcelable, Filter { mClassCookies); mApplication = mPortrait.mApplication; mLayoutId = mPortrait.mLayoutId; + mViewId = mPortrait.mViewId; mLightBackgroundLayoutId = mPortrait.mLightBackgroundLayoutId; } mApplyFlags = parcel.readInt(); @@ -3458,6 +3647,29 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the given + * {@link RemoteViews}. If the {@link RemoteViews} may be re-inflated or updated, + * {@link #removeAllViews(int)} must be called on the same {@code viewId + * } before the first call to this method for the behavior of this method to be predictable. + * + * The {@code stableId} will be used to identify a potential view to recycled when the remote + * view is inflated. Views can be re-used if inserted in the same order, potentially with + * some views appearing / disappearing. + * + * Note: if a view is re-used, all the actions will be re-applied on it. However, its properties + * are not reset, so what was applied in previous round will have an effect. As a view may be + * re-created at any time by the host, the RemoteViews should not rely on keeping information + * from previous applications and always re-set all the properties they need. + * + * @param viewId The id of the parent {@link ViewGroup} to add child into. + * @param nestedView {@link RemoteViews} that describes the child. + * @param stableId An id that is stable across different versions of RemoteViews. + */ + public void addStableView(@IdRes int viewId, @NonNull RemoteViews nestedView, int stableId) { + addAction(new ViewGroupActionAdd(viewId, nestedView, -1 /* index */, stableId)); + } + + /** * Equivalent to calling {@link ViewGroup#addView(View, int)} after inflating the * given {@link RemoteViews}. * @@ -4870,23 +5082,24 @@ public class RemoteViews implements Parcelable, Filter { public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener, InteractionHandler handler, SizeF size) { - return getAsyncApplyTask(context, parent, listener, handler, size, null /* themeColors */) - .startTaskOnExecutor(executor); + return applyAsync(context, parent, executor, listener, handler, size, + null /* themeColors */); } /** @hide */ public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener, InteractionHandler handler, SizeF size, ColorResources colorResources) { - return getAsyncApplyTask(context, parent, listener, handler, size, colorResources) - .startTaskOnExecutor(executor); + return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener, + handler, colorResources, null /* result */, + true /* topLevel */).startTaskOnExecutor(executor); } - private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent, + private AsyncApplyTask getInternalAsyncApplyTask(Context context, ViewGroup parent, OnViewAppliedListener listener, InteractionHandler handler, SizeF size, - ColorResources colorResources) { + ColorResources colorResources, View result) { return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener, - handler, colorResources, null /* result */); + handler, colorResources, result, false /* topLevel */); } private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree> @@ -4898,6 +5111,12 @@ public class RemoteViews implements Parcelable, Filter { final OnViewAppliedListener mListener; final InteractionHandler mHandler; final ColorResources mColorResources; + /** + * Whether the remote view is the top-level one (i.e. not within an action). + * + * This is only used if the result is specified (i.e. the view is being recycled). + */ + final boolean mTopLevel; private View mResult; private ViewTree mTree; @@ -4906,13 +5125,15 @@ public class RemoteViews implements Parcelable, Filter { private AsyncApplyTask( RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener, - InteractionHandler handler, ColorResources colorResources, View result) { + InteractionHandler handler, ColorResources colorResources, + View result, boolean topLevel) { mRV = rv; mParent = parent; mContext = context; mListener = listener; mColorResources = colorResources; mHandler = handler; + mTopLevel = topLevel; mResult = result; } @@ -4959,6 +5180,10 @@ public class RemoteViews implements Parcelable, Filter { a.apply(viewTree.mRoot, mParent, handler, mColorResources); } } + // If the parent of the view is has is a root, resolve the recycling. + if (mTopLevel && mResult instanceof ViewGroup) { + finalizeViewRecycling((ViewGroup) mResult); + } } catch (Exception e) { mError = e; } @@ -5011,6 +5236,14 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public void reapply(Context context, View v, InteractionHandler handler, SizeF size, ColorResources colorResources) { + reapply(context, v, handler, size, colorResources, true); + } + + // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls + // should set it to false. + private void reapply(Context context, View v, InteractionHandler handler, SizeF size, + ColorResources colorResources, boolean topLevel) { + RemoteViews rvToApply = getRemoteViewsToApply(context, size); // In the case that a view has this RemoteViews applied in one orientation or size, is @@ -5025,6 +5258,11 @@ public class RemoteViews implements Parcelable, Filter { } rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources); + + // If the parent of the view is has is a root, resolve the recycling. + if (topLevel && v instanceof ViewGroup) { + finalizeViewRecycling((ViewGroup) v); + } } /** @@ -5068,8 +5306,8 @@ public class RemoteViews implements Parcelable, Filter { } return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), - context, listener, handler, colorResources, v).startTaskOnExecutor( - executor); + context, listener, handler, colorResources, v, true /* topLevel */) + .startTaskOnExecutor(executor); } private void performApply(View v, ViewGroup parent, InteractionHandler handler, @@ -5282,6 +5520,7 @@ public class RemoteViews implements Parcelable, Filter { mIdealSize.writeToParcel(dest, flags); } dest.writeInt(mLayoutId); + dest.writeInt(mViewId); dest.writeInt(mLightBackgroundLayoutId); writeActionsToParcel(dest); } else if (hasSizedRemoteViews()) { @@ -5470,6 +5709,14 @@ public class RemoteViews implements Parcelable, Filter { mChildren.add(index, child); } + public void removeChildren(int start, int count) { + if (mChildren != null) { + for (int i = 0; i < count; i++) { + mChildren.remove(start); + } + } + } + private void addViewChild(View v) { // ViewTree only contains Views which can be found using findViewById. // If isRootNamespace is true, this view is skipped. @@ -5500,6 +5747,28 @@ public class RemoteViews implements Parcelable, Filter { } } } + + /** Find the first child for which the condition is true and return its index. */ + public int findChildIndex(Predicate<View> condition) { + return findChildIndex(0, condition); + } + + /** + * Find the first child, starting at {@code startIndex}, for which the condition is true and + * return its index. + */ + public int findChildIndex(int startIndex, Predicate<View> condition) { + if (mChildren == null) { + return -1; + } + + for (int i = startIndex; i < mChildren.size(); i++) { + if (condition.test(mChildren.get(i).mRoot)) { + return i; + } + } + return -1; + } } /** @@ -5758,9 +6027,9 @@ public class RemoteViews implements Parcelable, Filter { } /** - * Set the ID of the top-level view of the XML layout. + * Set the ID of the top-level view of the XML layout. * - * Set to {@link View#NO_ID} to reset and simply keep the id defined in the XML layout. + * Set to {@link View#NO_ID} to reset and simply keep the id defined in the XML layout. * * @throws UnsupportedOperationException if the method is called on a RemoteViews defined in * term of other RemoteViews (e.g. {@link #RemoteViews(RemoteViews, RemoteViews)}). diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fdc66fcb81d8..dba7fa915f35 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -195,6 +195,7 @@ import android.view.textclassifier.TextLinks; import android.view.textservice.SpellCheckerSubtype; import android.view.textservice.TextServicesManager; import android.view.translation.TranslationRequestValue; +import android.view.translation.UiTranslationController; import android.view.translation.ViewTranslationRequest; import android.view.translation.ViewTranslationResponse; import android.widget.RemoteViews.RemoteView; @@ -502,7 +503,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mImeIsConsumingInput; // Whether cursor is visible without regard to {@link mImeConsumesInput}. - // {code true} is the default value. + // {@code true} is the default value. private boolean mCursorVisibleFromAttr = true; static class Drawables { @@ -9316,7 +9317,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } for (int i = 0; i < n; i++) { - max = Math.max(max, layout.getLineWidth(i)); + max = Math.max(max, layout.getLineMax(i)); } return (int) Math.ceil(max); @@ -10570,6 +10571,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mEditor == null ? true : mEditor.mCursorVisible; } + /** + * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}. + * {@code true} is the default value. + * + * @see #setCursorVisible(boolean) + * @hide + */ + public boolean isCursorVisibleFromAttr() { + return mCursorVisibleFromAttr; + } + private boolean canMarquee() { int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); return width > 0 && (mLayout.getLineWidth(0) > width @@ -13835,6 +13847,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public ViewTranslationRequest onCreateTranslationRequest() { 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; } // Not translate password, editable text and not important for translation @@ -13842,6 +13858,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // 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()); + } return null; } // TODO(b/176488462): apply the view's important for translation property @@ -13870,6 +13891,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // 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."); } } @@ -13889,7 +13913,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTranslationTransformation != null) { setTransformationMethod(mTranslationTransformation); } else { - Log.w(LOG_TAG, "onResumeTranslatedText(): no translated text."); + // TODO(b/182433547): remove before S release + Log.w(LOG_TAG, "onRestoreUiTranslation(): no translated text."); } } @@ -13910,6 +13935,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTranslationTransformation != null) { setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod()); mTranslationTransformation = null; + } else { + // TODO(b/182433547): remove before S release + Log.w(LOG_TAG, "onFinishUiTranslation(): no translated text."); } } diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index 8f541d0bd194..3eb35c2c5e8a 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -18,6 +18,7 @@ package android.window; import android.view.SurfaceControl; import android.app.ActivityManager; +import android.graphics.Rect; import android.window.StartingWindowInfo; import android.window.WindowContainerToken; @@ -38,8 +39,12 @@ oneway interface ITaskOrganizer { /** * Called when the Task want to remove the starting window. + * @param leash A persistent leash for the top window in this task. + * @param frame Window frame of the top window. + * @param playRevealAnimation Play vanish animation. */ - void removeStartingWindow(int taskId); + void removeStartingWindow(int taskId, in SurfaceControl leash, in Rect frame, + in boolean playRevealAnimation); /** * Called when the Task want to copy the splash screen. diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 35ccfca101d3..da445b8b9f33 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -46,6 +46,8 @@ import android.widget.FrameLayout; import com.android.internal.R; import com.android.internal.policy.DecorView; +import java.util.function.Consumer; + /** * <p>The view which allows an activity to customize its splash screen exit animation.</p> * @@ -77,7 +79,8 @@ public final class SplashScreenView extends FrameLayout { private Animatable mAnimatableIcon; private ValueAnimator mAnimator; - + private Runnable mAnimationFinishListener; + private Consumer<Canvas> mOnDrawCallback; // cache original window and status private Window mWindow; private boolean mDrawBarBackground; @@ -85,7 +88,7 @@ public final class SplashScreenView extends FrameLayout { private int mNavigationBarColor; /** - * Internal builder to create a SplashScreenWindowView object. + * Internal builder to create a SplashScreenView object. * @hide */ public static class Builder { @@ -391,7 +394,7 @@ public final class SplashScreenView extends FrameLayout { * Get the initial background color of this view. * @hide */ - @ColorInt int getInitBackgroundColor() { + public @ColorInt int getInitBackgroundColor() { return mInitBackgroundColor; } diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java index d1c1e40d1578..c7672dc9fe7d 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -126,6 +126,12 @@ public final class StartingWindowInfo implements Parcelable { */ public int splashScreenThemeResId; + /** + * Is keyguard occluded on default display. + * @hide + */ + public boolean isKeyguardOccluded = false; + public StartingWindowInfo() { } @@ -147,6 +153,7 @@ public final class StartingWindowInfo implements Parcelable { dest.writeTypedObject(topOpaqueWindowLayoutParams, flags); dest.writeTypedObject(mainWindowLayoutParams, flags); dest.writeInt(splashScreenThemeResId); + dest.writeBoolean(isKeyguardOccluded); } void readFromParcel(@NonNull Parcel source) { @@ -157,6 +164,7 @@ public final class StartingWindowInfo implements Parcelable { WindowManager.LayoutParams.CREATOR); mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR); splashScreenThemeResId = source.readInt(); + isKeyguardOccluded = source.readBoolean(); } @Override diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index 04020ec3ee8a..3340cf4fb707 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -24,6 +24,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.ActivityManager; +import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.view.SurfaceControl; @@ -100,9 +101,14 @@ public class TaskOrganizer extends WindowOrganizer { /** * Called when the Task want to remove the starting window. + * @param leash A persistent leash for the top window in this task. Release it once exit + * animation has finished. + * @param frame Window frame of the top window. + * @param playRevealAnimation Play vanish animation. */ @BinderThread - public void removeStartingWindow(int taskId) {} + public void removeStartingWindow(int taskId, @Nullable SurfaceControl leash, + @Nullable Rect frame, boolean playRevealAnimation) {} /** * Called when the Task want to copy the splash screen. @@ -217,15 +223,16 @@ public class TaskOrganizer extends WindowOrganizer { private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() { @Override - public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo, appToken)); } @Override - public void removeStartingWindow(int taskId) { - mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(taskId)); + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { + mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(taskId, leash, frame, + playRevealAnimation)); } @Override diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 6cfd49888fac..d4d853624700 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1119,7 +1119,7 @@ public class ChooserActivity extends ResolverActivity implements ClipboardManager clipboardManager = (ClipboardManager) getSystemService( Context.CLIPBOARD_SERVICE); - clipboardManager.setPrimaryClip(clipData); + clipboardManager.setPrimaryClipAsPackage(clipData, getReferrerPackageName()); Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show(); // Log share completion via copy diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index c1952c7d52cf..957e416986e0 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -166,4 +166,15 @@ interface IBatteryStats { /** {@hide} */ boolean setChargingStateUpdateDelayMillis(int delay); + + /** Exposed as a test API. */ + void setChargerAcOnline(boolean online, boolean forceUpdate); + /** Exposed as a test API. */ + void setBatteryLevel(int level, boolean forceUpdate); + /** Exposed as a test API. */ + void unplugBattery(boolean forceUpdate); + /** Exposed as a test API. */ + void resetBattery(boolean forceUpdate); + /** Exposed as a test API. */ + void suspendBatteryInput(); } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index ccb980eb82b7..592f7c7e1925 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -21,6 +21,7 @@ import android.content.Intent; import android.media.permission.Identity; import android.os.Bundle; import android.os.RemoteCallback; +import android.os.SharedMemory; import com.android.internal.app.IVoiceActionCheckCallback; import com.android.internal.app.IVoiceInteractionSessionShowCallback; @@ -225,14 +226,19 @@ interface IVoiceInteractionManagerService { IBinder client); /** - * Sets hotword detection configuration. + * Set configuration and pass read-only data to hotword detection service. * - * Note: Currently it will trigger hotword detection service after calling this function when - * all conditions meet the requirements. - * - * @param options Config data. - * @return {@link VoiceInteractionService#HOTWORD_CONFIG_SUCCESS} in case of success, - * {@link VoiceInteractionService#HOTWORD_CONFIG_FAILURE} in case of failure. + * @param options Application configuration data provided by the + * {@link VoiceInteractionService}. The system strips out 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 + * such data to the trusted process. + */ + void setHotwordDetectionServiceConfig(in Bundle options, in SharedMemory sharedMemory); + + /** + * Requests to shutdown hotword detection service. */ - int setHotwordDetectionConfig(in Bundle options); + void shutdownHotwordDetectionService(); } diff --git a/core/java/com/android/internal/compat/AndroidBuildClassifier.java b/core/java/com/android/internal/compat/AndroidBuildClassifier.java index 0b937fad7df1..364db06976a0 100644 --- a/core/java/com/android/internal/compat/AndroidBuildClassifier.java +++ b/core/java/com/android/internal/compat/AndroidBuildClassifier.java @@ -31,4 +31,14 @@ public class AndroidBuildClassifier { public boolean isFinalBuild() { return "REL".equals(Build.VERSION.CODENAME); } + + /** + * The current platform SDK version. + */ + public int platformTargetSdk() { + if (isFinalBuild()) { + return Build.VERSION.SDK_INT; + } + return Build.VERSION_CODES.CUR_DEVELOPMENT; + } } diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java index c0bbe5082131..e408be2ab471 100644 --- a/core/java/com/android/internal/compat/OverrideAllowedState.java +++ b/core/java/com/android/internal/compat/OverrideAllowedState.java @@ -34,7 +34,8 @@ public final class OverrideAllowedState implements Parcelable { DISABLED_NON_TARGET_SDK, DISABLED_TARGET_SDK_TOO_HIGH, DEFERRED_VERIFICATION, - LOGGING_ONLY_CHANGE + LOGGING_ONLY_CHANGE, + PLATFORM_TOO_OLD }) @Retention(RetentionPolicy.SOURCE) public @interface State { @@ -65,6 +66,10 @@ public final class OverrideAllowedState implements Parcelable { * Change is marked as logging only, and cannot be toggled. */ public static final int LOGGING_ONLY_CHANGE = 5; + /** + * Change is gated by a target sdk version newer than the current platform sdk version. + */ + public static final int PLATFORM_TOO_OLD = 6; @State public final int state; @@ -123,6 +128,11 @@ public final class OverrideAllowedState implements Parcelable { throw new SecurityException(String.format( "Cannot override %1$d because it is marked as a logging-only change.", changeId)); + case PLATFORM_TOO_OLD: + throw new SecurityException(String.format( + "Cannot override %1$d for %2$s because the change's targetSdk threshold " + + "(%3$d) is above the platform sdk.", + changeId, packageName, changeIdTargetSdk)); } } @@ -170,6 +180,8 @@ public final class OverrideAllowedState implements Parcelable { return "DEFERRED_VERIFICATION"; case LOGGING_ONLY_CHANGE: return "LOGGING_ONLY_CHANGE"; + case PLATFORM_TOO_OLD: + return "PLATFORM_TOO_OLD"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/graphics/palette/Mean.java b/core/java/com/android/internal/graphics/palette/Mean.java index 894f91b6261c..bde036349d3b 100644 --- a/core/java/com/android/internal/graphics/palette/Mean.java +++ b/core/java/com/android/internal/graphics/palette/Mean.java @@ -22,20 +22,19 @@ import java.util.Random; * Represents a centroid in Kmeans algorithms. */ public class Mean { - private static final Random RANDOM = new Random(0); - public float[] center; /** * Constructor. * * @param upperBound maximum value of a dimension in the space Kmeans is optimizing in + * @param random used to generate a random center */ - Mean(int upperBound) { + Mean(int upperBound, Random random) { center = new float[]{ - RANDOM.nextInt(upperBound + 1), RANDOM.nextInt(upperBound + 1), - RANDOM.nextInt(upperBound + 1) + random.nextInt(upperBound + 1), random.nextInt(upperBound + 1), + random.nextInt(upperBound + 1) }; } diff --git a/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java b/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java index a87a34f4ae11..1d865c2513cf 100644 --- a/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java +++ b/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.Set; @@ -57,13 +58,27 @@ public class WSMeansQuantizer implements Quantizer { } if (maxColors > means.length) { + // Always initialize Random with the same seed. Ensures the results of quantization + // are consistent, even when random centroids are required. + Random random = new Random(0x42688); int randomMeansToCreate = maxColors - means.length; for (int i = 0; i < randomMeansToCreate; i++) { - mMeans[means.length + i] = new Mean(100); + mMeans[means.length + i] = new Mean(100, random); } } for (int pixel : pixels) { + // These are pixels from the bitmap that is being quantized. + // Depending on the bitmap & downscaling, it may have pixels that are less than opaque + // Ignore those pixels. + /// + // Note: they don't _have_ to be ignored, for example, we could instead turn them + // opaque. Traditionally, including outside Android, quantizers ignore transparent + // pixels, so that strategy was chosen. + int alpha = (pixel >> 24) & 0xff; + if (alpha < 255) { + continue; + } Integer currentCount = mCountByColor.get(pixel); if (currentCount == null) { currentCount = 0; @@ -105,8 +120,12 @@ public class WSMeansQuantizer implements Quantizer { /** Create random starting centroids for K-means. */ public static float[][] randomMeans(int maxColors, int upperBound) { float[][] means = new float[maxColors][]; + + // Always initialize Random with the same seed. Ensures the results of quantization + // are consistent, even when random centroids are required. + Random random = new Random(0x42688); for (int i = 0; i < maxColors; i++) { - means[i] = new Mean(upperBound).center; + means[i] = new Mean(upperBound, random).center; } return means; } diff --git a/core/java/com/android/internal/graphics/palette/WuQuantizer.java b/core/java/com/android/internal/graphics/palette/WuQuantizer.java index 66206bf8297a..a2652ea6d5e1 100644 --- a/core/java/com/android/internal/graphics/palette/WuQuantizer.java +++ b/core/java/com/android/internal/graphics/palette/WuQuantizer.java @@ -78,7 +78,12 @@ public class WuQuantizer implements Quantizer { // All of the sample Wu implementations are reimplementations of a snippet of C code from // the early 90s. They all cap the maximum # of colors at 256, and it is impossible to tell // if this is a requirement, a consequence of QUANT_SIZE, or arbitrary. - this.mMaxColors = Math.min(MAX_COLORS, maxColorCount); + // + // Also, the number of maximum colors should be capped at the number of pixels - otherwise, + // If extraction is run on a set of pixels whose count is less than max colors, + // then colors.length < max colors, and accesses to colors[index] throw an + // ArrayOutOfBoundsException. + this.mMaxColors = Math.min(Math.min(MAX_COLORS, maxColorCount), colors.length); Box[] cube = new Box[mMaxColors]; int red, green, blue; @@ -119,17 +124,13 @@ public class WuQuantizer implements Quantizer { } } - // If extraction is run on a set of pixels whose count is less than the - // number of max colors, then colors.length < max colors, and accesses - // to colors[index] inside the for loop throw an ArrayOutOfBoundsException. - int numColorsToCreate = (int) Math.min(mMaxColors, colors.length); - for (k = 0; k < numColorsToCreate; ++k) { + for (k = 0; k < mMaxColors; ++k) { weight = getVolume(cube[k], mWt); if (weight > 0) { red = (int) (getVolume(cube[k], mMr) / weight); green = (int) (getVolume(cube[k], mMg) / weight); blue = (int) (getVolume(cube[k], mMb) / weight); - colors[k] = ((red & 0x0ff) << 16) | ((green & 0x0ff) << 8) | (blue & 0x0ff); + colors[k] = (255 << 24) | (red << 16) | (green << 8) | blue; } else { colors[k] = 0; } diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 5829047ad642..7f0178e29d85 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -206,9 +206,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener * Cancel the trace session of the CUJ. */ public synchronized void cancel() { - // The session is ongoing, end the trace session. - // That means the cancel call is from external invocation, not from end(). - if (mBeginVsyncId != INVALID_ID && mEndVsyncId == INVALID_ID) { + // We don't need to end the trace section if it never begun. + if (mBeginVsyncId != INVALID_ID) { Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); } mCancelled = true; diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 9ecb0ad09bc4..11466f4bc042 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -6976,6 +6976,11 @@ public class BatteryStatsImpl extends BatteryStats { return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE); } + @Override + public long getCpuMeasuredBatteryConsumptionUC() { + return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); + } + /** * Returns the consumption (in microcoulombs) that the given standard power bucket consumed. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable @@ -8482,6 +8487,11 @@ public class BatteryStatsImpl extends BatteryStats { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON); } + @Override + public long getCpuMeasuredBatteryConsumptionUC() { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); + } + void initNetworkActivityLocked() { detachIfNotNull(mNetworkByteActivityCounters); mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java index 97f727ba72c5..b15543a26b4c 100644 --- a/core/java/com/android/internal/os/CpuPowerCalculator.java +++ b/core/java/com/android/internal/os/CpuPowerCalculator.java @@ -85,12 +85,14 @@ public class CpuPowerCalculator extends PowerCalculator { builder.getUidBatteryConsumerBuilders(); for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); - calculateApp(app, app.getBatteryStatsUid(), result); + calculateApp(app, app.getBatteryStatsUid(), query, result); } } - private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, Result result) { - calculatePowerAndDuration(u, BatteryStats.STATS_SINCE_CHARGED, result); + private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, + BatteryUsageStatsQuery query, Result result) { + calculatePowerAndDuration(u, BatteryStats.STATS_SINCE_CHARGED, + query.shouldForceUsePowerProfileModel(), result); app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, result.powerMah) .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, result.durationMs) @@ -112,7 +114,7 @@ public class CpuPowerCalculator extends PowerCalculator { } private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, Result result) { - calculatePowerAndDuration(u, statsType, result); + calculatePowerAndDuration(u, statsType, false, result); app.cpuPowerMah = result.powerMah; app.cpuTimeMs = result.durationMs; @@ -120,46 +122,16 @@ public class CpuPowerCalculator extends PowerCalculator { app.packageWithHighestDrain = result.packageWithHighestDrain; } - private void calculatePowerAndDuration(BatteryStats.Uid u, int statsType, Result result) { + private void calculatePowerAndDuration(BatteryStats.Uid u, int statsType, + boolean forceUsePowerProfileModel, Result result) { long durationMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000; - // Constant battery drain when CPU is active - double powerMah = calculateActiveCpuPowerMah(u.getCpuActiveTime()); - - // Additional per-cluster battery drain - long[] cpuClusterTimes = u.getCpuClusterTimes(); - if (cpuClusterTimes != null) { - if (cpuClusterTimes.length == mNumCpuClusters) { - for (int cluster = 0; cluster < mNumCpuClusters; cluster++) { - double power = calculatePerCpuClusterPowerMah(cluster, - cpuClusterTimes[cluster]); - powerMah += power; - if (DEBUG) { - Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster - + " clusterTimeMs=" + cpuClusterTimes[cluster] - + " power=" + formatCharge(power)); - } - } - } else { - Log.w(TAG, "UID " + u.getUid() + " CPU cluster # mismatch: Power Profile # " - + mNumCpuClusters + " actual # " + cpuClusterTimes.length); - } - } - - // Additional per-frequency battery drain - for (int cluster = 0; cluster < mNumCpuClusters; cluster++) { - final int speedsForCluster = mPerCpuFreqPowerEstimators[cluster].length; - for (int speed = 0; speed < speedsForCluster; speed++) { - final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType); - final double power = calculatePerCpuFreqPowerMah(cluster, speed, - timeUs / 1000); - if (DEBUG) { - Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #" - + speed + " timeUs=" + timeUs + " power=" - + formatCharge(power)); - } - powerMah += power; - } + final double powerMah; + final long consumptionUC = u.getCpuMeasuredBatteryConsumptionUC(); + if (forceUsePowerProfileModel || consumptionUC == BatteryStats.POWER_DATA_UNAVAILABLE) { + powerMah = calculateUidModeledPowerMah(u, statsType); + } else { + powerMah = uCtoMah(consumptionUC); } if (DEBUG && (durationMs != 0 || powerMah != 0)) { @@ -208,6 +180,48 @@ public class CpuPowerCalculator extends PowerCalculator { result.packageWithHighestDrain = packageWithHighestDrain; } + private double calculateUidModeledPowerMah(BatteryStats.Uid u, int statsType) { + // Constant battery drain when CPU is active + double powerMah = calculateActiveCpuPowerMah(u.getCpuActiveTime()); + + // Additional per-cluster battery drain + long[] cpuClusterTimes = u.getCpuClusterTimes(); + if (cpuClusterTimes != null) { + if (cpuClusterTimes.length == mNumCpuClusters) { + for (int cluster = 0; cluster < mNumCpuClusters; cluster++) { + double power = calculatePerCpuClusterPowerMah(cluster, + cpuClusterTimes[cluster]); + powerMah += power; + if (DEBUG) { + Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + + " clusterTimeMs=" + cpuClusterTimes[cluster] + + " power=" + formatCharge(power)); + } + } + } else { + Log.w(TAG, "UID " + u.getUid() + " CPU cluster # mismatch: Power Profile # " + + mNumCpuClusters + " actual # " + cpuClusterTimes.length); + } + } + + // Additional per-frequency battery drain + for (int cluster = 0; cluster < mNumCpuClusters; cluster++) { + final int speedsForCluster = mPerCpuFreqPowerEstimators[cluster].length; + for (int speed = 0; speed < speedsForCluster; speed++) { + final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType); + final double power = calculatePerCpuFreqPowerMah(cluster, speed, + timeUs / 1000); + if (DEBUG) { + Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #" + + speed + " timeUs=" + timeUs + " power=" + + formatCharge(power)); + } + powerMah += power; + } + } + return powerMah; + } + /** * Calculates active CPU power consumption. * diff --git a/core/java/com/android/internal/os/SelectedProcessCpuThreadReader.java b/core/java/com/android/internal/os/SelectedProcessCpuThreadReader.java new file mode 100644 index 000000000000..2ffff73f18cf --- /dev/null +++ b/core/java/com/android/internal/os/SelectedProcessCpuThreadReader.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import android.annotation.Nullable; +import android.os.Process; + +/** + * Reads CPU usage statistics about a selected process identified by its cmdline. + * + * Handles finding the pid for the process and delegates CPU usage reading from the eBPF map to + * KernelSingleProcessCpuThreadReader. Exactly one long-lived instance of the process is expected. + * Otherwise, no statistics are returned. + * + * See also SystemServerCpuThreadReader. + */ +public final class SelectedProcessCpuThreadReader { + private final String[] mCmdline; + + private int mPid; + private KernelSingleProcessCpuThreadReader mKernelCpuThreadReader; + + public SelectedProcessCpuThreadReader(String cmdline) { + mCmdline = new String[] { cmdline }; + } + + /** Returns CPU times, per thread group, since tracking started. */ + @Nullable + public KernelSingleProcessCpuThreadReader.ProcessCpuUsage readAbsolute() { + int[] pids = Process.getPidsForCommands(mCmdline); + if (pids == null || pids.length != 1) { + return null; + } + int pid = pids[0]; + if (mPid == pid) { + return mKernelCpuThreadReader.getProcessCpuUsage(); + } + mPid = pid; + mKernelCpuThreadReader = KernelSingleProcessCpuThreadReader.create(mPid); + mKernelCpuThreadReader.startTrackingThreadCpuTimes(); + return null; + } +} diff --git a/core/java/com/android/internal/protolog/BaseProtoLogImpl.java b/core/java/com/android/internal/protolog/BaseProtoLogImpl.java index bed85aed3625..83309fc61009 100644 --- a/core/java/com/android/internal/protolog/BaseProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/BaseProtoLogImpl.java @@ -359,18 +359,6 @@ public class BaseProtoLogImpl { + "\nLogging definitions loaded: " + mViewerConfig.knownViewerStringsNumber(); } - /** - * Writes the log buffer to a new file for the bugreport. - * - * This method is synchronized with {@code #startProtoLog(PrintWriter)} and - * {@link #stopProtoLog(PrintWriter, boolean)}. - */ - public void writeProtoLogToFile() { - synchronized (mProtoLogEnabledLock) { - writeProtoLogToFileLocked(); - } - } - private void writeProtoLogToFileLocked() { try { long offset = diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 200e0dd6e65b..fea07519fb4d 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -257,4 +257,12 @@ oneway interface IStatusBar * file descriptor passed in. */ void passThroughShellCommand(in String[] args, in ParcelFileDescriptor pfd); + + /** + * Enables/disables the navigation bar luma sampling. + * + * @param displayId the id of the display to notify. + * @param enable {@code true} if enable, otherwise set to {@code false}. + */ + void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable); } diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl index 85114e59cc2d..b57b4b959334 100644 --- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -72,5 +72,5 @@ oneway interface IPhoneStateListener { void onBarringInfoChanged(in BarringInfo barringInfo); void onPhysicalChannelConfigChanged(in List<PhysicalChannelConfig> configs); void onDataEnabledChanged(boolean enabled, int reason); - void onAllowedNetworkTypesChanged(in Map allowedNetworkTypeList); + void onAllowedNetworkTypesChanged(in int reason, in long allowedNetworkType); } diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 95e0a3b524c5..83691ee64103 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -95,5 +95,5 @@ interface ITelephonyRegistry { void notifyPhysicalChannelConfigForSubscriber(in int subId, in List<PhysicalChannelConfig> configs); void notifyDataEnabled(in int phoneId, int subId, boolean enabled, int reason); - void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in Map allowedNetworkTypeList); + void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType); } diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index dc6880e4f997..90c728293eb0 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -175,7 +175,7 @@ public class LatencyTracker { mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED); for (int action : ACTIONS_ALL) { mTraceThresholdPerAction[action] = - properties.getInt(getTraceTriggerNameForAction(action), -1); + properties.getInt(getNameOfAction(STATSD_ACTION[action]), -1); } } } diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index 19506a325d9a..d0c807d24b14 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -31,8 +31,10 @@ import android.util.imetracing.ImeTracing; import android.util.imetracing.InputConnectionHelper; import android.util.proto.ProtoOutputStream; import android.view.KeyEvent; +import android.view.View; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.DumpableInputConnection; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; @@ -49,7 +51,9 @@ import com.android.internal.inputmethod.IIntResultCallback; import com.android.internal.inputmethod.ISurroundingTextResultCallback; import com.android.internal.os.SomeArgs; -public abstract class IInputConnectionWrapper extends IInputContext.Stub { +import java.lang.ref.WeakReference; + +public final class IInputConnectionWrapper extends IInputContext.Stub { private static final String TAG = "IInputConnectionWrapper"; private static final boolean DEBUG = false; @@ -90,10 +94,13 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { private Looper mMainLooper; private Handler mH; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private Object mLock = new Object(); + private final Object mLock = new Object(); @GuardedBy("mLock") private boolean mFinished = false; + private final InputMethodManager mParentInputMethodManager; + private final WeakReference<View> mServedView; + class MyHandler extends Handler { MyHandler(Looper looper) { super(looper); @@ -104,11 +111,15 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { executeMessage(msg); } } - - public IInputConnectionWrapper(Looper mainLooper, @NonNull InputConnection inputConnection) { + + public IInputConnectionWrapper(@NonNull Looper mainLooper, + @NonNull InputConnection inputConnection, + @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) { mInputConnection = inputConnection; mMainLooper = mainLooper; mH = new MyHandler(mMainLooper); + mParentInputMethodManager = inputMethodManager; + mServedView = new WeakReference<>(servedView); } @Nullable @@ -118,21 +129,70 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } } - protected Looper getLooper() { - synchronized (mMainLooper) { - return mMainLooper; + private boolean isFinished() { + synchronized (mLock) { + return mFinished; } } - protected boolean isFinished() { - synchronized (mLock) { - return mFinished; + public boolean isActive() { + return mParentInputMethodManager.isActive() && !isFinished(); + } + + public View getServedView() { + return mServedView.get(); + } + + public void deactivate() { + if (isFinished()) { + // This is a small performance optimization. Still only the 1st call of + // reportFinish() will take effect. + return; + } + closeConnection(); + + // Notify the app that the InputConnection was closed. + final View servedView = mServedView.get(); + if (servedView != null) { + final Handler handler = servedView.getHandler(); + // The handler is null if the view is already detached. When that's the case, for + // now, we simply don't dispatch this callback. + if (handler != null) { + if (DEBUG) { + Log.v(TAG, "Calling View.onInputConnectionClosed: view=" + servedView); + } + if (handler.getLooper().isCurrentThread()) { + servedView.onInputConnectionClosedInternal(); + } else { + handler.post(servedView::onInputConnectionClosedInternal); + } + } } } - protected abstract boolean isActive(); + @Override + public String toString() { + return "IInputConnectionWrapper{" + + "connection=" + getInputConnection() + + " finished=" + isFinished() + + " mParentInputMethodManager.isActive()=" + mParentInputMethodManager.isActive() + + " mServedView=" + mServedView.get() + + "}"; + } - protected abstract InputMethodManager getIMM(); + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + synchronized (mLock) { + // Check that the call is initiated in the main thread of the current InputConnection + // {@link InputConnection#getHandler} since the messages to IInputConnectionWrapper are + // executed on this thread. Otherwise the messages are dispatched to the correct thread + // in IInputConnectionWrapper, but this is not wanted while dumpng, for performance + // reasons. + if ((mInputConnection instanceof DumpableInputConnection) + && Looper.myLooper() == mMainLooper) { + ((DumpableInputConnection) mInputConnection).dumpDebug(proto, fieldId); + } + } + } public void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback) { dispatchMessage(mH.obtainMessage(DO_GET_TEXT_AFTER_CURSOR, length, flags, callback)); @@ -161,6 +221,10 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(mH.obtainMessage(DO_GET_SURROUNDING_TEXT, flags, 0 /* unused */, args)); } + public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) { + // no-op + } + public void getCursorCapsMode(int reqModes, IIntResultCallback callback) { dispatchMessage( mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback)); @@ -309,7 +373,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { icProto = InputConnectionHelper.buildGetTextAfterCursorProto(msg.arg1, msg.arg2, result); ImeTracing.getInstance().triggerClientDump( - TAG + "#getTextAfterCursor", getIMM(), icProto); + TAG + "#getTextAfterCursor", mParentInputMethodManager, icProto); } try { callback.onResult(result); @@ -339,7 +403,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { icProto = InputConnectionHelper.buildGetTextBeforeCursorProto(msg.arg1, msg.arg2, result); ImeTracing.getInstance().triggerClientDump( - TAG + "#getTextBeforeCursor", getIMM(), icProto); + TAG + "#getTextBeforeCursor", mParentInputMethodManager, icProto); } try { callback.onResult(result); @@ -368,7 +432,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { if (ImeTracing.getInstance().isEnabled()) { icProto = InputConnectionHelper.buildGetSelectedTextProto(msg.arg1, result); ImeTracing.getInstance().triggerClientDump( - TAG + "#getSelectedText", getIMM(), icProto); + TAG + "#getSelectedText", mParentInputMethodManager, icProto); } try { callback.onResult(result); @@ -402,7 +466,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { icProto = InputConnectionHelper.buildGetSurroundingTextProto(beforeLength, afterLength, flags, result); ImeTracing.getInstance().triggerClientDump( - TAG + "#getSurroundingText", getIMM(), icProto); + TAG + "#getSurroundingText", mParentInputMethodManager, icProto); } try { callback.onResult(result); @@ -432,7 +496,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { icProto = InputConnectionHelper.buildGetCursorCapsModeProto(msg.arg1, result); ImeTracing.getInstance().triggerClientDump( - TAG + "#getCursorCapsMode", getIMM(), icProto); + TAG + "#getCursorCapsMode", mParentInputMethodManager, icProto); } try { callback.onResult(result); @@ -464,7 +528,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { icProto = InputConnectionHelper.buildGetExtractedTextProto(request, msg.arg1, result); ImeTracing.getInstance().triggerClientDump( - TAG + "#getExtractedText", getIMM(), icProto); + TAG + "#getExtractedText", mParentInputMethodManager, icProto); } try { callback.onResult(result); diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 8d82e33dc29f..c33637353984 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -35,8 +35,7 @@ import com.android.internal.view.InlineSuggestionsRequestInfo; * {@hide} */ oneway interface IInputMethod { - void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps, - int configChanges); + void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps); void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo, in IInlineSuggestionsRequestCallback cb); diff --git a/core/java/com/android/internal/view/OWNERS b/core/java/com/android/internal/view/OWNERS index 851d1f37522a..eb2478f6550a 100644 --- a/core/java/com/android/internal/view/OWNERS +++ b/core/java/com/android/internal/view/OWNERS @@ -18,3 +18,7 @@ per-file AppearanceRegion = file:/services/core/java/com/android/server/wm/OWNER per-file BaseIWIndow.java = file:/services/core/java/com/android/server/wm/OWNERS per-file RotationPolicy.java = file:/services/core/java/com/android/server/wm/OWNERS per-file WindowManagerPolicyThread.java = file:/services/core/java/com/android/server/wm/OWNERS + +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS index d284d5167843..8e68be0f742a 100644 --- a/core/java/com/android/internal/widget/OWNERS +++ b/core/java/com/android/internal/widget/OWNERS @@ -1,4 +1,6 @@ per-file PointerLocationView.java = michaelwr@google.com, svv@google.com +per-file RecyclerView.java = mount@google.com +per-file ViewPager.java = mount@google.com # LockSettings related per-file *LockPattern* = file:/services/core/java/com/android/server/locksettings/OWNERS diff --git a/core/jni/Android.bp b/core/jni/Android.bp index cea8b44a40ec..a153fab10214 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -1,4 +1,3 @@ - package { default_applicable_licenses: ["frameworks_base_core_jni_license"], } @@ -69,10 +68,9 @@ cc_library_shared { "liblog", "libminikin", "libz", - "libziparchive", ], - static_libs: ["libnativehelper_lazy"], + static_libs: ["libnativehelper_lazy", "libziparchive_for_incfs", ], export_include_dirs: [ ".", @@ -219,6 +217,7 @@ cc_library_shared { "fd_utils.cpp", "android_hardware_input_InputWindowHandle.cpp", "android_hardware_input_InputApplicationHandle.cpp", + "permission_utils.cpp", ], static_libs: [ @@ -237,6 +236,7 @@ cc_library_shared { "audioflinger-aidl-cpp", "av-types-aidl-cpp", "android.hardware.camera.device@3.2", + "media_permission-aidl-cpp", "libandroidicu", "libbpf_android", "libnetdbpf", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 7ad1b49d3469..94ac183517a2 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -97,6 +97,7 @@ extern int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env); extern int register_android_media_MicrophoneInfo(JNIEnv *env); extern int register_android_media_ToneGenerator(JNIEnv *env); extern int register_android_media_midi(JNIEnv *env); +extern int register_android_media_permission_Identity(JNIEnv* env); namespace android { @@ -636,6 +637,12 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p char saveResolvedClassesDelayMsOptsBuf[ sizeof("-Xps-save-resolved-classes-delay-ms:")-1 + PROPERTY_VALUE_MAX]; char madviseRandomOptsBuf[sizeof("-XX:MadviseRandomAccess:")-1 + PROPERTY_VALUE_MAX]; + char madviseWillNeedFileSizeVdex[ + sizeof("-XMadviseWillNeedVdexFileSize:")-1 + PROPERTY_VALUE_MAX]; + char madviseWillNeedFileSizeOdex[ + sizeof("-XMadviseWillNeedOdexFileSize:")-1 + PROPERTY_VALUE_MAX]; + char madviseWillNeedFileSizeArt[ + sizeof("-XMadviseWillNeedArtFileSize:")-1 + PROPERTY_VALUE_MAX]; char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX]; char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX]; char heaptargetutilizationOptsBuf[sizeof("-XX:HeapTargetUtilization=")-1 + PROPERTY_VALUE_MAX]; @@ -844,6 +851,22 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p parseRuntimeOption("dalvik.vm.madvise-random", madviseRandomOptsBuf, "-XX:MadviseRandomAccess:"); /* + * Use default platform configuration as limits for madvising, + * when no properties are specified. + */ + parseRuntimeOption("dalvik.vm.madvise.vdexfile.size", + madviseWillNeedFileSizeVdex, + "-XMadviseWillNeedVdexFileSize:"); + + parseRuntimeOption("dalvik.vm.madvise.odexfile.size", + madviseWillNeedFileSizeOdex, + "-XMadviseWillNeedOdexFileSize:"); + + parseRuntimeOption("dalvik.vm.madvise.artfile.size", + madviseWillNeedFileSizeArt, + "-XMadviseWillNeedArtFileSize:"); + + /* * Profile related options. */ parseRuntimeOption("dalvik.vm.hot-startup-method-samples", hotstartupsamplesOptsBuf, @@ -1564,6 +1587,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_media_RemoteDisplay), REG_JNI(register_android_media_ToneGenerator), REG_JNI(register_android_media_midi), + REG_JNI(register_android_media_permission_Identity), REG_JNI(register_android_opengl_classes), REG_JNI(register_android_server_NetworkManagementSocketTagger), diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index 0e0f98ec1fc4..7d3febbd5d14 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -22,6 +22,7 @@ #include <jni.h> #include <nativehelper/JNIHelp.h> #include "core_jni_helpers.h" +#include "permission_utils.h" #include <utils/Log.h> #include <media/AudioRecord.h> @@ -39,6 +40,9 @@ // ---------------------------------------------------------------------------- +using android::media::permission::convertIdentity; +using android::media::permission::Identity; + using namespace android; // ---------------------------------------------------------------------------- @@ -181,12 +185,11 @@ static sp<AudioRecord> setAudioRecord(JNIEnv* env, jobject thiz, const sp<AudioR } // ---------------------------------------------------------------------------- -static jint -android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jobject jaa, jintArray jSampleRate, jint channelMask, jint channelIndexMask, - jint audioFormat, jint buffSizeInBytes, jintArray jSession, jstring opPackageName, - jlong nativeRecordInJavaObj) -{ +static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this, + jobject jaa, jintArray jSampleRate, jint channelMask, + jint channelIndexMask, jint audioFormat, + jint buffSizeInBytes, jintArray jSession, + jobject jIdentity, jlong nativeRecordInJavaObj) { //ALOGV(">> Entering android_media_AudioRecord_setup"); //ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d " // "nativeRecordInJavaObj=0x%llX", @@ -262,10 +265,8 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this, size_t frameSize = channelCount * bytesPerSample; size_t frameCount = buffSizeInBytes / frameSize; - ScopedUtfChars opPackageNameStr(env, opPackageName); - // create an uninitialized AudioRecord object - lpRecorder = new AudioRecord(String16(opPackageNameStr.c_str())); + lpRecorder = new AudioRecord(convertIdentity(env, jIdentity)); // read the AudioAttributes values auto paa = JNIAudioAttributeHelper::makeUnique(); @@ -373,8 +374,6 @@ native_init_failure: return (jint) AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED; } - - // ---------------------------------------------------------------------------- static jint android_media_AudioRecord_start(JNIEnv *env, jobject thiz, jint event, jint triggerSession) @@ -893,9 +892,11 @@ static jint android_media_AudioRecord_get_port_id(JNIEnv *env, jobject thiz) { // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { + // name, signature, funcPtr {"native_start", "(II)I", (void *)android_media_AudioRecord_start}, {"native_stop", "()V", (void *)android_media_AudioRecord_stop}, - {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILjava/lang/String;J)I", + {"native_setup", + "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/media/permission/Identity;J)I", (void *)android_media_AudioRecord_setup}, {"native_finalize", "()V", (void *)android_media_AudioRecord_finalize}, {"native_release", "()V", (void *)android_media_AudioRecord_release}, diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 94bd28a59e7c..f102edc79e7f 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2204,13 +2204,11 @@ android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP( return jStatus; } -static jint -android_media_AudioSystem_getSurroundFormats(JNIEnv *env, jobject thiz, - jobject jSurroundFormats, jboolean reported) -{ +static jint android_media_AudioSystem_getSurroundFormats(JNIEnv *env, jobject thiz, + jobject jSurroundFormats) { ALOGV("getSurroundFormats"); - if (jSurroundFormats == NULL) { + if (jSurroundFormats == nullptr) { ALOGE("jSurroundFormats is NULL"); return (jint)AUDIO_JAVA_BAD_VALUE; } @@ -2221,10 +2219,10 @@ android_media_AudioSystem_getSurroundFormats(JNIEnv *env, jobject thiz, jint jStatus; unsigned int numSurroundFormats = 0; - audio_format_t *surroundFormats = NULL; - bool *surroundFormatsEnabled = NULL; - status_t status = AudioSystem::getSurroundFormats( - &numSurroundFormats, surroundFormats, surroundFormatsEnabled, reported); + audio_format_t *surroundFormats = nullptr; + bool *surroundFormatsEnabled = nullptr; + status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats, + surroundFormatsEnabled); if (status != NO_ERROR) { ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status); jStatus = nativeToJavaStatus(status); @@ -2236,8 +2234,8 @@ android_media_AudioSystem_getSurroundFormats(JNIEnv *env, jobject thiz, } surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t)); surroundFormatsEnabled = (bool *)calloc(numSurroundFormats, sizeof(bool)); - status = AudioSystem::getSurroundFormats( - &numSurroundFormats, surroundFormats, surroundFormatsEnabled, reported); + status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats, + surroundFormatsEnabled); jStatus = nativeToJavaStatus(status); if (status != NO_ERROR) { ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status); @@ -2258,6 +2256,50 @@ exit: return jStatus; } +static jint android_media_AudioSystem_getReportedSurroundFormats(JNIEnv *env, jobject thiz, + jobject jSurroundFormats) { + ALOGV("getReportedSurroundFormats"); + + if (jSurroundFormats == nullptr) { + ALOGE("jSurroundFormats is NULL"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jSurroundFormats, gArrayListClass)) { + ALOGE("jSurroundFormats not an arraylist"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + jint jStatus; + unsigned int numSurroundFormats = 0; + audio_format_t *surroundFormats = nullptr; + status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats); + if (status != NO_ERROR) { + ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status); + jStatus = nativeToJavaStatus(status); + goto exit; + } + if (numSurroundFormats == 0) { + jStatus = (jint)AUDIO_JAVA_SUCCESS; + goto exit; + } + surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t)); + status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats); + jStatus = nativeToJavaStatus(status); + if (status != NO_ERROR) { + ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status); + goto exit; + } + for (size_t i = 0; i < numSurroundFormats; i++) { + jobject surroundFormat = env->NewObject(gIntegerClass, gIntegerCstor, + audioFormatFromNative(surroundFormats[i])); + env->CallObjectMethod(jSurroundFormats, gArrayListMethods.add, surroundFormat); + env->DeleteLocalRef(surroundFormat); + } + +exit: + free(surroundFormats); + return jStatus; +} + static jint android_media_AudioSystem_setSurroundFormatEnabled(JNIEnv *env, jobject thiz, jint audioFormat, jboolean enabled) @@ -2619,8 +2661,10 @@ static const JNINativeMethod gMethods[] = (void *)android_media_AudioSystem_getOffloadSupport}, {"getMicrophones", "(Ljava/util/ArrayList;)I", (void *)android_media_AudioSystem_getMicrophones}, - {"getSurroundFormats", "(Ljava/util/Map;Z)I", + {"getSurroundFormats", "(Ljava/util/Map;)I", (void *)android_media_AudioSystem_getSurroundFormats}, + {"getReportedSurroundFormats", "(Ljava/util/ArrayList;)I", + (void *)android_media_AudioSystem_getReportedSurroundFormats}, {"setSurroundFormatEnabled", "(IZ)I", (void *)android_media_AudioSystem_setSurroundFormatEnabled}, {"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid}, diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index cae6db57e99c..62767a676e8b 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -48,6 +48,7 @@ using namespace android; using ::android::media::VolumeShaper; +using ::android::media::permission::Identity; // ---------------------------------------------------------------------------- static const char* const kClassPathName = "android/media/AudioTrack"; @@ -328,7 +329,10 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we // create the native AudioTrack object ScopedUtfChars opPackageNameStr(env, opPackageName); - lpTrack = new AudioTrack(opPackageNameStr.c_str()); + // TODO b/182469354: make consistent with AudioRecord + Identity identity = Identity(); + identity.packageName = std::string(opPackageNameStr.c_str()); + lpTrack = new AudioTrack(identity); // read the AudioAttributes values auto paa = JNIAudioAttributeHelper::makeUnique(); @@ -390,8 +394,8 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we sessionId, // audio session ID offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK : AudioTrack::TRANSFER_SYNC, - (offload || encapsulationMode) ? &offloadInfo : NULL, -1, - -1, // default uid, pid values + (offload || encapsulationMode) ? &offloadInfo : NULL, + Identity(), // default uid, pid values paa.get()); break; @@ -416,8 +420,8 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we true, // thread can call Java sessionId, // audio session ID AudioTrack::TRANSFER_SHARED, - NULL, // default offloadInfo - -1, -1, // default uid, pid values + NULL, // default offloadInfo + Identity(), // default uid, pid values paa.get()); break; diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp index 2384efaf1a54..413bcef57a64 100644 --- a/core/jni/android_os_incremental_IncrementalManager.cpp +++ b/core/jni/android_os_incremental_IncrementalManager.cpp @@ -41,6 +41,10 @@ static jboolean nativeIsIncrementalPath(JNIEnv* env, return (jboolean)IncFs_IsIncFsPath(path.c_str()); } +static jboolean nativeIsIncrementalFd(JNIEnv* env, jobject clazz, jint fd) { + return (jboolean)IncFs_IsIncFsFd(fd); +} + static jbyteArray nativeUnsafeGetFileSignature(JNIEnv* env, jobject clazz, jstring javaPath) { ScopedUtfChars path(env, javaPath); @@ -61,6 +65,7 @@ static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled}, {"nativeIsV2Available", "()Z", (void*)nativeIsV2Available}, {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath}, + {"nativeIsIncrementalFd", "(I)Z", (void*)nativeIsIncrementalFd}, {"nativeUnsafeGetFileSignature", "(Ljava/lang/String;)[B", (void*)nativeUnsafeGetFileSignature}}; diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index f7b3f309ed12..2e4be145d54d 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -1509,7 +1509,9 @@ static jboolean android_os_BinderProxy_unlinkToDeath(JNIEnv* env, jobject obj, res = JNI_TRUE; } else { jniThrowException(env, "java/util/NoSuchElementException", - "Death link does not exist"); + base::StringPrintf("Death link does not exist (%s)", + statusToString(err).c_str()) + .c_str()); } } diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp index 9746a07a1b77..97fdb43bb4f6 100644 --- a/core/jni/android_view_InputEventSender.cpp +++ b/core/jni/android_view_InputEventSender.cpp @@ -34,6 +34,8 @@ #include "core_jni_helpers.h" +using android::base::Result; + namespace android { // Log debug messages about the dispatch cycle. @@ -197,16 +199,9 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { ScopedLocalRef<jobject> senderObj(env, NULL); bool skipCallbacks = false; for (;;) { - uint32_t publishedSeq; - bool handled; - std::function<void(uint32_t seq, bool handled, nsecs_t consumeTime)> callback = - [&publishedSeq, &handled](uint32_t inSeq, bool inHandled, - nsecs_t inConsumeTime) -> void { - publishedSeq = inSeq; - handled = inHandled; - }; - status_t status = mInputPublisher.receiveFinishedSignal(callback); - if (status) { + Result<InputPublisher::Finished> result = mInputPublisher.receiveFinishedSignal(); + if (!result.ok()) { + const status_t status = result.error().code(); if (status == WOULD_BLOCK) { return OK; } @@ -215,7 +210,7 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { return status; } - auto it = mPublishedSeqMap.find(publishedSeq); + auto it = mPublishedSeqMap.find(result->seq); if (it == mPublishedSeqMap.end()) { continue; } @@ -225,9 +220,9 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, " - "pendingEvents=%zu.", - getInputChannelName().c_str(), seq, handled ? "true" : "false", - mPublishedSeqMap.size()); + "pendingEvents=%zu.", + getInputChannelName().c_str(), seq, result->handled ? "true" : "false", + mPublishedSeqMap.size()); } if (!skipCallbacks) { @@ -241,8 +236,8 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { } env->CallVoidMethod(senderObj.get(), - gInputEventSenderClassInfo.dispatchInputEventFinished, - jint(seq), jboolean(handled)); + gInputEventSenderClassInfo.dispatchInputEventFinished, + static_cast<jint>(seq), static_cast<jboolean>(result->handled)); if (env->ExceptionCheck()) { ALOGE("Exception dispatching finished signal."); skipCallbacks = true; diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp index ebc507a8381e..469e577829d8 100644 --- a/core/jni/android_view_KeyCharacterMap.cpp +++ b/core/jni/android_view_KeyCharacterMap.cpp @@ -16,9 +16,10 @@ #include <android_runtime/AndroidRuntime.h> -#include <input/KeyCharacterMap.h> -#include <input/Input.h> #include <binder/Parcel.h> +#include <input/Input.h> +#include <input/InputDevice.h> +#include <input/KeyCharacterMap.h> #include <jni.h> #include <nativehelper/JNIHelp.h> @@ -75,6 +76,10 @@ jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId, reinterpret_cast<jlong>(nativeMap)); } +static jobject nativeObtainEmptyKeyCharacterMap(JNIEnv* env, jobject /* clazz */, jint deviceId) { + return android_view_KeyCharacterMap_create(env, deviceId, nullptr); +} + static jlong nativeReadFromParcel(JNIEnv *env, jobject clazz, jobject parcelObj) { Parcel* parcel = parcelForJavaObject(env, parcelObj); if (!parcel) { @@ -224,33 +229,37 @@ static jobjectArray nativeGetEvents(JNIEnv *env, jobject clazz, jlong ptr, return result; } +static jboolean nativeEquals(JNIEnv* env, jobject clazz, jlong ptr1, jlong ptr2) { + const std::shared_ptr<KeyCharacterMap>& map1 = + (reinterpret_cast<NativeKeyCharacterMap*>(ptr1))->getMap(); + const std::shared_ptr<KeyCharacterMap>& map2 = + (reinterpret_cast<NativeKeyCharacterMap*>(ptr2))->getMap(); + if (map1 == nullptr || map2 == nullptr) { + return map1 == map2; + } + return static_cast<jboolean>(*map1 == *map2); +} /* * JNI registration. */ static const JNINativeMethod g_methods[] = { - /* name, signature, funcPtr */ - { "nativeReadFromParcel", "(Landroid/os/Parcel;)J", - (void*)nativeReadFromParcel }, - { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V", - (void*)nativeWriteToParcel }, - { "nativeDispose", "(J)V", - (void*)nativeDispose }, - { "nativeGetCharacter", "(JII)C", - (void*)nativeGetCharacter }, - { "nativeGetFallbackAction", "(JIILandroid/view/KeyCharacterMap$FallbackAction;)Z", - (void*)nativeGetFallbackAction }, - { "nativeGetNumber", "(JI)C", - (void*)nativeGetNumber }, - { "nativeGetMatch", "(JI[CI)C", - (void*)nativeGetMatch }, - { "nativeGetDisplayLabel", "(JI)C", - (void*)nativeGetDisplayLabel }, - { "nativeGetKeyboardType", "(J)I", - (void*)nativeGetKeyboardType }, - { "nativeGetEvents", "(J[C)[Landroid/view/KeyEvent;", - (void*)nativeGetEvents }, + /* name, signature, funcPtr */ + {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel}, + {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel}, + {"nativeDispose", "(J)V", (void*)nativeDispose}, + {"nativeGetCharacter", "(JII)C", (void*)nativeGetCharacter}, + {"nativeGetFallbackAction", "(JIILandroid/view/KeyCharacterMap$FallbackAction;)Z", + (void*)nativeGetFallbackAction}, + {"nativeGetNumber", "(JI)C", (void*)nativeGetNumber}, + {"nativeGetMatch", "(JI[CI)C", (void*)nativeGetMatch}, + {"nativeGetDisplayLabel", "(JI)C", (void*)nativeGetDisplayLabel}, + {"nativeGetKeyboardType", "(J)I", (void*)nativeGetKeyboardType}, + {"nativeGetEvents", "(J[C)[Landroid/view/KeyEvent;", (void*)nativeGetEvents}, + {"nativeObtainEmptyKeyCharacterMap", "(I)Landroid/view/KeyCharacterMap;", + (void*)nativeObtainEmptyKeyCharacterMap}, + {"nativeEquals", "(JJ)Z", (void*)nativeEquals}, }; int register_android_view_KeyCharacterMap(JNIEnv* env) diff --git a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp index 6b41b2ec8f93..d644e3709045 100644 --- a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp +++ b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp @@ -30,13 +30,13 @@ static jboolean KernelCpuBpfTracking_startTrackingInternal(JNIEnv *, jobject) { static jlongArray KernelCpuBpfTracking_getFreqsInternal(JNIEnv *env, jobject) { auto freqs = android::bpf::getCpuFreqs(); - if (!freqs) return NULL; + if (!freqs) return nullptr; std::vector<uint64_t> allFreqs; for (const auto &vec : *freqs) std::copy(vec.begin(), vec.end(), std::back_inserter(allFreqs)); auto ar = env->NewLongArray(allFreqs.size()); - if (ar != NULL) { + if (ar != nullptr) { env->SetLongArrayRegion(ar, 0, allFreqs.size(), reinterpret_cast<const jlong *>(allFreqs.data())); } @@ -45,7 +45,7 @@ static jlongArray KernelCpuBpfTracking_getFreqsInternal(JNIEnv *env, jobject) { static jintArray KernelCpuBpfTracking_getFreqsClustersInternal(JNIEnv *env, jobject) { auto freqs = android::bpf::getCpuFreqs(); - if (!freqs) return NULL; + if (!freqs) return nullptr; std::vector<uint32_t> freqsClusters; uint32_t clusters = freqs->size(); @@ -54,7 +54,7 @@ static jintArray KernelCpuBpfTracking_getFreqsClustersInternal(JNIEnv *env, jobj } auto ar = env->NewIntArray(freqsClusters.size()); - if (ar != NULL) { + if (ar != nullptr) { env->SetIntArrayRegion(ar, 0, freqsClusters.size(), reinterpret_cast<const jint *>(freqsClusters.data())); } diff --git a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp index ad43014d321f..472bd23c0f8d 100644 --- a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp +++ b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp @@ -22,7 +22,7 @@ namespace android { static jlongArray KernelCpuTotalBpfMapReader_readInternal(JNIEnv *env, jobject) { auto freqTimes = android::bpf::getTotalCpuFreqTimes(); - if (!freqTimes) return JNI_FALSE; + if (!freqTimes) return nullptr; std::vector<uint64_t> allTimes; for (const auto &vec : *freqTimes) { @@ -32,7 +32,7 @@ static jlongArray KernelCpuTotalBpfMapReader_readInternal(JNIEnv *env, jobject) } auto ar = env->NewLongArray(allTimes.size()); - if (ar != NULL) { + if (ar != nullptr) { env->SetLongArrayRegion(ar, 0, allTimes.size(), reinterpret_cast<const jlong *>(allTimes.data())); } diff --git a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp index 7900d301dbb0..098a4d868269 100644 --- a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp +++ b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp @@ -37,7 +37,7 @@ static jlongArray getUidArray(JNIEnv *env, jobject sparseAr, uint32_t uid, jsize jlongArray ar = (jlongArray)env->CallObjectMethod(sparseAr, gSparseArrayClassInfo.get, uid); if (!ar) { ar = env->NewLongArray(sz); - if (ar == NULL) return ar; + if (ar == nullptr) return ar; env->CallVoidMethod(sparseAr, gSparseArrayClassInfo.put, uid, ar); } return ar; @@ -65,7 +65,7 @@ static jboolean KernelCpuUidFreqTimeBpfMapReader_readBpfData(JNIEnv *env, jobjec static uint64_t lastUpdate = 0; uint64_t newLastUpdate = lastUpdate; auto sparseAr = env->GetObjectField(thiz, gmData); - if (sparseAr == NULL) return false; + if (sparseAr == nullptr) return false; auto data = android::bpf::getUidsUpdatedCpuFreqTimes(&newLastUpdate); if (!data.has_value()) return false; @@ -75,7 +75,7 @@ static jboolean KernelCpuUidFreqTimeBpfMapReader_readBpfData(JNIEnv *env, jobjec for (const auto &subVec : times) s += subVec.size(); } jlongArray ar = getUidArray(env, sparseAr, uid, s); - if (ar == NULL) return false; + if (ar == nullptr) return false; copy2DVecToArray(env, ar, times); } lastUpdate = newLastUpdate; @@ -91,7 +91,7 @@ static jboolean KernelCpuUidActiveTimeBpfMapReader_readBpfData(JNIEnv *env, jobj static uint64_t lastUpdate = 0; uint64_t newLastUpdate = lastUpdate; auto sparseAr = env->GetObjectField(thiz, gmData); - if (sparseAr == NULL) return false; + if (sparseAr == nullptr) return false; auto data = android::bpf::getUidsUpdatedConcurrentTimes(&newLastUpdate); if (!data.has_value()) return false; @@ -99,7 +99,7 @@ static jboolean KernelCpuUidActiveTimeBpfMapReader_readBpfData(JNIEnv *env, jobj // TODO: revise calling code so we can divide by NSEC_PER_MSEC here instead for (auto &time : times.active) time /= NSEC_PER_MSEC; jlongArray ar = getUidArray(env, sparseAr, uid, times.active.size()); - if (ar == NULL) return false; + if (ar == nullptr) return false; env->SetLongArrayRegion(ar, 0, times.active.size(), reinterpret_cast<const jlong *>(times.active.data())); } @@ -111,7 +111,7 @@ static jlongArray KernelCpuUidActiveTimeBpfMapReader_getDataDimensions(JNIEnv *e jlong nCpus = get_nprocs_conf(); auto ar = env->NewLongArray(1); - if (ar != NULL) env->SetLongArrayRegion(ar, 0, 1, &nCpus); + if (ar != nullptr) env->SetLongArrayRegion(ar, 0, 1, &nCpus); return ar; } @@ -124,7 +124,7 @@ static jboolean KernelCpuUidClusterTimeBpfMapReader_readBpfData(JNIEnv *env, job static uint64_t lastUpdate = 0; uint64_t newLastUpdate = lastUpdate; auto sparseAr = env->GetObjectField(thiz, gmData); - if (sparseAr == NULL) return false; + if (sparseAr == nullptr) return false; auto data = android::bpf::getUidsUpdatedConcurrentTimes(&newLastUpdate); if (!data.has_value()) return false; @@ -134,7 +134,7 @@ static jboolean KernelCpuUidClusterTimeBpfMapReader_readBpfData(JNIEnv *env, job for (const auto &subVec : times.policy) s += subVec.size(); } jlongArray ar = getUidArray(env, sparseAr, uid, s); - if (ar == NULL) return false; + if (ar == nullptr) return false; copy2DVecToArray(env, ar, times.policy); } lastUpdate = newLastUpdate; @@ -143,12 +143,12 @@ static jboolean KernelCpuUidClusterTimeBpfMapReader_readBpfData(JNIEnv *env, job static jlongArray KernelCpuUidClusterTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) { auto times = android::bpf::getUidConcurrentTimes(0); - if (!times.has_value()) return NULL; + if (!times.has_value()) return nullptr; std::vector<jlong> clusterCores; for (const auto &vec : times->policy) clusterCores.push_back(vec.size()); auto ar = env->NewLongArray(clusterCores.size()); - if (ar != NULL) env->SetLongArrayRegion(ar, 0, clusterCores.size(), clusterCores.data()); + if (ar != nullptr) env->SetLongArrayRegion(ar, 0, clusterCores.size(), clusterCores.data()); return ar; } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 836074f1d5f7..0bed29b7ba28 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -184,6 +184,17 @@ static constexpr int PROCESS_PRIORITY_MIN = 19; /** The numeric value for the normal priority a process should have. */ static constexpr int PROCESS_PRIORITY_DEFAULT = 0; +/** Exponential back off parameters for storage dir check. */ +static constexpr unsigned int STORAGE_DIR_CHECK_RETRY_MULTIPLIER = 2; +static constexpr unsigned int STORAGE_DIR_CHECK_INIT_INTERVAL_US = 50; +static constexpr unsigned int STORAGE_DIR_CHECK_MAX_INTERVAL_US = 1000; +/** + * Lower bound time we allow storage dir check to sleep. + * If it exceeds 2s, PROC_START_TIMEOUT_MSG will kill the starting app anyway, + * so it's fine to assume max retries is 5 mins. + */ +static constexpr int STORAGE_DIR_CHECK_TIMEOUT_US = 1000 * 1000 * 60 * 5; + /** * A helper class containing accounting information for USAPs. */ @@ -1458,6 +1469,31 @@ static void isolateJitProfile(JNIEnv* env, jobjectArray pkg_data_info_list, } } +static void WaitUntilDirReady(const std::string& target, fail_fn_t fail_fn) { + unsigned int sleepIntervalUs = STORAGE_DIR_CHECK_INIT_INTERVAL_US; + + // This is just an approximate value as it doesn't need to be very accurate. + unsigned int sleepTotalUs = 0; + + const char* dir_path = target.c_str(); + while (sleepTotalUs < STORAGE_DIR_CHECK_TIMEOUT_US) { + if (access(dir_path, F_OK) == 0) { + return; + } + // Failed, so we add exponential backoff and retry + usleep(sleepIntervalUs); + sleepTotalUs += sleepIntervalUs; + sleepIntervalUs = std::min<unsigned int>( + sleepIntervalUs * STORAGE_DIR_CHECK_RETRY_MULTIPLIER, + STORAGE_DIR_CHECK_MAX_INTERVAL_US); + } + // Last chance and get the latest errno if it fails. + if (access(dir_path, F_OK) == 0) { + return; + } + fail_fn(CREATE_ERROR("Error dir is not ready %s: %s", dir_path, strerror(errno))); +} + static void BindMountStorageToLowerFs(const userid_t user_id, const uid_t uid, const char* dir_name, const char* package, fail_fn_t fail_fn) { bool hasSdcardFs = IsSdcardfsUsed(); @@ -1468,6 +1504,10 @@ static void BindMountStorageToLowerFs(const userid_t user_id, const uid_t uid, source = StringPrintf("/mnt/pass_through/%d/emulated/%d/%s/%s", user_id, user_id, dir_name, package); } + + // Directory might be not ready, as prepareStorageDirs() is running asynchronously in ProcessList, + // so wait until dir is created. + WaitUntilDirReady(source, fail_fn); std::string target = StringPrintf("/storage/emulated/%d/%s/%s", user_id, dir_name, package); // As the parent is mounted as tmpfs, we need to create the target dir here. diff --git a/core/jni/permission_utils.cpp b/core/jni/permission_utils.cpp new file mode 100644 index 000000000000..2b7ef9999491 --- /dev/null +++ b/core/jni/permission_utils.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "permission_utils.h" +#include "core_jni_helpers.h" + +static struct { + jfieldID fieldUid; // Identity.uid + jfieldID fieldPid; // Identity.pid + jfieldID fieldPackageName; // Identity.packageName + jfieldID fieldAttributionTag; // Identity.attributionTag +} javaIdentityFields; + +static const JNINativeMethod method_table[] = { + // no static methods, currently +}; + +int register_android_media_permission_Identity(JNIEnv* env) { + jclass identityClass = android::FindClassOrDie(env, "android/media/permission/Identity"); + javaIdentityFields.fieldUid = android::GetFieldIDOrDie(env, identityClass, "uid", "I"); + javaIdentityFields.fieldPid = android::GetFieldIDOrDie(env, identityClass, "pid", "I"); + javaIdentityFields.fieldPackageName = + android::GetFieldIDOrDie(env, identityClass, "packageName", "Ljava/lang/String;"); + javaIdentityFields.fieldAttributionTag = + android::GetFieldIDOrDie(env, identityClass, "attributionTag", "Ljava/lang/String;"); + + return android::RegisterMethodsOrDie(env, "android/media/permission/Identity", method_table, + NELEM(method_table)); +} + +namespace android::media::permission { + +Identity convertIdentity(JNIEnv* env, const jobject& jIdentity) { + Identity identity; + + identity.uid = env->GetIntField(jIdentity, javaIdentityFields.fieldUid); + identity.pid = env->GetIntField(jIdentity, javaIdentityFields.fieldPid); + + jstring packageNameStr = static_cast<jstring>( + env->GetObjectField(jIdentity, javaIdentityFields.fieldPackageName)); + if (packageNameStr == nullptr) { + identity.packageName = std::nullopt; + } else { + identity.packageName = std::string(ScopedUtfChars(env, packageNameStr).c_str()); + } + + jstring attributionTagStr = static_cast<jstring>( + env->GetObjectField(jIdentity, javaIdentityFields.fieldAttributionTag)); + if (attributionTagStr == nullptr) { + identity.attributionTag = std::nullopt; + } else { + identity.attributionTag = std::string(ScopedUtfChars(env, attributionTagStr).c_str()); + } + + return identity; +} + +} // namespace android::media::permission diff --git a/core/jni/permission_utils.h b/core/jni/permission_utils.h new file mode 100644 index 000000000000..d625bb6ba30a --- /dev/null +++ b/core/jni/permission_utils.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <android/media/permission/Identity.h> +#include <jni.h> + +namespace android::media::permission { + +Identity convertIdentity(JNIEnv* env, const jobject& jIdentity); +} + +int register_android_media_permission_Identity(JNIEnv* env); diff --git a/core/proto/OWNERS b/core/proto/OWNERS index e62b5c102a59..ea5e7f729845 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -14,9 +14,10 @@ per-file settings_enums.proto=tmfang@google.com # Frameworks ogunwale@google.com jjaggi@google.com +kwekua@google.com roosa@google.com per-file package_item_info.proto = toddke@google.com -per-file usagestatsservice.proto, usagestatsservice_v2.proto = mwachens@google.com +per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS # Biometrics diff --git a/core/proto/android/app/OWNERS b/core/proto/android/app/OWNERS index 296abd18aadc..cc479e61b855 100644 --- a/core/proto/android/app/OWNERS +++ b/core/proto/android/app/OWNERS @@ -1 +1 @@ -per-file location_time_zone_manager.proto = nfuller@google.com, mingaleev@google.com +per-file location_time_zone_manager.proto, time_zone_detector.proto = nfuller@google.com, mingaleev@google.com diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto index f44d5495f132..891e9fca36aa 100644 --- a/core/proto/android/app/location_time_zone_manager.proto +++ b/core/proto/android/app/location_time_zone_manager.proto @@ -17,6 +17,7 @@ syntax = "proto2"; package android.app.time; +import "frameworks/base/core/proto/android/app/time_zone_detector.proto"; import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; @@ -31,15 +32,6 @@ message LocationTimeZoneManagerServiceStateProto { repeated TimeZoneProviderStateProto secondary_provider_states = 3; } -// Represents a GeolocationTimeZoneSuggestion that can be / has been passed to the time zone -// detector. -message GeolocationTimeZoneSuggestionProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - - repeated string zone_ids = 1; - repeated string debug_info = 2; -} - // The state tracked for a LocationTimeZoneProvider. message TimeZoneProviderStateProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto new file mode 100644 index 000000000000..b33ca1d4f476 --- /dev/null +++ b/core/proto/android/app/time_zone_detector.proto @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.app.time; + +import "frameworks/base/core/proto/android/privacy.proto"; + +option java_multiple_files = true; +option java_outer_classname = "TimeZoneDetectorProto"; + +// Represents a GeolocationTimeZoneSuggestion that can be / has been passed to the time zone +// detector. +message GeolocationTimeZoneSuggestionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + repeated string zone_ids = 1; + repeated string debug_info = 2; +} + +/* + * An obfuscated and simplified time zone suggestion for metrics use. + * + * The suggestion's time zone IDs (which relate to location) are obfuscated by + * mapping them to an ordinal. When the ordinal is assigned consistently across + * several objects (i.e. so the same time zone ID is always mapped to the same + * ordinal), this allows comparisons between those objects. For example, we can + * answer "did these two suggestions agree?", "does the suggestion match the + * device's current time zone?", without leaking knowledge of location. Ordinals + * are also significantly more compact than full IANA TZDB IDs, albeit highly + * unstable and of limited use. + */ +message MetricsTimeZoneSuggestion { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + enum Type { + CERTAIN = 1; + UNCERTAIN = 2; + } + optional Type type = 1; + + // The ordinals for time zone(s) in the suggestion. Always empty for + // UNCERTAIN, and can be empty for CERTAIN, for example when the device is in + // a disputed area / on an ocean. + repeated uint32 time_zone_ordinals = 2; +} diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index f26bf7cdb6c1..a7127ad79401 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -346,6 +346,7 @@ message ActivityRecordProto { optional int32 proc_id = 29; optional bool translucent = 30; optional bool pip_auto_enter_enabled = 31; + optional bool in_size_compat_mode = 32; } /* represents WindowToken */ @@ -406,7 +407,7 @@ message WindowStateProto { optional int64 finished_seamless_rotation_frame = 40; optional WindowFramesProto window_frames = 41; optional bool force_seamless_rotation = 42; - optional bool in_size_compat_mode = 43; + optional bool has_compat_scale = 43; optional float global_scale = 44; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 991f5dec9f2d..f92276171b26 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5259,15 +5259,20 @@ <permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to manage speech recognition service. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.MANAGE_SPEECH_RECOGNITION" + android:protectionLevel="signature" /> + <!-- @SystemApi Allows an application to manage the content suggestions service. @hide <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" - android:protectionLevel="signature" /> + android:protectionLevel="signature" /> <!-- @SystemApi Allows an application to manage the app predictions service. @hide <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.MANAGE_APP_PREDICTIONS" - android:protectionLevel="signature|appPredictor" /> + android:protectionLevel="signature|appPredictor" /> <!-- @SystemApi Allows an application to manage the search ui service. @hide <p>Not for use by third-party applications.</p> --> @@ -5414,6 +5419,8 @@ intents}. <p>Protection level: normal --> <permission android:name="android.permission.USE_FULL_SCREEN_INTENT" + android:label="@string/permlab_fullScreenIntent" + android:description="@string/permdesc_fullScreenIntent" android:protectionLevel="normal" /> <!-- @SystemApi Allows requesting the framework broadcast the diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 7c446a90b06f..6ebd8786b5f1 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3564,16 +3564,6 @@ <attr name="__removed2" format="boolean" /> <!-- Specifies whether the IME supports showing inline suggestions. --> <attr name="supportsInlineSuggestions" format="boolean" /> - <!-- Specify one or more configuration changes that the IME will handle itself. If not - specified, the IME will be restarted if any of these configuration changes happen in - the system. Otherwise, the IME will remain running and its - {@link android.inputmethodservice.InputMethodService#onConfigurationChanged} - method is called with the new configuration. - <p>Note that all of these configuration changes can impact the - resource values seen by the application, so you will generally need - to re-retrieve all resources (including view layouts, drawables, etc) - to correctly handle any configuration change.--> - <attr name="configChanges" /> </declare-styleable> <!-- This is the subtype of InputMethod. Subtype can describe locales (for example, en_US and @@ -8173,7 +8163,7 @@ <flag name="hide_from_picker" value="0x2" /> <!-- The widget provides a default configuration. The host may decide not to launch the provided configuration activity. --> - <flag name="configuration_optional" value="0x3" /> + <flag name="configuration_optional" value="0x4" /> </attr> <!-- A resource identifier for a string containing a short description of the widget. --> <attr name="description" /> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index cc52655ad7d2..601d66ec7b39 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -829,7 +829,6 @@ {@code FLAG_ACTIVITY_MULTIPLE_TASK} is set.--> <enum name="singleInstancePerTask" value="4" /> </attr> - <!-- Specify the orientation an activity should be run in. If not specified, it will run in the current preferred orientation of the screen. @@ -1603,6 +1602,12 @@ <enum name="sync" value="2" /> </attr> + <!-- Attribution tag to be used for permission sub-attribution if a + permission is checked in {@link android.content.Context#sendBroadcast(Intent, String)}. + Multiple tags can be specified separated by '|'. + --> + <attr name="attributionTags" format="string" /> + <!-- The <code>manifest</code> tag is the root of an <code>AndroidManifest.xml</code> file, describing the contents of an Android package (.apk) file. One @@ -2825,6 +2830,10 @@ <p> See {@link android.content.pm.ActivityInfo#FLAG_PREFER_MINIMAL_POST_PROCESSING} --> <attr name="preferMinimalPostProcessing" format="boolean"/> + <!-- Specify the attributionTags to be used if a permission is required due to + {@link android.content.Context#sendBroadcast(Intent, String)} being used. + Multiple tags can be specified separated by '|'. --> + <attr name="attributionTags"/> </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 8bc3a52fa17b..f6fee880bf2f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -324,9 +324,6 @@ <item>"0,1"</item> </string-array> - <!-- The maximum duration (in milliseconds) we expect a network transition to take --> - <integer name="config_networkTransitionTimeout">60000</integer> - <!-- Whether/how to notify the user on network switches. See LingerMonitor.java. --> <integer translatable="false" name="config_networkNotifySwitchType">0</integer> @@ -339,16 +336,6 @@ Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. --> <integer translatable="false" name="config_networkAvoidBadWifi">1</integer> - <!-- Configuration hook for the URL returned by ConnectivityManager#getCaptivePortalServerUrl. - If empty, the returned value is controlled by Settings.Global.CAPTIVE_PORTAL_HTTP_URL, - and if that value is empty, the framework will use a hard-coded default. - This is *NOT* a URL that will always be used by the system network validation to detect - captive portals: NetworkMonitor may use different strategies and will not necessarily use - this URL. NetworkMonitor behaviour should be configured with NetworkStack resource overlays - instead. --> - <!--suppress CheckTagEmptyBody --> - <string translatable="false" name="config_networkCaptivePortalServerUrl"></string> - <!-- If the hardware supports specially marking packets that caused a wakeup of the main CPU, set this value to the mark used. --> <integer name="config_networkWakeupPacketMark">0</integer> @@ -456,14 +443,6 @@ apps requested it. --> <bool name="config_vehicleInternalNetworkAlwaysRequested">false</bool> - <!-- Configuration of network interfaces that support WakeOnLAN --> - <string-array translatable="false" name="config_wakeonlan_supported_interfaces"> - <!-- - <item>wlan0</item> - <item>eth0</item> - --> - </string-array> - <!-- This setting is deprecated, please use com.android.networkstack.tethering.R.array.config_mobile_hotspot_provision_app instead. --> <string-array translatable="false" name="config_mobile_hotspot_provision_app"> @@ -1961,6 +1940,8 @@ <string name="config_systemWifiCoexManager" translateable="false"></string> <!-- The name of the package that will hold the wellbeing role. --> <string name="config_systemWellbeing" translatable="false"></string> + <!-- The name of the package that will hold the television notification handler role --> + <string name="config_systemTelevisionNotificationHandler" translatable="false"></string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false"></string> @@ -2270,6 +2251,9 @@ <bool name="config_dozeWakeLockScreenSensorAvailable">false</bool> <integer name="config_dozeWakeLockScreenDebounce">300</integer> + <!-- Type of the quick pickup sensor. Empty if quick pickup is not supported. --> + <string name="config_quickPickupSensorType" translatable="false"></string> + <!-- Control whether the always on display mode is available. This should only be enabled on devices where the display has been tuned to be power efficient in DOZE and/or DOZE_SUSPEND states. --> @@ -4738,4 +4722,97 @@ <!-- Whether to allow the caching of the SIM PIN for verification after unattended reboot --> <bool name="config_allow_pin_storage_for_unattended_reboot">true</bool> + + <!-- CEC Configuration --> + <bool name="config_cecHdmiCecEnabled_userConfigurable">true</bool> + <bool name="config_cecHdmiCecControlEnabled_allowed">true</bool> + <bool name="config_cecHdmiCecControlEnabled_default">true</bool> + <bool name="config_cecHdmiCecControlDisabled_allowed">true</bool> + <bool name="config_cecHdmiCecControlDisabled_default">false</bool> + + <bool name="config_cecHdmiCecVersion_userConfigurable">true</bool> + <bool name="config_cecHdmiCecVersion14b_allowed">true</bool> + <bool name="config_cecHdmiCecVersion14b_default">true</bool> + <bool name="config_cecHdmiCecVersion20_allowed">true</bool> + <bool name="config_cecHdmiCecVersion20_default">false</bool> + + <bool name="config_cecSendStandbyOnSleep_userConfigurable">true</bool> + <bool name="config_cecPowerControlModeTv_allowed">true</bool> + <bool name="config_cecPowerControlModeTv_default">true</bool> + <bool name="config_cecPowerControlModeBroadcast_allowed">true</bool> + <bool name="config_cecPowerControlModeBroadcast_default">false</bool> + <bool name="config_cecPowerControlModeNone_allowed">true</bool> + <bool name="config_cecPowerControlModeNone_default">false</bool> + + <bool name="config_cecPowerStateChangeOnActiveSourceLost_userConfigurable">true</bool> + <bool name="config_cecPowerStateChangeOnActiveSourceLostNone_allowed">true</bool> + <bool name="config_cecPowerStateChangeOnActiveSourceLostNone_default">true</bool> + <bool name="config_cecPowerStateChangeOnActiveSourceLostStandbyNow_allowed">true</bool> + <bool name="config_cecPowerStateChangeOnActiveSourceLostStandbyNow_default">false</bool> + + <bool name="config_cecSystemAudioModeMuting_userConfigurable">true</bool> + <bool name="config_cecSystemAudioModeMutingEnabled_allowed">true</bool> + <bool name="config_cecSystemAudioModeMutingEnabled_default">true</bool> + <bool name="config_cecSystemAudioModeMutingDisabled_allowed">true</bool> + <bool name="config_cecSystemAudioModeMutingDisabled_default">false</bool> + + <bool name="config_cecVolumeControlMode_userConfigurable">true</bool> + <bool name="config_cecVolumeControlModeEnabled_allowed">true</bool> + <bool name="config_cecVolumeControlModeEnabled_default">true</bool> + <bool name="config_cecVolumeControlModeDisabled_allowed">true</bool> + <bool name="config_cecVolumeControlModeDisabled_default">false</bool> + + <bool name="config_cecTvWakeOnOneTouchPlay_userConfigurable">true</bool> + <bool name="config_cecTvWakeOnOneTouchPlayEnabled_allowed">true</bool> + <bool name="config_cecTvWakeOnOneTouchPlayEnabled_default">true</bool> + <bool name="config_cecTvWakeOnOneTouchPlayDisabled_allowed">true</bool> + <bool name="config_cecTvWakeOnOneTouchPlayDisabled_default">false</bool> + + <bool name="config_cecTvSendStandbyOnSleep_userConfigurable">true</bool> + <bool name="config_cecTvSendStandbyOnSleepEnabled_allowed">true</bool> + <bool name="config_cecTvSendStandbyOnSleepEnabled_default">true</bool> + <bool name="config_cecTvSendStandbyOnSleepDisabled_allowed">true</bool> + <bool name="config_cecTvSendStandbyOnSleepDisabled_default">false</bool> + + <bool name="config_cecRcProfileTv_userConfigurable">true</bool> + <bool name="config_cecRcProfileTvNone_allowed">true</bool> + <bool name="config_cecRcProfileTvNone_default">true</bool> + <bool name="config_cecRcProfileTvOne_allowed">true</bool> + <bool name="config_cecRcProfileTvOne_default">false</bool> + <bool name="config_cecRcProfileTvTwo_allowed">true</bool> + <bool name="config_cecRcProfileTvTwo_default">false</bool> + <bool name="config_cecRcProfileTvThree_allowed">true</bool> + <bool name="config_cecRcProfileTvThree_default">false</bool> + <bool name="config_cecRcProfileTvFour_allowed">true</bool> + <bool name="config_cecRcProfileTvFour_default">false</bool> + + <bool name="config_cecRcProfileSourceRootMenu_userConfigurable">true</bool> + <bool name="config_cecRcProfileSourceRootMenuHandled_allowed">true</bool> + <bool name="config_cecRcProfileSourceRootMenuHandled_default">true</bool> + <bool name="config_cecRcProfileSourceRootMenuNotHandled_allowed">true</bool> + <bool name="config_cecRcProfileSourceRootMenuNotHandled_default">false</bool> + + <bool name="config_cecRcProfileSourceSetupMenu_userConfigurable">true</bool> + <bool name="config_cecRcProfileSourceSetupMenuHandled_allowed">true</bool> + <bool name="config_cecRcProfileSourceSetupMenuHandled_default">true</bool> + <bool name="config_cecRcProfileSourceSetupMenuNotHandled_allowed">true</bool> + <bool name="config_cecRcProfileSourceSetupMenuNotHandled_default">false</bool> + + <bool name="config_cecRcProfileSourceContentsMenu_userConfigurable">true</bool> + <bool name="config_cecRcProfileSourceContentsMenuHandled_allowed">true</bool> + <bool name="config_cecRcProfileSourceContentsMenuHandled_default">false</bool> + <bool name="config_cecRcProfileSourceContentsMenuNotHandled_allowed">true</bool> + <bool name="config_cecRcProfileSourceContentsMenuNotHandled_default">true</bool> + + <bool name="config_cecRcProfileSourceTopMenu_userConfigurable">true</bool> + <bool name="config_cecRcProfileSourceTopMenuHandled_allowed">true</bool> + <bool name="config_cecRcProfileSourceTopMenuHandled_default">false</bool> + <bool name="config_cecRcProfileSourceTopMenuNotHandled_allowed">true</bool> + <bool name="config_cecRcProfileSourceTopMenuNotHandled_default">true</bool> + + <bool name="config_cecRcProfileSourceMediaContextSensitiveMenu_userConfigurable">true</bool> + <bool name="config_cecRcProfileSourceMediaContextSensitiveMenuHandled_allowed">true</bool> + <bool name="config_cecRcProfileSourceMediaContextSensitiveMenuHandled_default">false</bool> + <bool name="config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_allowed">true</bool> + <bool name="config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_default">true</bool> </resources> diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index 3a41d5fed238..7bc4663d1070 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -247,4 +247,10 @@ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_IME_ENTER}. --> <item type="id" name="accessibilityActionImeEnter" /> + + <!-- View tag for remote views to store the index of the next child when adding nested remote views dynamically. --> + <item type="id" name="remote_views_next_child" /> + + <!-- View tag associating a view with its stable id for potential recycling. --> + <item type="id" name = "remote_views_stable_id" /> </resources> diff --git a/core/res/res/values/policy_exempt_apps.xml b/core/res/res/values/policy_exempt_apps.xml new file mode 100644 index 000000000000..1cead8322e55 --- /dev/null +++ b/core/res/res/values/policy_exempt_apps.xml @@ -0,0 +1,24 @@ +<?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> + <!-- + A collection of apps that are critical for the device and hence will never be disabled by + device policies or APIs. + --> + <string-array translatable="false" name="policy_exempt_apps"> + </string-array> +</resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 293018d881d8..40c80dbeeb73 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3071,7 +3071,6 @@ <public name="windowSplashScreenAnimationDuration"/> <public name="windowSplashScreenBrandingImage"/> <public name="splashScreenTheme" /> - <public name="rippleStyle" /> <public name="maxResizeWidth" /> <public name="maxResizeHeight" /> <public name="targetCellWidth" /> @@ -3088,6 +3087,7 @@ <public name="passwordsActivity"/> <public name="selectableAsDefault"/> <public name="isAccessibilityTool"/> + <public name="attributionTags"/> </public-group> <public-group type="drawable" first-id="0x010800b5"> @@ -3175,6 +3175,8 @@ <public name="config_systemWifiCoexManager" /> <!-- @hide @SystemApi --> <public name="config_systemWellbeing" /> + <!-- @hide @SystemApi --> + <public name="config_systemTelevisionNotificationHandler" /> </public-group> <public-group type="id" first-id="0x01020055"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3ba0f7c03887..930bb87de59d 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -889,6 +889,11 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_expandStatusBar">Allows the app to expand or collapse the status bar.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_fullScreenIntent">display notifications as full screen activities on a locked device</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_fullScreenIntent">Allows the app to display notifications as full screen activities on a locked device</string> + <!-- Title of an application permission, listed so the user can install application shortcuts in their Launcher --> <string name="permlab_install_shortcut">install shortcuts</string> @@ -3691,6 +3696,8 @@ <string name="ext_media_checking_notification_title">Checking <xliff:g id="name" example="SD card">%s</xliff:g>\u2026</string> <!-- Notification body when external media is being checked [CHAR LIMIT=NONE] --> <string name="ext_media_checking_notification_message">Reviewing current content</string> + <!-- TV specific notification body when external media is being checked [CHAR LIMIT=75] --> + <string name="ext_media_checking_notification_message" product="tv">Analyzing media storage</string> <!-- Notification body when new external media is detected [CHAR LIMIT=30] --> <string name="ext_media_new_notification_title">New <xliff:g id="name" example="SD card">%s</xliff:g></string> @@ -3698,11 +3705,15 @@ <string name="ext_media_new_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string> <!-- Notification body when new external media is detected [CHAR LIMIT=NONE] --> <string name="ext_media_new_notification_message">Tap to set up</string> + <!-- TV specific notification body when new external media is detected [CHAR LIMIT=75] --> + <string name="ext_media_new_notification_message" product="tv">Select to set up</string> <!-- Automotive specific notification body when new external media is detected. [CHAR LIMIT=NONE] --> <string name="ext_media_new_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string> <!-- Notification body when external media is ready for use [CHAR LIMIT=NONE] --> <string name="ext_media_ready_notification_message">For transferring photos and media</string> + <!-- TV specific notification body when external media is ready for use [CHAR LIMIT=75] --> + <string name="ext_media_ready_notification_message" product="tv">Browse media files</string> <!-- Notification title when external media is unmountable (corrupt) [CHAR LIMIT=30] --> <string name="ext_media_unmountable_notification_title">Issue with <xliff:g id="name" example="SD card">%s</xliff:g></string> @@ -3721,8 +3732,8 @@ <string name="ext_media_unsupported_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string> <!-- Notification body when external media is unsupported [CHAR LIMIT=NONE] --> <string name="ext_media_unsupported_notification_message">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>. Tap to set up in a supported format.</string> - <!-- TV-specific notification body when external media is unsupported [CHAR LIMIT=NONE] --> - <string name="ext_media_unsupported_notification_message" product="tv">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>. Select to set up in a supported format.</string> + <!-- TV-specific notification body when external media is unsupported [CHAR LIMIT=75] --> + <string name="ext_media_unsupported_notification_message" product="tv">Select to set up <xliff:g id="name" example="SD card">%s</xliff:g> in a supported format.</string> <!-- Automotive specific notification body when external media is unsupported [CHAR LIMIT=NONE] --> <string name="ext_media_unsupported_notification_message" product="automotive">You may need to reformat the device</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d904f7ab97ed..5a7b1faf36b2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -692,7 +692,6 @@ <java-symbol type="string" name="not_checked" /> <java-symbol type="array" name="config_ethernet_interfaces" /> <java-symbol type="bool" name="config_vehicleInternalNetworkAlwaysRequested" /> - <java-symbol type="array" name="config_wakeonlan_supported_interfaces" /> <java-symbol type="string" name="config_forceVoiceInteractionServicePackage" /> <java-symbol type="string" name="config_mms_user_agent" /> <java-symbol type="string" name="config_mms_user_agent_profile_url" /> @@ -1266,6 +1265,8 @@ <java-symbol type="array" name="vendor_disallowed_apps_managed_device" /> <java-symbol type="array" name="cross_profile_apps" /> <java-symbol type="array" name="vendor_cross_profile_apps" /> + <java-symbol type="array" name="policy_exempt_apps" /> + <java-symbol type="array" name="vendor_policy_exempt_apps" /> <java-symbol type="drawable" name="default_wallpaper" /> <java-symbol type="drawable" name="default_lock_wallpaper" /> @@ -1970,11 +1971,9 @@ <java-symbol type="integer" name="config_lowBatteryCloseWarningBump" /> <java-symbol type="integer" name="config_lowBatteryWarningLevel" /> <java-symbol type="integer" name="config_networkPolicyDefaultWarning" /> - <java-symbol type="integer" name="config_networkTransitionTimeout" /> <java-symbol type="integer" name="config_networkNotifySwitchType" /> <java-symbol type="array" name="config_networkNotifySwitches" /> <java-symbol type="integer" name="config_networkAvoidBadWifi" /> - <java-symbol type="string" name="config_networkCaptivePortalServerUrl" /> <java-symbol type="integer" name="config_networkWakeupPacketMark" /> <java-symbol type="integer" name="config_networkWakeupPacketMask" /> <java-symbol type="bool" name="config_apfDrop802_3Frames" /> @@ -3595,6 +3594,7 @@ <java-symbol type="string" name="config_dozeUdfpsLongPressSensorType" /> <java-symbol type="bool" name="config_dozeWakeLockScreenSensorAvailable" /> <java-symbol type="integer" name="config_dozeWakeLockScreenDebounce" /> + <java-symbol type="string" name="config_quickPickupSensorType" /> <java-symbol type="array" name="config_allowedGlobalInstantAppSettings" /> <java-symbol type="array" name="config_allowedSystemInstantAppSettings" /> @@ -4224,4 +4224,100 @@ <java-symbol type="bool" name="config_voice_data_sms_auto_fallback" /> <java-symbol type="bool" name="config_enableOneHandedKeyguard" /> + + <!-- CEC Configuration --> + <java-symbol type="bool" name="config_cecHdmiCecEnabled_userConfigurable" /> + <java-symbol type="bool" name="config_cecHdmiCecControlEnabled_allowed" /> + <java-symbol type="bool" name="config_cecHdmiCecControlEnabled_default" /> + <java-symbol type="bool" name="config_cecHdmiCecControlDisabled_allowed" /> + <java-symbol type="bool" name="config_cecHdmiCecControlDisabled_default" /> + + <java-symbol type="bool" name="config_cecHdmiCecVersion_userConfigurable" /> + <java-symbol type="bool" name="config_cecHdmiCecVersion14b_allowed" /> + <java-symbol type="bool" name="config_cecHdmiCecVersion14b_default" /> + <java-symbol type="bool" name="config_cecHdmiCecVersion20_allowed" /> + <java-symbol type="bool" name="config_cecHdmiCecVersion20_default" /> + + <java-symbol type="bool" name="config_cecSendStandbyOnSleep_userConfigurable" /> + <java-symbol type="bool" name="config_cecPowerControlModeTv_allowed" /> + <java-symbol type="bool" name="config_cecPowerControlModeTv_default" /> + <java-symbol type="bool" name="config_cecPowerControlModeBroadcast_allowed" /> + <java-symbol type="bool" name="config_cecPowerControlModeBroadcast_default" /> + <java-symbol type="bool" name="config_cecPowerControlModeNone_allowed" /> + <java-symbol type="bool" name="config_cecPowerControlModeNone_default" /> + + <java-symbol type="bool" name="config_cecPowerStateChangeOnActiveSourceLost_userConfigurable" /> + <java-symbol type="bool" name="config_cecPowerStateChangeOnActiveSourceLostNone_allowed" /> + <java-symbol type="bool" name="config_cecPowerStateChangeOnActiveSourceLostNone_default" /> + <java-symbol type="bool" name="config_cecPowerStateChangeOnActiveSourceLostStandbyNow_allowed" /> + <java-symbol type="bool" name="config_cecPowerStateChangeOnActiveSourceLostStandbyNow_default" /> + + <java-symbol type="bool" name="config_cecSystemAudioModeMuting_userConfigurable" /> + <java-symbol type="bool" name="config_cecSystemAudioModeMutingEnabled_allowed" /> + <java-symbol type="bool" name="config_cecSystemAudioModeMutingEnabled_default" /> + <java-symbol type="bool" name="config_cecSystemAudioModeMutingDisabled_allowed" /> + <java-symbol type="bool" name="config_cecSystemAudioModeMutingDisabled_default" /> + + <java-symbol type="bool" name="config_cecVolumeControlMode_userConfigurable" /> + <java-symbol type="bool" name="config_cecVolumeControlModeEnabled_allowed" /> + <java-symbol type="bool" name="config_cecVolumeControlModeEnabled_default" /> + <java-symbol type="bool" name="config_cecVolumeControlModeDisabled_allowed" /> + <java-symbol type="bool" name="config_cecVolumeControlModeDisabled_default" /> + + <java-symbol type="bool" name="config_cecTvWakeOnOneTouchPlay_userConfigurable" /> + <java-symbol type="bool" name="config_cecTvWakeOnOneTouchPlayEnabled_allowed" /> + <java-symbol type="bool" name="config_cecTvWakeOnOneTouchPlayEnabled_default" /> + <java-symbol type="bool" name="config_cecTvWakeOnOneTouchPlayDisabled_allowed" /> + <java-symbol type="bool" name="config_cecTvWakeOnOneTouchPlayDisabled_default" /> + + <java-symbol type="bool" name="config_cecTvSendStandbyOnSleep_userConfigurable" /> + <java-symbol type="bool" name="config_cecTvSendStandbyOnSleepEnabled_allowed" /> + <java-symbol type="bool" name="config_cecTvSendStandbyOnSleepEnabled_default" /> + <java-symbol type="bool" name="config_cecTvSendStandbyOnSleepDisabled_allowed" /> + <java-symbol type="bool" name="config_cecTvSendStandbyOnSleepDisabled_default" /> + + <java-symbol type="bool" name="config_cecRcProfileTv_userConfigurable" /> + <java-symbol type="bool" name="config_cecRcProfileTvNone_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileTvNone_default" /> + <java-symbol type="bool" name="config_cecRcProfileTvOne_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileTvOne_default" /> + <java-symbol type="bool" name="config_cecRcProfileTvTwo_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileTvTwo_default" /> + <java-symbol type="bool" name="config_cecRcProfileTvThree_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileTvThree_default" /> + <java-symbol type="bool" name="config_cecRcProfileTvFour_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileTvFour_default" /> + + <java-symbol type="bool" name="config_cecRcProfileSourceRootMenu_userConfigurable" /> + <java-symbol type="bool" name="config_cecRcProfileSourceRootMenuHandled_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileSourceRootMenuHandled_default" /> + <java-symbol type="bool" name="config_cecRcProfileSourceRootMenuNotHandled_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileSourceRootMenuNotHandled_default" /> + + <java-symbol type="bool" name="config_cecRcProfileSourceSetupMenu_userConfigurable" /> + <java-symbol type="bool" name="config_cecRcProfileSourceSetupMenuHandled_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileSourceSetupMenuHandled_default" /> + <java-symbol type="bool" name="config_cecRcProfileSourceSetupMenuNotHandled_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileSourceSetupMenuNotHandled_default" /> + + <java-symbol type="bool" name="config_cecRcProfileSourceContentsMenu_userConfigurable" /> + <java-symbol type="bool" name="config_cecRcProfileSourceContentsMenuHandled_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileSourceContentsMenuHandled_default" /> + <java-symbol type="bool" name="config_cecRcProfileSourceContentsMenuNotHandled_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileSourceContentsMenuNotHandled_default" /> + + <java-symbol type="bool" name="config_cecRcProfileSourceTopMenu_userConfigurable" /> + <java-symbol type="bool" name="config_cecRcProfileSourceTopMenuHandled_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileSourceTopMenuHandled_default" /> + <java-symbol type="bool" name="config_cecRcProfileSourceTopMenuNotHandled_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileSourceTopMenuNotHandled_default" /> + + <java-symbol type="bool" name="config_cecRcProfileSourceMediaContextSensitiveMenu_userConfigurable" /> + <java-symbol type="bool" name="config_cecRcProfileSourceMediaContextSensitiveMenuHandled_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileSourceMediaContextSensitiveMenuHandled_default" /> + <java-symbol type="bool" name="config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_allowed" /> + <java-symbol type="bool" name="config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_default" /> + + <java-symbol type="id" name="remote_views_next_child" /> + <java-symbol type="id" name="remote_views_stable_id" /> </resources> diff --git a/core/res/res/values/vendor_policy_exempt_apps.xml b/core/res/res/values/vendor_policy_exempt_apps.xml new file mode 100644 index 000000000000..eb4c760b6724 --- /dev/null +++ b/core/res/res/values/vendor_policy_exempt_apps.xml @@ -0,0 +1,24 @@ +<?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> + <!-- + A collection of apps that are critical for the device and hence will never be disabled by + device policies or APIs. + --> + <string-array translatable="false" name="vendor_policy_exempt_apps"> + </string-array> +</resources> diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java new file mode 100644 index 000000000000..fe31b907f077 --- /dev/null +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.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.app.appsearch; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.expectThrows; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +public class AppSearchSessionUnitTest { + private final Context mContext = ApplicationProvider.getApplicationContext(); + private final AppSearchManager mAppSearch = mContext.getSystemService(AppSearchManager.class); + private final Executor mExecutor = mContext.getMainExecutor(); + private AppSearchSession mSearchSession; + + @Before + 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(); + CompletableFuture<AppSearchResult<AppSearchSession>> future = new CompletableFuture<>(); + mAppSearch.createSearchSession(searchContext, mExecutor, future::complete); + mSearchSession = future.get().getResultValue(); + + CompletableFuture<AppSearchResult<SetSchemaResponse>> schemaFuture = + new CompletableFuture<>(); + mSearchSession.setSchema( + new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor, + schemaFuture::complete); + + schemaFuture.get().getResultValue(); + } + + @Test + public void testPutDocument_throwsNullException() throws Exception { + // Create a document + AppSearchEmail inEmail = + new AppSearchEmail.Builder("uri1") + .setFrom("from@example.com") + .setTo("to1@example.com", "to2@example.com") + .setSubject("testPut example") + .setBody("This is the body of the testPut email") + .build(); + + // clear the document bundle to make our service crash and throw an NullPointerException. + inEmail.getBundle().clear(); + CompletableFuture<AppSearchBatchResult<String, Void>> putDocumentsFuture = + new CompletableFuture<>(); + + // Index the broken document. + mSearchSession.put( + new PutDocumentsRequest.Builder().addGenericDocuments(inEmail).build(), + mExecutor, new BatchResultCallback<String, Void>() { + @Override + public void onResult(AppSearchBatchResult<String, Void> result) { + putDocumentsFuture.complete(result); + } + + @Override + public void onSystemError(Throwable throwable) { + putDocumentsFuture.completeExceptionally(throwable); + } + }); + + // Verify the NullPointException has been thrown. + ExecutionException executionException = expectThrows(ExecutionException.class, + putDocumentsFuture::get); + assertThat(executionException.getCause()).isInstanceOf(NullPointerException.class); + } +} diff --git a/core/tests/coretests/src/android/inputmethodservice/InputMethodServiceTest.java b/core/tests/coretests/src/android/inputmethodservice/InputMethodServiceTest.java deleted file mode 100644 index 4863cfe588ba..000000000000 --- a/core/tests/coretests/src/android/inputmethodservice/InputMethodServiceTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.inputmethodservice; - -import static android.content.res.Configuration.KEYBOARD_12KEY; -import static android.content.res.Configuration.NAVIGATION_NONAV; - -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - - -import static junit.framework.Assert.assertFalse; - -import static org.junit.Assert.assertTrue; - -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.os.Build; - -import androidx.test.filters.SmallTest; -import androidx.test.rule.ServiceTestRule; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.TimeoutException; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class InputMethodServiceTest { - private InputMethodService mService; - private Context mContext; - @Rule - public final ServiceTestRule serviceRule = new ServiceTestRule(); - - @Before - public void setUp() throws TimeoutException { - mContext = getInstrumentation().getContext(); - mService = new InputMethodService(); - } - - @Test - public void testShouldImeRestartForConfig() throws Exception { - // Make sure we preserve Pre-S behavior i.e. Service restarts. - mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.R; - Configuration config = mContext.getResources().getConfiguration(); - mService.setLastKnownConfig(config); - assertTrue("IME should restart for Pre-S", - mService.shouldImeRestartForConfig(config)); - - // IME shouldn't restart on targetSdk S+ (with no config changes). - mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.S; - assertFalse("IME shouldn't restart for S+", - mService.shouldImeRestartForConfig(config)); - - // Screen density changed but IME doesn't handle congfigChanges - config.densityDpi = 99; - assertTrue("IME should restart for unhandled configChanges", - mService.shouldImeRestartForConfig(config)); - - // opt-in IME to handle config changes. - mService.setHandledConfigChanges(ActivityInfo.CONFIG_DENSITY); - assertFalse("IME shouldn't restart for S+ since it handles configChanges", - mService.shouldImeRestartForConfig(config)); - } -} diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java index c06405affc1b..09c36dd261bd 100644 --- a/core/tests/coretests/src/android/os/VibratorInfoTest.java +++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java @@ -16,9 +16,10 @@ package android.os; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import android.hardware.vibrator.IVibrator; import android.platform.test.annotations.Presubmit; @@ -33,72 +34,128 @@ public class VibratorInfoTest { @Test public void testHasAmplitudeControl() { - assertFalse(createInfo(/* capabilities= */ 0).hasAmplitudeControl()); - assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS - | IVibrator.CAP_AMPLITUDE_CONTROL).hasAmplitudeControl()); + VibratorInfo noCapabilities = new InfoBuilder().build(); + assertFalse(noCapabilities.hasAmplitudeControl()); + VibratorInfo composeAndAmplitudeControl = new InfoBuilder() + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS + | IVibrator.CAP_AMPLITUDE_CONTROL) + .build(); + assertTrue(composeAndAmplitudeControl.hasAmplitudeControl()); } @Test public void testHasCapabilities() { - assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS) - .hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)); - assertFalse(createInfo(IVibrator.CAP_COMPOSE_EFFECTS) - .hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)); + VibratorInfo info = new InfoBuilder() + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .build(); + assertTrue(info.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)); + assertFalse(info.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)); } @Test public void testIsEffectSupported() { - VibratorInfo info = new VibratorInfo(/* id= */ 0, /* capabilities= */0, - new int[]{VibrationEffect.EFFECT_CLICK}, null); + VibratorInfo noEffects = new InfoBuilder().build(); + VibratorInfo canClick = new InfoBuilder() + .setSupportedEffects(VibrationEffect.EFFECT_CLICK) + .build(); assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN, - createInfo(/* capabilities= */ 0).isEffectSupported(VibrationEffect.EFFECT_CLICK)); + noEffects.isEffectSupported(VibrationEffect.EFFECT_CLICK)); assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES, - info.isEffectSupported(VibrationEffect.EFFECT_CLICK)); + canClick.isEffectSupported(VibrationEffect.EFFECT_CLICK)); assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO, - info.isEffectSupported(VibrationEffect.EFFECT_TICK)); + canClick.isEffectSupported(VibrationEffect.EFFECT_TICK)); } @Test public void testIsPrimitiveSupported() { - VibratorInfo info = new VibratorInfo(/* id= */ 0, IVibrator.CAP_COMPOSE_EFFECTS, - null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}); + VibratorInfo info = new InfoBuilder() + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK) + .build(); assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)); // Returns false when there is no compose capability. - info = new VibratorInfo(/* id= */ 0, /* capabilities= */ 0, - null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}); + info = new InfoBuilder() + .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK) + .build(); assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); } @Test public void testEquals() { - VibratorInfo empty = new VibratorInfo(1, 0, null, null); - VibratorInfo complete = new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, - new int[]{VibrationEffect.EFFECT_CLICK}, - new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}); + InfoBuilder completeBuilder = new InfoBuilder() + .setId(1) + .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL) + .setSupportedEffects(VibrationEffect.EFFECT_CLICK) + .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK) + .setQFactor(2f) + .setResonantFrequency(150f); + VibratorInfo complete = completeBuilder.build(); assertEquals(complete, complete); - assertEquals(complete, new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, - new int[]{VibrationEffect.EFFECT_CLICK}, - new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})); - - assertFalse(empty.equals(new VibratorInfo(1, 0, new int[]{}, new int[]{}))); - assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS, - new int[]{VibrationEffect.EFFECT_CLICK}, - new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}))); - assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, - new int[]{}, new int[]{}))); - assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, - null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}))); - assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL, - new int[]{VibrationEffect.EFFECT_CLICK}, null))); + assertEquals(complete, completeBuilder.build()); + + VibratorInfo completeWithComposeControl = completeBuilder + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .build(); + assertNotEquals(complete, completeWithComposeControl); + + VibratorInfo completeWithNoEffects = completeBuilder + .setSupportedEffects() + .setSupportedPrimitives() + .build(); + assertNotEquals(complete, completeWithNoEffects); + + VibratorInfo completeWithUnknownEffects = completeBuilder + .setSupportedEffects(null) + .build(); + assertNotEquals(complete, completeWithNoEffects); + + VibratorInfo completeWithUnknownPrimitives = completeBuilder + .setSupportedPrimitives(null) + .build(); + assertNotEquals(complete, completeWithUnknownPrimitives); + + VibratorInfo completeWithDifferentF0 = completeBuilder + .setResonantFrequency(complete.getResonantFrequency() + 3f) + .build(); + assertNotEquals(complete, completeWithDifferentF0); + + VibratorInfo completeWithUnknownF0 = completeBuilder + .setResonantFrequency(Float.NaN) + .build(); + assertNotEquals(complete, completeWithUnknownF0); + + VibratorInfo completeWithUnknownQFactor = completeBuilder + .setQFactor(Float.NaN) + .build(); + assertNotEquals(complete, completeWithUnknownQFactor); + + VibratorInfo completeWithDifferentQFactor = completeBuilder + .setQFactor(complete.getQFactor() + 3f) + .build(); + assertNotEquals(complete, completeWithDifferentQFactor); + + VibratorInfo empty = new InfoBuilder().setId(1).build(); + VibratorInfo emptyWithKnownSupport = new InfoBuilder() + .setId(1) + .setSupportedEffects() + .setSupportedPrimitives() + .build(); + assertNotEquals(empty, emptyWithKnownSupport); } @Test - public void testSerialization() { - VibratorInfo original = new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS, - new int[]{VibrationEffect.EFFECT_CLICK}, null); + public void testParceling() { + VibratorInfo original = new InfoBuilder() + .setId(1) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedEffects(VibrationEffect.EFFECT_CLICK) + .setSupportedPrimitives(null) + .setResonantFrequency(1.3f) + .setQFactor(Float.NaN) + .build(); Parcel parcel = Parcel.obtain(); original.writeToParcel(parcel, 0); @@ -107,7 +164,47 @@ public class VibratorInfoTest { assertEquals(original, restored); } - private static VibratorInfo createInfo(long capabilities) { - return new VibratorInfo(/* id= */ 0, capabilities, null, null); + private static class InfoBuilder { + private int mId = 0; + private int mCapabilities = 0; + private int[] mSupportedEffects = null; + private int[] mSupportedPrimitives = null; + private float mResonantFrequency = Float.NaN; + private float mQFactor = Float.NaN; + + public InfoBuilder setId(int id) { + mId = id; + return this; + } + + public InfoBuilder setCapabilities(int capabilities) { + mCapabilities = capabilities; + return this; + } + + public InfoBuilder setSupportedEffects(int... supportedEffects) { + mSupportedEffects = supportedEffects; + return this; + } + + public InfoBuilder setSupportedPrimitives(int... supportedPrimitives) { + mSupportedPrimitives = supportedPrimitives; + return this; + } + + public InfoBuilder setResonantFrequency(float resonantFrequency) { + mResonantFrequency = resonantFrequency; + return this; + } + + public InfoBuilder setQFactor(float qFactor) { + mQFactor = qFactor; + return this; + } + + public VibratorInfo build() { + return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives, + mResonantFrequency, mQFactor); + } } } diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS index 80165f065995..fa1aa5eab26c 100644 --- a/core/tests/coretests/src/android/view/OWNERS +++ b/core/tests/coretests/src/android/view/OWNERS @@ -9,3 +9,6 @@ per-file *Focus* = file:/services/core/java/com/android/server/wm/OWNERS per-file *Insets* = file:/services/core/java/com/android/server/wm/OWNERS per-file *View* = file:/services/core/java/com/android/server/wm/OWNERS per-file *Visibility* = file:/services/core/java/com/android/server/wm/OWNERS + +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java index 1e0e1235161c..f5fcb03bb816 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java @@ -125,7 +125,7 @@ public class InputMethodSubtypeTest { assertEquals("he", clonedSubtypeHe.getLocale()); } - private static final InputMethodSubtype cloneViaParcel(final InputMethodSubtype original) { + private static InputMethodSubtype cloneViaParcel(final InputMethodSubtype original) { Parcel parcel = null; try { parcel = Parcel.obtain(); @@ -157,4 +157,4 @@ public class InputMethodSubtypeTest { .setIsAsciiCapable(true) .build(); } -}
\ No newline at end of file +} diff --git a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java b/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java index 453ad72b64dc..f264cc630dc5 100644 --- a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java @@ -62,20 +62,20 @@ public class SparseRectFArrayTest { @Test public void testBuilder() throws Exception { - final RectF TEMP_RECT = new RectF(10.0f, 20.0f, 30.0f, 40.0f); - final int TEMP_FLAGS = 0x1234; + final RectF testRect = new RectF(10.0f, 20.0f, 30.0f, 40.0f); + final int testFlags = 0x1234; final SparseRectFArrayBuilder builder = new SparseRectFArrayBuilder(); - builder.append(100, TEMP_RECT.left, TEMP_RECT.top, TEMP_RECT.right, TEMP_RECT.bottom, - TEMP_FLAGS); + builder.append(100, testRect.left, testRect.top, testRect.right, testRect.bottom, + testFlags); assertNull(builder.build().get(-1)); assertNull(builder.build().get(0)); assertNull(builder.build().get(99)); assertEquals(0, builder.build().getFlags(99, 0 /* valueIfKeyNotFound */)); assertEquals(1, builder.build().getFlags(99, 1 /* valueIfKeyNotFound */)); - assertEquals(TEMP_RECT, builder.build().get(100)); - assertEquals(TEMP_FLAGS, builder.build().getFlags(100, 0 /* valueIfKeyNotFound */)); - assertEquals(TEMP_FLAGS, builder.build().getFlags(100, 1 /* valueIfKeyNotFound */)); + assertEquals(testRect, builder.build().get(100)); + assertEquals(testFlags, builder.build().getFlags(100, 0 /* valueIfKeyNotFound */)); + assertEquals(testFlags, builder.build().getFlags(100, 1 /* valueIfKeyNotFound */)); assertNull(builder.build().get(101)); assertEquals(0, builder.build().getFlags(101, 0 /* valueIfKeyNotFound */)); assertEquals(1, builder.build().getFlags(101, 1 /* valueIfKeyNotFound */)); diff --git a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java index f6e02bc1f48a..28f9ccc10135 100644 --- a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java +++ b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java @@ -41,7 +41,9 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import android.content.res.TypedArray; import android.text.Selection; @@ -356,4 +358,71 @@ public class SuggestionsPopupWindowTest { .perform(clearText()); } } + + @Test + public void testCursorVisibility() { + final TextView textView = getActivity().findViewById(R.id.textview); + final String text = "abc"; + + assertTrue(textView.isCursorVisible()); + + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(replaceText(text)); + final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), + new String[]{"ABC"}, SuggestionSpan.FLAG_AUTO_CORRECTION); + setSuggestionSpan(suggestionSpan, text.indexOf('a'), text.indexOf('c') + 1); + showSuggestionsPopup(); + + assertSuggestionsPopupIsDisplayed(); + assertSuggestionsPopupContainsItem("ABC"); + assertFalse(textView.isCursorVisible()); + + // Delete an item. + clickSuggestionsPopupItem( + getActivity().getString(com.android.internal.R.string.delete)); + assertSuggestionsPopupIsNotDisplayed(); + assertTrue(textView.isCursorVisible()); + } + + @Test + public void testCursorVisibilityWhenImeConsumesInput() { + final TextView textView = getActivity().findViewById(R.id.textview); + final String text = "abc"; + + assertTrue(textView.isCursorVisible()); + + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(replaceText(text)); + setImeConsumesInputWithExpect(textView, true /* imeConsumesInput */, + false /* expectedCursorVisibility */); + final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(), + new String[]{"ABC"}, SuggestionSpan.FLAG_AUTO_CORRECTION); + setSuggestionSpan(suggestionSpan, text.indexOf('a'), text.indexOf('c') + 1); + showSuggestionsPopup(); + + assertSuggestionsPopupIsDisplayed(); + assertSuggestionsPopupContainsItem("ABC"); + assertFalse(textView.isCursorVisible()); + + // Delete an item. + clickSuggestionsPopupItem( + getActivity().getString(com.android.internal.R.string.delete)); + assertSuggestionsPopupIsNotDisplayed(); + assertFalse(textView.isCursorVisible()); + + // Set IME not consumes input, cursor should be back to visible. + setImeConsumesInputWithExpect(textView, false /* imeConsumesInput */, + true /* expectedCursorVisibility */); + } + + private void setImeConsumesInputWithExpect( + final TextView textView, boolean imeConsumesInput, boolean expectedCursorVisibility) { + textView.post(() -> textView.setImeConsumesInput(imeConsumesInput)); + getInstrumentation().waitForIdleSync(); + if (expectedCursorVisibility) { + assertTrue(textView.isCursorVisible()); + } else { + assertFalse(textView.isCursorVisible()); + } + } } diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java index 70888905d7c8..10ff3a47a7d9 100644 --- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java @@ -19,18 +19,22 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.os.BatteryConsumer; +import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.UidBatteryConsumer; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.power.MeasuredEnergyStats; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -81,6 +85,10 @@ public class CpuPowerCalculatorTest { public void setUp() { MockitoAnnotations.initMocks(this); + final boolean[] supportedPowerBuckets = + new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS]; + supportedPowerBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true; + mStatsRule.getBatteryStats() .setUserInfoProvider(mMockUserInfoProvider) .setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders) @@ -88,7 +96,8 @@ public class CpuPowerCalculatorTest { .setKernelCpuUidClusterTimeReader(mMockKernelCpuUidClusterTimeReader) .setKernelCpuUidUserSysTimeReader(mMockKernelCpuUidUserSysTimeReader) .setKernelCpuUidActiveTimeReader(mMockKerneCpuUidActiveTimeReader) - .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader); + .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader) + .initMeasuredEnergyStatsLocked(supportedPowerBuckets, 0); } @Test @@ -103,28 +112,28 @@ public class CpuPowerCalculatorTest { // User/System CPU time doAnswer(invocation -> { - final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0); + final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(1); // User/system time in microseconds callback.onUidCpuTime(APP_UID1, new long[]{1111000, 2222000}); callback.onUidCpuTime(APP_UID2, new long[]{3333000, 4444000}); return null; - }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(any()); + }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(anyBoolean(), any()); // Active CPU time doAnswer(invocation -> { - final KernelCpuUidTimeReader.Callback<Long> callback = invocation.getArgument(0); + final KernelCpuUidTimeReader.Callback<Long> callback = invocation.getArgument(1); callback.onUidCpuTime(APP_UID1, 1111L); callback.onUidCpuTime(APP_UID2, 3333L); return null; - }).when(mMockKerneCpuUidActiveTimeReader).readDelta(any()); + }).when(mMockKerneCpuUidActiveTimeReader).readDelta(anyBoolean(), any()); // Per-cluster CPU time doAnswer(invocation -> { - final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0); + final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(1); callback.onUidCpuTime(APP_UID1, new long[]{1111, 2222}); callback.onUidCpuTime(APP_UID2, new long[]{3333, 4444}); return null; - }).when(mMockKernelCpuUidClusterTimeReader).readDelta(any()); + }).when(mMockKernelCpuUidClusterTimeReader).readDelta(anyBoolean(), any()); mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null); @@ -134,7 +143,8 @@ public class CpuPowerCalculatorTest { CpuPowerCalculator calculator = new CpuPowerCalculator(mStatsRule.getPowerProfile()); - mStatsRule.apply(calculator); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(), + calculator); UidBatteryConsumer uidConsumer1 = mStatsRule.getUidBatteryConsumer(APP_UID1); assertThat(uidConsumer1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU)) @@ -150,4 +160,64 @@ public class CpuPowerCalculatorTest { .isWithin(PRECISION).of(2.672322); assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull(); } + + @Test + public void testMeasuredEnergyBasedModel() { + when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true); + + when(mMockKernelCpuSpeedReaders[0].readDelta()).thenReturn(new long[]{1000, 2000}); + when(mMockKernelCpuSpeedReaders[1].readDelta()).thenReturn(new long[]{3000, 4000}); + + when(mMockCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(false); + + // User/System CPU time + doAnswer(invocation -> { + final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(1); + // User/system time in microseconds + callback.onUidCpuTime(APP_UID1, new long[]{1111000, 2222000}); + callback.onUidCpuTime(APP_UID2, new long[]{3333000, 4444000}); + return null; + }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(anyBoolean(), any()); + + // Active CPU time + doAnswer(invocation -> { + final KernelCpuUidTimeReader.Callback<Long> callback = invocation.getArgument(1); + callback.onUidCpuTime(APP_UID1, 1111L); + callback.onUidCpuTime(APP_UID2, 3333L); + return null; + }).when(mMockKerneCpuUidActiveTimeReader).readDelta(anyBoolean(), any()); + + // Per-cluster CPU time + doAnswer(invocation -> { + final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(1); + callback.onUidCpuTime(APP_UID1, new long[]{1111, 2222}); + callback.onUidCpuTime(APP_UID2, new long[]{3333, 4444}); + return null; + }).when(mMockKernelCpuUidClusterTimeReader).readDelta(anyBoolean(), any()); + + final long[] clusterChargesUC = new long[]{13577531, 24688642}; + mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, clusterChargesUC); + + mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("foo").addCpuTimeLocked(4321, 1234); + mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("bar").addCpuTimeLocked(5432, 2345); + + CpuPowerCalculator calculator = + new CpuPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + UidBatteryConsumer uidConsumer1 = mStatsRule.getUidBatteryConsumer(APP_UID1); + assertThat(uidConsumer1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU)) + .isEqualTo(3333); + assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU)) + .isWithin(PRECISION).of(3.18877); + assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar"); + + UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2); + assertThat(uidConsumer2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU)) + .isEqualTo(7777); + assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU)) + .isWithin(PRECISION).of(7.44072); + assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull(); + } } diff --git a/core/tests/coretests/src/com/android/internal/view/OWNERS b/core/tests/coretests/src/com/android/internal/view/OWNERS new file mode 100644 index 000000000000..1dad10de5ac7 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/view/OWNERS @@ -0,0 +1,3 @@ +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTests.java b/core/tests/mockingcoretests/src/android/view/DisplayTests.java index 678f21fe1211..a036db224a54 100644 --- a/core/tests/mockingcoretests/src/android/view/DisplayTests.java +++ b/core/tests/mockingcoretests/src/android/view/DisplayTests.java @@ -73,6 +73,11 @@ public class DisplayTests { private static Rect sAppBoundsPortrait = buildAppBounds(LOGICAL_WIDTH, LOGICAL_HEIGHT); private static Rect sAppBoundsLandscape = buildAppBounds(LOGICAL_HEIGHT, LOGICAL_WIDTH); + // Bounds of the device. + private static Rect sDeviceBoundsPortrait = new Rect(0, 0, LOGICAL_WIDTH, LOGICAL_HEIGHT); + private static Rect sDeviceBoundsLandscape = new Rect(0, 0, LOGICAL_HEIGHT, LOGICAL_WIDTH); + + private StaticMockitoSession mMockitoSession; private DisplayManagerGlobal mDisplayManagerGlobal; @@ -278,29 +283,57 @@ public class DisplayTests { } @Test - public void testGetRealSize_resourcesPortraitSandboxed_matchesSandboxBounds() { + public void testGetRealSize_resourcesPortraitSandboxed_matchesAppSandboxBounds() { // GIVEN display is not rotated. setDisplayInfoPortrait(mDisplayInfo); // GIVEN app is letterboxed. - setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), - sAppBoundsPortrait); + setMaxBoundsSandboxed(mApplicationContext.getResources(), sAppBoundsPortrait); final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, mApplicationContext.getResources()); // THEN real size matches app bounds. - verifyRealSizeMatchesApp(display, sAppBoundsPortrait); + verifyRealSizeMatchesBounds(display, sAppBoundsPortrait); } @Test - public void testGetRealSize_resourcesLandscapeSandboxed_matchesSandboxBounds() { + public void testGetRealSize_resourcesPortraitSandboxed_matchesDisplayAreaSandboxBounds() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN max bounds reflect DisplayArea size, which is the same size as the display. + setMaxBoundsSandboxed(mApplicationContext.getResources(), sDeviceBoundsPortrait); + // GIVEN app bounds do not stretch to include the full DisplayArea. + mApplicationContext.getResources().getConfiguration().windowConfiguration + .setAppBounds(buildAppBounds(LOGICAL_WIDTH, LOGICAL_HEIGHT - 10)); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches max bounds for the DisplayArea. + verifyRealSizeMatchesBounds(display, sDeviceBoundsPortrait); + } + + @Test + public void testGetRealSize_resourcesLandscapeSandboxed_matchesAppSandboxBounds() { // GIVEN display is rotated. setDisplayInfoLandscape(mDisplayInfo); // GIVEN app is letterboxed. - setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), - sAppBoundsLandscape); + setMaxBoundsSandboxed(mApplicationContext.getResources(), sAppBoundsLandscape); final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, mApplicationContext.getResources()); // THEN real size matches app bounds. - verifyRealSizeMatchesApp(display, sAppBoundsLandscape); + verifyRealSizeMatchesBounds(display, sAppBoundsLandscape); + } + + @Test + public void testGetRealSize_resourcesLandscapeSandboxed_matchesDisplayAreaSandboxBounds() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + // GIVEN max bounds reflect DisplayArea size, which is the same size as the display. + setMaxBoundsSandboxed(mApplicationContext.getResources(), sDeviceBoundsLandscape); + // GIVEN app bounds do not stretch to include the full DisplayArea. + mApplicationContext.getResources().getConfiguration().windowConfiguration + .setAppBounds(buildAppBounds(LOGICAL_HEIGHT, LOGICAL_WIDTH - 10)); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches max bounds for the DisplayArea. + verifyRealSizeMatchesBounds(display, sDeviceBoundsLandscape); } @Test @@ -396,29 +429,57 @@ public class DisplayTests { } @Test - public void testGetRealMetrics_resourcesPortraitSandboxed_matchesSandboxBounds() { + public void testGetRealMetrics_resourcesPortraitSandboxed_matchesAppSandboxBounds() { // GIVEN display is not rotated. setDisplayInfoPortrait(mDisplayInfo); // GIVEN app is letterboxed. - setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), - sAppBoundsPortrait); + setMaxBoundsSandboxed(mApplicationContext.getResources(), sAppBoundsPortrait); final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, mApplicationContext.getResources()); // THEN real metrics matches app bounds. - verifyRealMetricsMatchesApp(display, sAppBoundsPortrait); + verifyRealMetricsMatchesBounds(display, sAppBoundsPortrait); + } + + @Test + public void testGetRealMetrics_resourcesPortraitSandboxed_matchesDisplayAreaSandboxBounds() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN max bounds reflect DisplayArea size, which is the same size as the display. + setMaxBoundsSandboxed(mApplicationContext.getResources(), sDeviceBoundsPortrait); + // GIVEN app bounds do not stretch to include the full DisplayArea. + mApplicationContext.getResources().getConfiguration().windowConfiguration + .setAppBounds(buildAppBounds(LOGICAL_WIDTH, LOGICAL_HEIGHT - 10)); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches max bounds for the DisplayArea. + verifyRealMetricsMatchesBounds(display, sDeviceBoundsPortrait); } @Test - public void testGetRealMetrics_resourcesLandscapeSandboxed_matchesSandboxBounds() { + public void testGetRealMetrics_resourcesLandscapeSandboxed_matchesAppSandboxBounds() { // GIVEN display is rotated. setDisplayInfoLandscape(mDisplayInfo); // GIVEN app is letterboxed. - setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), - sAppBoundsLandscape); + setMaxBoundsSandboxed(mApplicationContext.getResources(), sAppBoundsLandscape); final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, mApplicationContext.getResources()); // THEN real metrics matches app bounds. - verifyRealMetricsMatchesApp(display, sAppBoundsLandscape); + verifyRealMetricsMatchesBounds(display, sAppBoundsLandscape); + } + + @Test + public void testGetRealMetrics_resourcesLandscapeSandboxed_matchesDisplayAreaSandboxBounds() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + // GIVEN max bounds reflect DisplayArea size, which is the same size as the display. + setMaxBoundsSandboxed(mApplicationContext.getResources(), sDeviceBoundsLandscape); + // GIVEN app bounds do not stretch to include the full DisplayArea. + mApplicationContext.getResources().getConfiguration().windowConfiguration + .setAppBounds(buildAppBounds(LOGICAL_HEIGHT, LOGICAL_WIDTH - 10)); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches max bounds for the DisplayArea. + verifyRealMetricsMatchesBounds(display, sDeviceBoundsLandscape); } // Given rotated display dimensions, calculate the letterboxed app bounds. @@ -450,8 +511,8 @@ public class DisplayTests { * Set max bounds to be sandboxed to the app bounds, indicating the app is in * size compat mode or letterbox. */ - private static void setMaxBoundsSandboxedToMatchAppBounds(Resources resources, Rect appBounds) { - resources.getConfiguration().windowConfiguration.setMaxBounds(appBounds); + private static void setMaxBoundsSandboxed(Resources resources, Rect bounds) { + resources.getConfiguration().windowConfiguration.setMaxBounds(bounds); } /** @@ -492,17 +553,17 @@ public class DisplayTests { assertThat(metrics.heightPixels).isEqualTo(LOGICAL_HEIGHT); } - private static void verifyRealSizeMatchesApp(Display display, Rect appBounds) { + private static void verifyRealSizeMatchesBounds(Display display, Rect bounds) { Point size = new Point(); display.getRealSize(size); - assertThat(size).isEqualTo(new Point(appBounds.width(), appBounds.height())); + assertThat(size).isEqualTo(new Point(bounds.width(), bounds.height())); } - private static void verifyRealMetricsMatchesApp(Display display, Rect appBounds) { + private static void verifyRealMetricsMatchesBounds(Display display, Rect bounds) { DisplayMetrics metrics = new DisplayMetrics(); display.getRealMetrics(metrics); - assertThat(metrics.widthPixels).isEqualTo(appBounds.width()); - assertThat(metrics.heightPixels).isEqualTo(appBounds.height()); + assertThat(metrics.widthPixels).isEqualTo(bounds.width()); + assertThat(metrics.heightPixels).isEqualTo(bounds.height()); } private static FixedRotationAdjustments setOverrideFixedRotationAdjustments( diff --git a/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java b/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java index c01bb75c32aa..e41805dd3a59 100644 --- a/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java +++ b/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java @@ -22,7 +22,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.os.PersistableBundle; import android.os.RemoteException; @@ -32,6 +31,7 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import java.util.concurrent.Executor; @@ -42,51 +42,23 @@ import java.util.concurrent.Executor; @RunWith(AndroidJUnit4.class) public class RangingManagerTest { - private static final IUwbAdapter ADAPTER = mock(IUwbAdapter.class); private static final Executor EXECUTOR = UwbTestUtils.getExecutor(); private static final PersistableBundle PARAMS = new PersistableBundle(); private static final @RangingChangeReason int REASON = RangingChangeReason.UNKNOWN; @Test public void testOpenSession_OpenRangingInvoked() throws RemoteException { - RangingManager rangingManager = new RangingManager(ADAPTER); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingManager rangingManager = new RangingManager(adapter); RangingSession.Callback callback = mock(RangingSession.Callback.class); rangingManager.openSession(PARAMS, EXECUTOR, callback); - verify(ADAPTER, times(1)).openRanging(eq(rangingManager), eq(PARAMS)); - } - - @Test - public void testOpenSession_ErrorIfSameSessionHandleReturned() throws RemoteException { - RangingManager rangingManager = new RangingManager(ADAPTER); - RangingSession.Callback callback = mock(RangingSession.Callback.class); - SessionHandle handle = new SessionHandle(1); - when(ADAPTER.openRanging(any(), any())).thenReturn(handle); - - rangingManager.openSession(PARAMS, EXECUTOR, callback); - - // Calling openSession will cause the same session handle to be returned. The onClosed - // callback should be invoked - RangingSession.Callback callback2 = mock(RangingSession.Callback.class); - rangingManager.openSession(PARAMS, EXECUTOR, callback2); - verify(callback, times(0)).onClosed(anyInt(), any()); - verify(callback2, times(1)).onClosed(anyInt(), any()); - } - - @Test - public void testOnRangingOpened_ValidSessionHandle() throws RemoteException { - RangingManager rangingManager = new RangingManager(ADAPTER); - RangingSession.Callback callback = mock(RangingSession.Callback.class); - SessionHandle handle = new SessionHandle(1); - when(ADAPTER.openRanging(any(), any())).thenReturn(handle); - - rangingManager.openSession(PARAMS, EXECUTOR, callback); - rangingManager.onRangingOpened(handle); - verify(callback, times(1)).onOpened(any()); + verify(adapter, times(1)).openRanging(any(), eq(rangingManager), eq(PARAMS)); } @Test public void testOnRangingOpened_InvalidSessionHandle() throws RemoteException { - RangingManager rangingManager = new RangingManager(ADAPTER); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingManager rangingManager = new RangingManager(adapter); RangingSession.Callback callback = mock(RangingSession.Callback.class); rangingManager.onRangingOpened(new SessionHandle(2)); @@ -95,18 +67,20 @@ public class RangingManagerTest { @Test public void testOnRangingOpened_MultipleSessionsRegistered() throws RemoteException { - SessionHandle sessionHandle1 = new SessionHandle(1); - SessionHandle sessionHandle2 = new SessionHandle(2); + IUwbAdapter adapter = mock(IUwbAdapter.class); RangingSession.Callback callback1 = mock(RangingSession.Callback.class); RangingSession.Callback callback2 = mock(RangingSession.Callback.class); + ArgumentCaptor<SessionHandle> sessionHandleCaptor = + ArgumentCaptor.forClass(SessionHandle.class); - when(ADAPTER.openRanging(any(), any())) - .thenReturn(sessionHandle1) - .thenReturn(sessionHandle2); - - RangingManager rangingManager = new RangingManager(ADAPTER); + RangingManager rangingManager = new RangingManager(adapter); rangingManager.openSession(PARAMS, EXECUTOR, callback1); + verify(adapter, times(1)).openRanging(sessionHandleCaptor.capture(), any(), any()); + SessionHandle sessionHandle1 = sessionHandleCaptor.getValue(); + rangingManager.openSession(PARAMS, EXECUTOR, callback2); + verify(adapter, times(2)).openRanging(sessionHandleCaptor.capture(), any(), any()); + SessionHandle sessionHandle2 = sessionHandleCaptor.getValue(); rangingManager.onRangingOpened(sessionHandle1); verify(callback1, times(1)).onOpened(any()); @@ -119,12 +93,17 @@ public class RangingManagerTest { @Test public void testCorrectCallbackInvoked() throws RemoteException { - RangingManager rangingManager = new RangingManager(ADAPTER); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingManager rangingManager = new RangingManager(adapter); RangingSession.Callback callback = mock(RangingSession.Callback.class); - SessionHandle handle = new SessionHandle(1); - when(ADAPTER.openRanging(any(), any())).thenReturn(handle); + + ArgumentCaptor<SessionHandle> sessionHandleCaptor = + ArgumentCaptor.forClass(SessionHandle.class); rangingManager.openSession(PARAMS, EXECUTOR, callback); + verify(adapter, times(1)).openRanging(sessionHandleCaptor.capture(), any(), any()); + SessionHandle handle = sessionHandleCaptor.getValue(); + rangingManager.onRangingOpened(handle); verify(callback, times(1)).onOpened(any()); @@ -156,20 +135,23 @@ public class RangingManagerTest { @Test public void testOnRangingClosed_MultipleSessionsRegistered() throws RemoteException { + IUwbAdapter adapter = mock(IUwbAdapter.class); // Verify that if multiple sessions are registered, only the session that is // requested to close receives the associated callbacks - SessionHandle sessionHandle1 = new SessionHandle(1); - SessionHandle sessionHandle2 = new SessionHandle(2); RangingSession.Callback callback1 = mock(RangingSession.Callback.class); RangingSession.Callback callback2 = mock(RangingSession.Callback.class); - when(ADAPTER.openRanging(any(), any())) - .thenReturn(sessionHandle1) - .thenReturn(sessionHandle2); + RangingManager rangingManager = new RangingManager(adapter); + ArgumentCaptor<SessionHandle> sessionHandleCaptor = + ArgumentCaptor.forClass(SessionHandle.class); - RangingManager rangingManager = new RangingManager(ADAPTER); rangingManager.openSession(PARAMS, EXECUTOR, callback1); + verify(adapter, times(1)).openRanging(sessionHandleCaptor.capture(), any(), any()); + SessionHandle sessionHandle1 = sessionHandleCaptor.getValue(); + rangingManager.openSession(PARAMS, EXECUTOR, callback2); + verify(adapter, times(2)).openRanging(sessionHandleCaptor.capture(), any(), any()); + SessionHandle sessionHandle2 = sessionHandleCaptor.getValue(); rangingManager.onRangingClosed(sessionHandle1, REASON, PARAMS); verify(callback1, times(1)).onClosed(anyInt(), any()); @@ -182,19 +164,22 @@ public class RangingManagerTest { @Test public void testOnRangingReport_MultipleSessionsRegistered() throws RemoteException { - SessionHandle sessionHandle1 = new SessionHandle(1); - SessionHandle sessionHandle2 = new SessionHandle(2); + IUwbAdapter adapter = mock(IUwbAdapter.class); RangingSession.Callback callback1 = mock(RangingSession.Callback.class); RangingSession.Callback callback2 = mock(RangingSession.Callback.class); - when(ADAPTER.openRanging(any(), any())) - .thenReturn(sessionHandle1) - .thenReturn(sessionHandle2); + ArgumentCaptor<SessionHandle> sessionHandleCaptor = + ArgumentCaptor.forClass(SessionHandle.class); - RangingManager rangingManager = new RangingManager(ADAPTER); + RangingManager rangingManager = new RangingManager(adapter); rangingManager.openSession(PARAMS, EXECUTOR, callback1); + verify(adapter, times(1)).openRanging(sessionHandleCaptor.capture(), any(), any()); + SessionHandle sessionHandle1 = sessionHandleCaptor.getValue(); + rangingManager.onRangingStarted(sessionHandle1, PARAMS); rangingManager.openSession(PARAMS, EXECUTOR, callback2); + verify(adapter, times(2)).openRanging(sessionHandleCaptor.capture(), any(), any()); + SessionHandle sessionHandle2 = sessionHandleCaptor.getValue(); rangingManager.onRangingStarted(sessionHandle2, PARAMS); rangingManager.onRangingResult(sessionHandle1, UwbTestUtils.getRangingReports(1)); @@ -232,17 +217,24 @@ public class RangingManagerTest { private void runReason(@RangingChangeReason int reasonIn, @RangingSession.Callback.Reason int reasonOut) throws RemoteException { - RangingManager rangingManager = new RangingManager(ADAPTER); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingManager rangingManager = new RangingManager(adapter); RangingSession.Callback callback = mock(RangingSession.Callback.class); - SessionHandle handle = new SessionHandle(1); - when(ADAPTER.openRanging(any(), any())).thenReturn(handle); + + ArgumentCaptor<SessionHandle> sessionHandleCaptor = + ArgumentCaptor.forClass(SessionHandle.class); + rangingManager.openSession(PARAMS, EXECUTOR, callback); + verify(adapter, times(1)).openRanging(sessionHandleCaptor.capture(), any(), any()); + SessionHandle handle = sessionHandleCaptor.getValue(); rangingManager.onRangingOpenFailed(handle, reasonIn, PARAMS); verify(callback, times(1)).onOpenFailed(eq(reasonOut), eq(PARAMS)); // Open a new session rangingManager.openSession(PARAMS, EXECUTOR, callback); + verify(adapter, times(2)).openRanging(sessionHandleCaptor.capture(), any(), any()); + handle = sessionHandleCaptor.getValue(); rangingManager.onRangingOpened(handle); rangingManager.onRangingStartFailed(handle, reasonIn, PARAMS); diff --git a/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java b/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java index 8e7f7c562ade..75c6924a1939 100644 --- a/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java +++ b/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java @@ -16,34 +16,23 @@ package android.uwb; -import android.content.Context; -import android.content.pm.PackageManager; import android.os.SystemClock; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.Executor; public class UwbTestUtils { private UwbTestUtils() {} - public static boolean isUwbSupported(Context context) { - PackageManager packageManager = context.getPackageManager(); - return packageManager.hasSystemFeature(PackageManager.FEATURE_UWB); - } - public static AngleMeasurement getAngleMeasurement() { - return new AngleMeasurement.Builder() - .setRadians(getDoubleInRange(-Math.PI, Math.PI)) - .setErrorRadians(getDoubleInRange(0, Math.PI)) - .setConfidenceLevel(getDoubleInRange(0, 1)) - .build(); + return new AngleMeasurement( + getDoubleInRange(-Math.PI, Math.PI), + getDoubleInRange(0, Math.PI), + getDoubleInRange(0, 1)); } public static AngleOfArrivalMeasurement getAngleOfArrivalMeasurement() { - return new AngleOfArrivalMeasurement.Builder() + return new AngleOfArrivalMeasurement.Builder(getAngleMeasurement()) .setAltitude(getAngleMeasurement()) - .setAzimuth(getAngleMeasurement()) .build(); } @@ -69,14 +58,6 @@ public class UwbTestUtils { .build(); } - public static List<RangingMeasurement> getRangingMeasurements(int num) { - List<RangingMeasurement> result = new ArrayList<>(); - for (int i = 0; i < num; i++) { - result.add(getRangingMeasurement()); - } - return result; - } - public static RangingReport getRangingReports(int numMeasurements) { RangingReport.Builder builder = new RangingReport.Builder(); for (int i = 0; i < numMeasurements; i++) { diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 77a38a9bb1a0..b3a180d70fe2 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -154,6 +154,7 @@ <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" /> <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="media" /> <assign-permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" uid="media" /> + <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="media" /> <assign-permission name="android.permission.INTERNET" uid="media" /> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index c49fe8563dab..a7b6636a15de 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -488,6 +488,8 @@ applications that come with the platform <permission name="android.permission.MANAGE_UI_TRANSLATION" /> <!-- Permission required for CTS test - ClipboardManagerTest --> <permission name="android.permission.SET_CLIP_SOURCE" /> + <!-- Permission required for CTS test - FontManagerTest --> + <permission name="android.permission.UPDATE_FONTS" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 43d56626ed1a..4a3bd99b8f7c 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -307,6 +307,12 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1777196134": { + "message": "goodToGo(): No apps to animate, mPendingAnimations=%d", + "level": "DEBUG", + "group": "WM_DEBUG_REMOTE_ANIMATIONS", + "at": "com\/android\/server\/wm\/RemoteAnimationController.java" + }, "-1770075711": { "message": "Adding window client %s that is dead, aborting.", "level": "WARN", @@ -1987,12 +1993,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "194124419": { - "message": "goodToGo(): Animation finished already, canceled=%s mPendingAnimations=%d", - "level": "DEBUG", - "group": "WM_DEBUG_REMOTE_ANIMATIONS", - "at": "com\/android\/server\/wm\/RemoteAnimationController.java" - }, "200829729": { "message": "ScreenRotationAnimation onAnimationEnd", "level": "DEBUG", @@ -2089,6 +2089,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DragState.java" }, + "269976641": { + "message": "goodToGo(): Animation canceled already", + "level": "DEBUG", + "group": "WM_DEBUG_REMOTE_ANIMATIONS", + "at": "com\/android\/server\/wm\/RemoteAnimationController.java" + }, "274773837": { "message": "applyAnimation: anim=%s nextAppTransition=ANIM_CLIP_REVEAL transit=%s Callers=%s", "level": "VERBOSE", diff --git a/data/keyboards/Vendor_0957_Product_0001.idc b/data/keyboards/Vendor_0957_Product_0001.idc index e1f4346369f3..39479ce725e1 100644 --- a/data/keyboards/Vendor_0957_Product_0001.idc +++ b/data/keyboards/Vendor_0957_Product_0001.idc @@ -19,5 +19,4 @@ # Basic Parameters keyboard.layout = Vendor_0957_Product_0001 -keyboard.characterMap = Vendor_0957_Product_0001 audio.mic = 1
\ No newline at end of file diff --git a/data/keyboards/Vendor_248a_Product_8266.idc b/data/keyboards/Vendor_248a_Product_8266.idc new file mode 100644 index 000000000000..3021655c286e --- /dev/null +++ b/data/keyboards/Vendor_248a_Product_8266.idc @@ -0,0 +1,24 @@ +# 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. + +# +# Input Device Configuration file for Google Reference RCU Remote. +# +# + +# Basic Parameters +# Due to a memory error on early prototypes of the reference remote control +# the VID/PID is mapped to 248a/8266 instead of 0957/0001 +keyboard.layout = Vendor_0957_Product_0001 +audio.mic = 1
\ No newline at end of file diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index c80788269c24..2a6bbf36ef76 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -25,6 +25,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.annotation.UiThread; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; @@ -169,6 +170,21 @@ public class Typeface { Collections.emptyMap(); /** + * Returns the shared memory that used for creating Typefaces. + * + * @return A SharedMemory used for creating Typeface. Maybe null if the lazy initialization is + * disabled or inside SystemServer or Zygote. + * @hide + */ + @TestApi + public static @Nullable SharedMemory getSystemFontMapSharedMemory() { + if (ENABLE_LAZY_TYPEFACE_INITIALIZATION) { + Objects.requireNonNull(sSystemFontMapSharedMemory); + } + return sSystemFontMapSharedMemory; + } + + /** * @hide */ @UnsupportedAppUsage @@ -1196,8 +1212,13 @@ public class Typeface { } } - /** @hide */ - public static SharedMemory serializeFontMap(Map<String, Typeface> fontMap) + /** + * Create a serialized system font mappings. + * + * @hide + */ + @TestApi + public static @NonNull SharedMemory serializeFontMap(@NonNull Map<String, Typeface> fontMap) throws IOException, ErrnoException { long[] nativePtrs = new long[fontMap.size()]; // The name table will not be large, so let's create a byte array in memory. @@ -1229,9 +1250,14 @@ public class Typeface { } // buffer's byte order should be BIG_ENDIAN. - /** @hide */ - @VisibleForTesting - public static Map<String, Typeface> deserializeFontMap(ByteBuffer buffer) throws IOException { + /** + * Deserialize the font mapping from the serialized byte buffer. + * + * @hide + */ + @TestApi + public static @NonNull Map<String, Typeface> deserializeFontMap(@NonNull ByteBuffer buffer) + throws IOException { Map<String, Typeface> fontMap = new ArrayMap<>(); int typefacesBytesCount = buffer.getInt(); long[] nativePtrs = nativeReadTypefaces(buffer.slice()); diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 7fd3cdc39cc7..d6bbee90d73b 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -110,17 +110,6 @@ import java.util.Arrays; * </pre> * * @attr ref android.R.styleable#RippleDrawable_color - * - * To change the ripple style, assign the value of "solid" or "patterned" to the android:rippleStyle - * attribute. - * - * <pre> - * <code><!-- A red ripple masked against an opaque rectangle. --/> - * <ripple android:rippleStyle="patterned"> - * </ripple></code> - * </pre> - * - * @attr ref android.R.styleable#RippleDrawable_rippleStyle */ public class RippleDrawable extends LayerDrawable { /** @@ -132,12 +121,14 @@ public class RippleDrawable extends LayerDrawable { /** * Ripple style where a solid circle is drawn. This is also the default style * @see #setRippleStyle(int) + * @hide */ public static final int STYLE_SOLID = 0; /** * Ripple style where a circle shape with a patterned, * noisy interior expands from the hotspot to the bounds". * @see #setRippleStyle(int) + * @hide */ public static final int STYLE_PATTERNED = 1; @@ -1248,6 +1239,7 @@ public class RippleDrawable extends LayerDrawable { * @see #STYLE_PATTERNED * * @param style The style of the ripple + * @hide */ public void setRippleStyle(@RippleStyle int style) throws IllegalArgumentException { if (style == STYLE_SOLID || style == STYLE_PATTERNED) { @@ -1260,6 +1252,7 @@ public class RippleDrawable extends LayerDrawable { /** * Get the current ripple style * @return Ripple style + * @hide */ public @RippleStyle int getRippleStyle() { return mState.mRippleStyle; diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java index 25492779e6e9..657a32c1ac46 100644 --- a/graphics/java/android/graphics/drawable/RippleShader.java +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -61,53 +61,6 @@ final class RippleShader extends RuntimeShader { + " return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);\n" + "}\n" + "\n" - + "float softRing(vec2 uv, vec2 xy, float radius, float blur) {\n" - + " float thickness = 0.4;\n" - + " float circle_outer = softCircle(uv, xy, radius + thickness * 0.5, blur);\n" - + " float circle_inner = softCircle(uv, xy, radius - thickness * 0.5, blur);\n" - + " return circle_outer - circle_inner;\n" - + "}\n" - + "\n" - + "struct Viewport {\n" - + " float aspect;\n" - + " vec2 uv;\n" - + " vec2 resolution_pixels;\n" - + "};\n" - + "\n" - + "Viewport getViewport(vec2 frag_coord, vec2 resolution_pixels) {\n" - + " Viewport v;\n" - + " v.aspect = resolution_pixels.y / resolution_pixels.x;\n" - + " v.uv = frag_coord / resolution_pixels;\n" - + " v.uv.y = (1.0 - v.uv.y) * v.aspect;\n" - + " v.resolution_pixels = resolution_pixels;\n" - + " return v;\n" - + "}\n" - + "\n" - + "vec2 getTouch(vec2 touch_position_pixels, Viewport viewport) {\n" - + " vec2 touch = touch_position_pixels / viewport.resolution_pixels;\n" - + " touch.y *= viewport.aspect;\n" - + " return touch;\n" - + "}\n" - + "\n" - + "struct Wave {\n" - + " float ring;\n" - + " float circle;\n" - + "};\n" - + "\n" - + "Wave getWave(Viewport viewport, vec2 touch, float progress) {\n" - + " float fade = pow((clamp(progress, 0.8, 1.0)), 8.);\n" - + " Wave w;\n" - + " w.ring = max(softRing(viewport.uv, touch, progress, 0.45) - fade, 0.);\n" - + " w.circle = softCircle(viewport.uv, touch, 2.0 * progress, 0.2) - progress;\n" - + " return w;\n" - + "}\n" - + "\n" - + "vec4 getRipple(vec4 color, float loudness, float sparkle, Wave wave) {\n" - + " float alpha = wave.ring * sparkle * loudness\n" - + " + wave.circle * color.a;\n" - + " return vec4(color.rgb, saturate(alpha));\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" @@ -126,19 +79,15 @@ final class RippleShader extends RuntimeShader { + " float fadeIn = subProgress(0., 0.175, in_progress);\n" + " float fadeOutNoise = subProgress(0.375, 1., in_progress);\n" + " float fadeOutRipple = subProgress(0.375, 0.75, in_progress);\n" - + " Viewport vp = getViewport(p, in_resolution);\n" - + " vec2 touch = getTouch(in_origin, vp);\n" - + " Wave w = getWave(vp, touch, in_progress * 0.25);\n" + " float ring = getRingMask(p, in_origin, in_maxRadius, fadeIn);\n" + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n" + " float sparkle = sparkles(p, in_progress * 0.25 + in_secondsOffset)\n" + " * ring * alpha;\n" - + " vec4 r = getRipple(in_color, 1., sparkle, w);\n" + " float fade = min(fadeIn, 1.-fadeOutRipple);\n" - + " vec4 circle = vec4(in_color.rgb, softCircle(p, in_origin, in_maxRadius " - + " * fadeIn, 0.2) * fade * in_color.a);\n" + + " vec4 circle = in_color * (softCircle(p, in_origin, in_maxRadius " + + " * fadeIn, 0.2) * fade);\n" + " float mask = in_hasMask == 1. ? sample(in_shader).a > 0. ? 1. : 0. : 1.;\n" - + " return mix(circle, vec4(1.), sparkle * mask);\n" + + " return mix(circle, vec4(sparkle), sparkle) * mask;\n" + "}"; private static final String SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN; diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java index af49619fb840..917eef2ffede 100644 --- a/graphics/java/android/graphics/fonts/FontFileUtil.java +++ b/graphics/java/android/graphics/fonts/FontFileUtil.java @@ -183,6 +183,23 @@ public class FontFileUtil { return nIsPostScriptType1Font(buffer, index); } + /** + * Analyze the file content and returns 1 if the font file is an OpenType collection file, 0 if + * the font file is a OpenType font file, -1 otherwise. + */ + public static int isCollectionFont(@NonNull ByteBuffer buffer) { + ByteBuffer copied = buffer.slice(); + copied.order(ByteOrder.BIG_ENDIAN); + int magicNumber = copied.getInt(0); + if (magicNumber == TTC_TAG) { + return 1; + } else if (magicNumber == SFNT_VERSION_1 || magicNumber == SFNT_VERSION_OTTO) { + return 0; + } else { + return -1; + } + } + @FastNative private static native long nGetFontRevision(@NonNull ByteBuffer buffer, @IntRange(from = 0) int index); diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index ed789f03f9ba..35b1c169f283 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.ServiceManager; import android.os.ServiceSpecificException; -import android.security.usermanager.IKeystoreUserManager; +import android.security.maintenance.IKeystoreMaintenance; import android.system.keystore2.Domain; import android.system.keystore2.ResponseCode; import android.util.Log; @@ -34,9 +34,9 @@ public class AndroidKeyStoreMaintenance { public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR; - private static IKeystoreUserManager getService() { - return IKeystoreUserManager.Stub.asInterface( - ServiceManager.checkService("android.security.usermanager")); + private static IKeystoreMaintenance getService() { + return IKeystoreMaintenance.Stub.asInterface( + ServiceManager.checkService("android.security.maintenance")); } /** @@ -121,4 +121,22 @@ public class AndroidKeyStoreMaintenance { return SYSTEM_ERROR; } } + + /** + * Queries user state from Keystore 2.0. + * + * @param userId - Android user id of the user. + * @return UserState enum variant as integer if successful or an error + */ + public static int getState(int userId) { + try { + return getService().getState(userId); + } catch (ServiceSpecificException e) { + Log.e(TAG, "getState failed", e); + return e.errorCode; + } catch (Exception e) { + Log.e(TAG, "Can not connect to keystore", e); + return SYSTEM_ERROR; + } + } } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 93658e69eac8..937f01ce3767 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -43,6 +43,7 @@ import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import android.security.keystore.KeystoreResponse; import android.security.keystore.UserNotAuthenticatedException; +import android.security.maintenance.UserState; import android.system.keystore2.Domain; import android.util.Log; @@ -196,6 +197,19 @@ public class KeyStore { public State state(int userId) { final int ret; try { + if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) { + int userState = AndroidKeyStoreMaintenance.getState(userId); + switch (userState) { + case UserState.UNINITIALIZED: + return KeyStore.State.UNINITIALIZED; + case UserState.LSKF_UNLOCKED: + return KeyStore.State.UNLOCKED; + case UserState.LSKF_LOCKED: + return KeyStore.State.LOCKED; + default: + throw new AssertionError(KeyStore.VALUE_CORRUPTED); + } + } ret = mBinder.getState(userId); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java index f48da74eb397..cd77d9c2723f 100644 --- a/keystore/java/android/security/keystore/AttestationUtils.java +++ b/keystore/java/android/security/keystore/AttestationUtils.java @@ -22,7 +22,6 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.Context; import android.os.Build; -import android.security.KeyStore; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterCertificateChain; import android.security.keymaster.KeymasterDefs; @@ -32,9 +31,14 @@ import android.util.ArraySet; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.SecureRandom; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.spec.ECGenParameterSpec; import java.util.Collection; +import java.util.Random; import java.util.Set; /** @@ -223,22 +227,47 @@ public abstract class AttestationUtils { @NonNull public static X509Certificate[] attestDeviceIds(Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws DeviceIdAttestationException { - final KeymasterArguments attestArgs = prepareAttestationArgumentsForDeviceId( - context, idTypes, attestationChallenge); - - // Perform attestation. - final KeymasterCertificateChain outChain = new KeymasterCertificateChain(); - final int errorCode = KeyStore.getInstance().attestDeviceIds(attestArgs, outChain); - if (errorCode != KeyStore.NO_ERROR) { - throw new DeviceIdAttestationException("Unable to perform attestation", - KeyStore.getKeyStoreException(errorCode)); + String keystoreAlias = generateRandomAlias(); + KeyGenParameterSpec.Builder builder = + new KeyGenParameterSpec.Builder(keystoreAlias, KeyProperties.PURPOSE_SIGN) + .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) + .setDigests(KeyProperties.DIGEST_SHA256) + .setAttestationChallenge(attestationChallenge); + + if (idTypes != null) { + builder.setAttestationIds(idTypes); + builder.setDevicePropertiesAttestationIncluded(true); } try { - return parseCertificateChain(outChain); - } catch (KeyAttestationException e) { - throw new DeviceIdAttestationException(e.getMessage(), e); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); + keyPairGenerator.initialize(builder.build()); + keyPairGenerator.generateKeyPair(); + + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + + X509Certificate[] certificateChain = + (X509Certificate[]) keyStore.getCertificateChain(keystoreAlias); + + keyStore.deleteEntry(keystoreAlias); + + return certificateChain; + } catch (Exception e) { + throw new DeviceIdAttestationException("Unable to perform attestation", e); + } + } + + private static String generateRandomAlias() { + Random random = new SecureRandom(); + StringBuilder builder = new StringBuilder(); + // Pick random uppercase letters, A-Z. 20 of them gives us ~94 bits of entropy, which + // should prevent any conflicts with app-selected aliases, even for very unlucky users. + for (int i = 0; i < 20; ++i) { + builder.append(random.nextInt(26) + 'A'); } + return builder.toString(); } /** diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 72735a787b7f..5cb2c3b41517 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -467,8 +467,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * * @return The numeric namespace as configured in the keystore2_key_contexts files of Android's * SEPolicy. - * TODO b/171806779 link to public Keystore 2.0 documentation. - * See bug for more details for now. + * See <a href="https://source.android.com/security/keystore#access-control"> + * Keystore 2.0 access control</a> * @hide */ @SystemApi @@ -1042,9 +1042,9 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * keys between system and vendor components, e.g., WIFI settings and WPA supplicant. * * @param namespace Numeric SELinux namespace as configured in keystore2_key_contexts - * of Android's SEPolicy. - * TODO b/171806779 link to public Keystore 2.0 documentation. - * See bug for more details for now. + * of Android's SEPolicy. + * See <a href="https://source.android.com/security/keystore#access-control"> + * Keystore 2.0 access control</a> * @return this Builder object. * * @hide diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java index d36695b9b410..fa852e33a1d8 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -340,11 +340,11 @@ public class AndroidKeyStoreProvider extends Provider { * @param keyStore The keystore2 backend. * @param alias The alias of the key in the Keystore database. * @param namespace The a Keystore namespace. This is used by system api only to request - * Android system specific keystore namespace, which can be configured - * in the device's SEPolicy. Third party apps and most system components - * set this parameter to -1 to indicate their application specific namespace. - * TODO b/171806779 link to public Keystore 2.0 documentation. - * See bug for more details for now. + * Android system specific keystore namespace, which can be configured + * in the device's SEPolicy. Third party apps and most system components + * set this parameter to -1 to indicate their application specific namespace. + * See <a href="https://source.android.com/security/keystore#access-control"> + * Keystore 2.0 access control</a> * @hide **/ @NonNull diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 1b5dc8bdbcaa..3f03302de474 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -38,6 +38,14 @@ filegroup { path: "src", } +filegroup { + name: "wm_shell-aidls", + srcs: [ + "src/**/*.aidl", + ], + path: "src", +} + // TODO(b/168581922) protologtool do not support kotlin(*.kt) filegroup { name: "wm_shell-sources-kt", @@ -98,7 +106,7 @@ android_library { ":wm_shell_protolog_src", // TODO(b/168581922) protologtool do not support kotlin(*.kt) ":wm_shell-sources-kt", - "src/**/I*.aidl", + ":wm_shell-aidls", ], resource_dirs: [ "res", diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index c0bc73dcbd47..d2b3cf6a4fe2 100644 --- a/libs/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml @@ -18,4 +18,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.wm.shell"> <uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" /> + <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> </manifest> diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 24198659e15d..c2f591b9d7af 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -51,4 +51,10 @@ <!-- maximum animation duration for the icon when entering the starting window --> <integer name="max_starting_window_intro_icon_anim_duration">1000</integer> + + <!-- Animation duration when exit starting window: icon going away --> + <integer name="starting_window_icon_exit_anim_duration">166</integer> + + <!-- Animation duration when exit starting window: reveal app --> + <integer name="starting_window_app_reveal_anim_duration">333</integer> </resources> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 75bed3777a9d..3ced8d3ec6e7 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -188,4 +188,13 @@ <!-- The height of the brand image on staring surface. --> <dimen name="starting_surface_brand_image_height">80dp</dimen> + + <!-- The length of the shift of main window when exit starting window. --> + <dimen name="starting_surface_exit_animation_window_shift_length">20dp</dimen> + + <!-- The distance of the shift icon when normal exit starting window. --> + <dimen name="starting_surface_normal_exit_icon_distance">120dp</dimen> + + <!-- The distance of the shift icon when early exit starting window. --> + <dimen name="starting_surface_early_exit_icon_distance">32dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java index eaed24d6195a..d451f4a0661b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -47,21 +47,7 @@ public final class ShellCommandHandlerImpl { private final ShellExecutor mMainExecutor; private final HandlerImpl mImpl = new HandlerImpl(); - public static ShellCommandHandler create( - ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreenController> legacySplitScreenOptional, - Optional<SplitScreenController> splitScreenOptional, - Optional<Pip> pipOptional, - Optional<OneHandedController> oneHandedOptional, - Optional<HideDisplayCutoutController> hideDisplayCutout, - Optional<AppPairsController> appPairsOptional, - ShellExecutor mainExecutor) { - return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional, - splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout, - appPairsOptional, mainExecutor).mImpl; - } - - private ShellCommandHandlerImpl( + public ShellCommandHandlerImpl( ShellTaskOrganizer shellTaskOrganizer, Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, @@ -80,6 +66,10 @@ public final class ShellCommandHandlerImpl { mMainExecutor = mainExecutor; } + public ShellCommandHandler asShellCommandHandler() { + return mImpl; + } + /** Dumps WM Shell internal state. */ private void dump(PrintWriter pw) { mShellTaskOrganizer.dump(pw, ""); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index 85bd24c1c2bf..6f4550c2a89e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -26,7 +26,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.splitscreen.SplitScreenController; -import com.android.wm.shell.startingsurface.StartingSurface; +import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.transition.Transitions; import java.util.Optional; @@ -47,44 +47,20 @@ public class ShellInitImpl { private final FullscreenTaskListener mFullscreenTaskListener; private final ShellExecutor mMainExecutor; private final Transitions mTransitions; - private final Optional<StartingSurface> mStartingSurfaceOptional; + private final StartingWindowController mStartingWindow; private final InitImpl mImpl = new InitImpl(); - public static ShellInit create(DisplayImeController displayImeController, + public ShellInitImpl(DisplayImeController displayImeController, DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, - Optional<StartingSurface> startingSurfaceOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional, - FullscreenTaskListener fullscreenTaskListener, - Transitions transitions, - ShellExecutor mainExecutor) { - return new ShellInitImpl(displayImeController, - dragAndDropController, - shellTaskOrganizer, - legacySplitScreenOptional, - splitScreenOptional, - appPairsOptional, - startingSurfaceOptional, - pipTouchHandlerOptional, - fullscreenTaskListener, - transitions, - mainExecutor).mImpl; - } - - private ShellInitImpl(DisplayImeController displayImeController, - DragAndDropController dragAndDropController, - ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreenController> legacySplitScreenOptional, - Optional<SplitScreenController> splitScreenOptional, - Optional<AppPairsController> appPairsOptional, - Optional<StartingSurface> startingSurfaceOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, + StartingWindowController startingWindow, ShellExecutor mainExecutor) { mDisplayImeController = displayImeController; mDragAndDropController = dragAndDropController; @@ -96,17 +72,21 @@ public class ShellInitImpl { mPipTouchHandlerOptional = pipTouchHandlerOptional; mTransitions = transitions; mMainExecutor = mainExecutor; - mStartingSurfaceOptional = startingSurfaceOptional; + mStartingWindow = startingWindow; + } + + public ShellInit asShellInit() { + return mImpl; } private void init() { // Start listening for display changes mDisplayImeController.startMonitorDisplays(); + // Setup the shell organizer mShellTaskOrganizer.addListenerForType( mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN); - mStartingSurfaceOptional.ifPresent(mShellTaskOrganizer::initStartingSurface); - // Register the shell organizer + mShellTaskOrganizer.initStartingWindow(mStartingWindow); mShellTaskOrganizer.registerOrganizer(); mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index cb04bd7ce02b..94d13eab4299 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -31,6 +31,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; import android.content.Context; import android.content.LocusId; +import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.util.ArrayMap; @@ -47,7 +48,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.sizecompatui.SizeCompatUIController; -import com.android.wm.shell.startingsurface.StartingSurface; +import com.android.wm.shell.startingsurface.StartingWindowController; import java.io.PrintWriter; import java.util.ArrayList; @@ -132,7 +133,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>(); private final Object mLock = new Object(); - private StartingSurface mStartingSurface; + private StartingWindowController mStartingWindow; /** * In charge of showing size compat UI. Can be {@code null} if device doesn't support size @@ -183,8 +184,8 @@ public class ShellTaskOrganizer extends TaskOrganizer { /** * @hide */ - public void initStartingSurface(StartingSurface startingSurface) { - mStartingSurface = startingSurface; + public void initStartingWindow(StartingWindowController startingWindow) { + mStartingWindow = startingWindow; } /** @@ -301,22 +302,23 @@ public class ShellTaskOrganizer extends TaskOrganizer { @Override public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { - if (mStartingSurface != null) { - mStartingSurface.addStartingWindow(info, appToken); + if (mStartingWindow != null) { + mStartingWindow.addStartingWindow(info, appToken); } } @Override - public void removeStartingWindow(int taskId) { - if (mStartingSurface != null) { - mStartingSurface.removeStartingWindow(taskId); + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { + if (mStartingWindow != null) { + mStartingWindow.removeStartingWindow(taskId, leash, frame, playRevealAnimation); } } @Override public void copySplashScreenView(int taskId) { - if (mStartingSurface != null) { - mStartingSurface.copySplashScreenView(taskId); + if (mStartingWindow != null) { + mStartingWindow.copySplashScreenView(taskId); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index 46884fefd69c..7d65a08ce798 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -83,6 +83,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private boolean mIsInitialized; private Listener mListener; private Executor mListenerExecutor; + private Rect mObscuredTouchRect; private final Rect mTmpRect = new Rect(); private final Rect mTmpRootRect = new Rect(); @@ -161,6 +162,15 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, } /** + * Indicates a region of the view that is not touchable. + * + * @param obscuredRect the obscured region of the view. + */ + public void setObscuredTouchRect(Rect obscuredRect) { + mObscuredTouchRect = obscuredRect; + } + + /** * Call when view position or size has changed. Do not call when animating. */ public void onLocationChanged() { @@ -384,6 +394,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, mTmpRect.set(mTmpLocation[0], mTmpLocation[1], mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight()); inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE); + + if (mObscuredTouchRect != null) { + inoutInfo.touchableRegion.union(mObscuredTouchRect); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java index 562b32b41dd2..b6d408afd703 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import android.app.ActivityManager; +import android.app.ActivityTaskManager; import android.graphics.Rect; import android.view.SurfaceControl; import android.window.WindowContainerToken; @@ -88,7 +89,8 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan ProtoLog.v(WM_SHELL_TASK_ORG, "pair task1=%d task2=%d in AppPair=%s", task1.taskId, task2.taskId, this); - if (!task1.isResizeable || !task2.isResizeable) { + if ((!task1.isResizeable || !task2.isResizeable) + && !ActivityTaskManager.supportsNonResizableMultiWindow()) { ProtoLog.e(WM_SHELL_TASK_ORG, "Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b", task1.isResizeable, task2.isResizeable); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 64a44ca9e7e9..16ede735660f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -41,7 +41,6 @@ import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.Region; import android.os.Bundle; import android.provider.Settings; import android.util.Log; @@ -1046,6 +1045,7 @@ public class BubbleStackView extends FrameLayout } }; + // TODO: Create ManageMenuView and move setup / animations there private void setUpManageMenu() { if (mManageMenu != null) { removeView(mManageMenu); @@ -2146,50 +2146,6 @@ public class BubbleStackView extends FrameLayout } /** - * This method is called by {@link android.app.ActivityView} because the BubbleStackView has a - * higher Z-index than the ActivityView (so that dragged-out bubbles are visible over the AV). - * ActivityView is asking BubbleStackView to subtract the stack's bounds from the provided - * touchable region, so that the ActivityView doesn't consume events meant for the stack. Due to - * the special nature of ActivityView, it does not respect the standard - * {@link #dispatchTouchEvent} and {@link #onInterceptTouchEvent} methods typically used for - * this purpose. - * - * BubbleStackView is MATCH_PARENT, so that bubbles can be positioned via their translation - * properties for performance reasons. This means that the default implementation of this method - * subtracts the entirety of the screen from the ActivityView's touchable region, resulting in - * it not receiving any touch events. This was previously addressed by returning false in the - * stack's {@link View#canReceivePointerEvents()} method, but this precluded the use of any - * touch handlers in the stack or its child views. - * - * To support touch handlers, we're overriding this method to leave the ActivityView's touchable - * region alone. The only touchable part of the stack that can ever overlap the AV is a - * dragged-out bubble that is animating back into the row of bubbles. It's not worth continually - * updating the touchable region to allow users to grab a bubble while it completes its ~50ms - * animation back to the bubble row. - * - * NOTE: Any future additions to the stack that obscure the ActivityView region will need their - * bounds subtracted here in order to receive touch events. - */ - @Override - public void subtractObscuredTouchableRegion(Region touchableRegion, View view) { - // If the notification shade is expanded, or the manage menu is open, or we are showing - // manage bubbles user education, we shouldn't let the ActivityView steal any touch events - // from any location. - if (!mIsExpanded - || mShowingManage - || (mManageEduView != null - && mManageEduView.getVisibility() == VISIBLE)) { - touchableRegion.setEmpty(); - } - } - - /** - * If you're here because you're not receiving touch events on a view that is a descendant of - * BubbleStackView, and you think BSV is intercepting them - it's not! You need to subtract the - * bounds of the view in question in {@link #subtractObscuredTouchableRegion}. The ActivityView - * consumes all touch events within its bounds, even for views like the BubbleStackView that are - * above it. It ignores typical view touch handling methods like this one and - * dispatchTouchEvent. */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { @@ -2539,6 +2495,11 @@ public class BubbleStackView extends FrameLayout } mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect); + if (mExpandedBubble.getExpandedView().getTaskView() != null) { + mExpandedBubble.getExpandedView().getTaskView().setObscuredTouchRect(mShowingManage + ? new Rect(0, 0, getWidth(), getHeight()) + : null); + } final boolean isLtr = getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_LTR; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java new file mode 100644 index 000000000000..b29058b1f204 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common; + +import android.Manifest; +import android.util.Slog; + +import java.util.function.Consumer; + +/** + * Helpers for working with executors + */ +public class ExecutorUtils { + + /** + * Checks that the caller has the MANAGE_ACTIVITY_TASKS permission and executes the given + * callback. + */ + public static <T> void executeRemoteCallWithTaskPermission(RemoteCallable<T> controllerInstance, + String log, Consumer<T> callback) { + executeRemoteCallWithTaskPermission(controllerInstance, log, callback, + false /* blocking */); + } + + /** + * Checks that the caller has the MANAGE_ACTIVITY_TASKS permission and executes the given + * callback. + */ + public static <T> void executeRemoteCallWithTaskPermission(RemoteCallable<T> controllerInstance, + String log, Consumer<T> callback, boolean blocking) { + if (controllerInstance == null) return; + + final RemoteCallable<T> controller = controllerInstance; + controllerInstance.getContext().enforceCallingPermission( + Manifest.permission.MANAGE_ACTIVITY_TASKS, log); + if (blocking) { + try { + controllerInstance.getRemoteCallExecutor().executeBlocking(() -> { + callback.accept((T) controller); + }); + } catch (InterruptedException e) { + Slog.e("ExecutorUtils", "Remote call failed", e); + } + } else { + controllerInstance.getRemoteCallExecutor().execute(() -> { + callback.accept((T) controller); + }); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java new file mode 100644 index 000000000000..30f535ba940c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common; + +import android.content.Context; + +/** + * An interface for controllers that can receive remote calls. + */ +public interface RemoteCallable<T> { + /** + * Returns a context used for permission checking. + */ + Context getContext(); + + /** + * Returns the executor to post the handler callback to. + */ + ShellExecutor getRemoteCallExecutor(); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index aab2334f8255..9a09ca43d1d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -235,7 +235,7 @@ public class DragAndDropPolicy { mStarter.startShortcut(packageName, id, stage, position, opts, user); } else { mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT), - mContext, null, stage, position, opts); + null, stage, position, opts); } } @@ -295,7 +295,7 @@ public class DragAndDropPolicy { @Nullable Bundle options); void startShortcut(String packageName, String shortcutId, @StageType int stage, @StagePosition int position, @Nullable Bundle options, UserHandle user); - void startIntent(PendingIntent intent, Context context, Intent fillInIntent, + void startIntent(PendingIntent intent, Intent fillInIntent, @StageType int stage, @StagePosition int position, @Nullable Bundle options); void enterSplitScreen(int taskId, boolean leftOrTop); @@ -337,9 +337,8 @@ public class DragAndDropPolicy { } @Override - public void startIntent(PendingIntent intent, Context context, - @Nullable Intent fillInIntent, int stage, int position, - @Nullable Bundle options) { + public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, int stage, + int position, @Nullable Bundle options) { try { intent.send(mContext, 0, fillInIntent, null, null, null, options); } catch (PendingIntent.CanceledException e) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java index 7ce9014fc9ba..57a9dd2ec6cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java @@ -143,14 +143,14 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor @ImeAnimationFlags public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean imeShouldShow, boolean imeIsFloating, SurfaceControl.Transaction t) { - mHiddenTop = hiddenTop; - mShownTop = shownTop; - mTargetShown = imeShouldShow; if (!isDividerVisible()) { return 0; } - final boolean splitIsVisible = !getView().isHidden(); + mHiddenTop = hiddenTop; + mShownTop = shownTop; + mTargetShown = imeShouldShow; mSecondaryHasFocus = getSecondaryHasFocus(displayId); + final boolean splitIsVisible = !getView().isHidden(); final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus && !imeIsFloating && !getLayout().mDisplayLayout.isLandscape() && !mSplits.mSplitScreenController.isMinimized(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java index eea5c08818cc..d06064a82ff0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java @@ -30,6 +30,7 @@ import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityTaskManager; import android.app.WindowConfiguration; import android.graphics.Rect; import android.os.IBinder; @@ -91,9 +92,11 @@ public class SplitScreenTransitions implements Transitions.TransitionHandler { // is nothing behind it. ((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK) && triggerTask.parentTaskId == mListener.mPrimary.taskId) - // if a non-resizable is launched, we also need to leave split-screen. + // if a non-resizable is launched when it is not supported in multi window, + // we also need to leave split-screen. || ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) - && !triggerTask.isResizeable); + && !triggerTask.isResizeable + && !ActivityTaskManager.supportsNonResizableMultiWindow()); // In both cases, dismiss the primary if (shouldDismiss) { WindowManagerProxy.buildDismissSplit(out, mListener, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java index 82468ad999b4..5a2ef568d82a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java @@ -46,6 +46,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.List; +import java.util.function.BooleanSupplier; /** * Proxy to simplify calls into window manager/activity manager @@ -208,11 +209,17 @@ class WindowManagerProxy { return false; } ActivityManager.RunningTaskInfo topHomeTask = null; + // One-time lazy wrapper to avoid duplicated IPC in loop. Not store as class variable + // because the value can be changed at runtime. + final BooleanSupplier supportsNonResizableMultiWindow = + createSupportsNonResizableMultiWindowSupplier(); for (int i = rootTasks.size() - 1; i >= 0; --i) { final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i); - // Only move resizeable task to split secondary. However, we have an exception - // for non-resizable home because we will minimize to show it. - if (!rootTask.isResizeable && rootTask.topActivityType != ACTIVITY_TYPE_HOME) { + // Check whether to move resizeable task to split secondary. + // Also, we have an exception for non-resizable home because we will minimize to show + // it. + if (!rootTask.isResizeable && rootTask.topActivityType != ACTIVITY_TYPE_HOME + && !supportsNonResizableMultiWindow.getAsBoolean()) { continue; } // Only move fullscreen tasks to split secondary. @@ -357,6 +364,21 @@ class WindowManagerProxy { outWct.setFocusable(tiles.mPrimary.token, true /* focusable */); } + /** Creates a lazy wrapper to get whether it supports non-resizable in multi window. */ + private static BooleanSupplier createSupportsNonResizableMultiWindowSupplier() { + return new BooleanSupplier() { + private Boolean mSupportsNonResizableMultiWindow; + @Override + public boolean getAsBoolean() { + if (mSupportsNonResizableMultiWindow == null) { + mSupportsNonResizableMultiWindow = + ActivityTaskManager.supportsNonResizableMultiWindow(); + } + return mSupportsNonResizableMultiWindow; + } + }; + } + /** * Utility to apply a sync transaction serially with other sync transactions. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl new file mode 100644 index 000000000000..008b5087d7da --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.onehanded; + +/** + * Interface that is exposed to remote callers to manipulate the OneHanded feature. + */ +interface IOneHanded { + + /** + * Enters one handed mode. + */ + oneway void startOneHanded() = 1; + + /** + * Exits one handed mode. + */ + oneway void stopOneHanded() = 2; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java index 4f31c370108d..a7e9a0135de0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java @@ -26,6 +26,14 @@ import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEv */ @ExternalThread public interface OneHanded { + + /** + * Returns a binder that can be passed to an external process to manipulate OneHanded. + */ + default IOneHanded createExternalInterface() { + return null; + } + /** * Return one handed settings enabled or not. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 5fc7c987899f..8022c1b6e047 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -18,6 +18,10 @@ package com.android.wm.shell.onehanded; import static android.os.UserHandle.USER_CURRENT; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; + +import android.Manifest; +import android.annotation.BinderThread; import android.content.ComponentName; import android.content.Context; import android.content.om.IOverlayManager; @@ -43,6 +47,8 @@ import com.android.internal.logging.UiEventLogger; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ExecutorUtils; +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; @@ -54,7 +60,7 @@ import java.io.PrintWriter; /** * Manages and manipulates the one handed states, transitions, and gesture for phones. */ -public class OneHandedController { +public class OneHandedController implements RemoteCallable<OneHandedController> { private static final String TAG = "OneHandedController"; private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE = @@ -239,6 +245,16 @@ public class OneHandedController { return mImpl; } + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + /** * Set one handed enabled or disabled when user update settings */ @@ -567,8 +583,22 @@ public class OneHandedController { } } + /** + * The interface for calls from outside the Shell, within the host process. + */ @ExternalThread private class OneHandedImpl implements OneHanded { + private IOneHandedImpl mIOneHanded; + + @Override + public IOneHanded createExternalInterface() { + if (mIOneHanded != null) { + mIOneHanded.invalidate(); + } + mIOneHanded = new IOneHandedImpl(OneHandedController.this); + return mIOneHanded; + } + @Override public boolean isOneHandedEnabled() { // This is volatile so return directly @@ -637,4 +667,39 @@ public class OneHandedController { }); } } + + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IOneHandedImpl extends IOneHanded.Stub { + private OneHandedController mController; + + IOneHandedImpl(OneHandedController controller) { + mController = controller; + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; + } + + @Override + public void startOneHanded() { + executeRemoteCallWithTaskPermission(mController, "startOneHanded", + (controller) -> { + controller.startOneHanded(); + }); + } + + @Override + public void stopOneHanded() { + executeRemoteCallWithTaskPermission(mController, "stopOneHanded", + (controller) -> { + controller.stopOneHanded(); + }); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl new file mode 100644 index 000000000000..a6ffa6e44584 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip; + +import android.app.PictureInPictureParams; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.graphics.Rect; + +import com.android.wm.shell.pip.IPipAnimationListener; + +/** + * Interface that is exposed to remote callers to manipulate the Pip feature. + */ +interface IPip { + + /** + * Notifies that Activity is about to be swiped to home with entering PiP transition and + * queries the destination bounds for PiP depends on Launcher's rotation and shelf height. + * + * @param componentName ComponentName represents the Activity + * @param activityInfo ActivityInfo tied to the Activity + * @param pictureInPictureParams PictureInPictureParams tied to the Activity + * @param launcherRotation Launcher rotation to calculate the PiP destination bounds + * @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds + * @return destination bounds the PiP window should land into + */ + Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo, + in PictureInPictureParams pictureInPictureParams, + int launcherRotation, int shelfHeight) = 1; + + /** + * Notifies the swiping Activity to PiP onto home transition is finished + * + * @param componentName ComponentName represents the Activity + * @param destinationBounds the destination bounds the PiP window lands into + */ + oneway void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds) = 2; + + /** + * Sets listener to get pinned stack animation callbacks. + */ + oneway void setPinnedStackAnimationListener(IPipAnimationListener listener) = 3; + + /** + * Sets the shelf height and visibility. + */ + oneway void setShelfHeight(boolean visible, int shelfHeight) = 4; +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IPinnedStackAnimationListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl index 97aa512ea7df..2569b780c1bb 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IPinnedStackAnimationListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl @@ -14,15 +14,14 @@ * limitations under the License. */ -package com.android.systemui.shared.recents; +package com.android.wm.shell.pip; /** - * Listener interface that Launcher attaches to SystemUI to get - * pinned stack animation callbacks. + * Listener interface that Launcher attaches to SystemUI to get Pip animation callbacks. */ -oneway interface IPinnedStackAnimationListener { +oneway interface IPipAnimationListener { /** - * Notifies the pinned stack animation is started. + * Notifies the listener that the Pip animation is started. */ - void onPinnedStackAnimationStarted(); + void onPipAnimationStarted(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index d14c3e3c0dd4..6d4773bdeb1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -16,15 +16,10 @@ package com.android.wm.shell.pip; -import android.annotation.Nullable; -import android.app.PictureInPictureParams; -import android.content.ComponentName; -import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.pip.phone.PipTouchHandler; import java.io.PrintWriter; import java.util.function.Consumer; @@ -34,6 +29,14 @@ import java.util.function.Consumer; */ @ExternalThread public interface Pip { + + /** + * Returns a binder that can be passed to an external process to manipulate PIP. + */ + default IPip createExternalInterface() { + return null; + } + /** * Expand PIP, it's possible that specific request to activate the window via Alt-tab. */ @@ -109,30 +112,6 @@ public interface Pip { default void showPictureInPictureMenu() {} /** - * Called by Launcher when swiping an auto-pip enabled Activity to home starts - * @param componentName {@link ComponentName} represents the Activity entering PiP - * @param activityInfo {@link ActivityInfo} tied to the Activity - * @param pictureInPictureParams {@link PictureInPictureParams} tied to the Activity - * @param launcherRotation Rotation Launcher is in - * @param shelfHeight Shelf height when landing PiP window onto Launcher - * @return Destination bounds of PiP window based on the parameters passed in - */ - default Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, - PictureInPictureParams pictureInPictureParams, - int launcherRotation, int shelfHeight) { - return null; - } - - /** - * Called by Launcher when swiping an auto-pip enable Activity to home finishes - * @param componentName {@link ComponentName} represents the Activity entering PiP - * @param destinationBounds Destination bounds of PiP window - */ - default void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { - return; - } - - /** * Called by NavigationBar in order to listen in for PiP bounds change. This is mostly used * for times where the PiP bounds could conflict with SystemUI elements, such as a stashed * PiP and the Back-from-Edge gesture. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 9a584c67f97c..501b90ea6828 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -21,8 +21,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.view.WindowManager.INPUT_CONSUMER_PIP; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; +import android.Manifest; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.PictureInPictureParams; @@ -33,6 +35,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -44,6 +47,7 @@ import android.view.DisplayInfo; import android.view.WindowManagerGlobal; import android.window.WindowContainerTransaction; +import androidx.annotation.BinderThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -51,9 +55,13 @@ import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.ExecutorUtils; +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.pip.IPip; +import com.android.wm.shell.pip.IPipAnimationListener; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsAlgorithm; @@ -71,7 +79,8 @@ import java.util.function.Consumer; /** * Manages the picture-in-picture (PIP) UI and states for Phones. */ -public class PipController implements PipTransitionController.PipTransitionCallback { +public class PipController implements PipTransitionController.PipTransitionCallback, + RemoteCallable<PipController> { private static final String TAG = "PipController"; private Context mContext; @@ -85,12 +94,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb private PipBoundsState mPipBoundsState; private PipTouchHandler mTouchHandler; private PipTransitionController mPipTransitionController; - protected final PipImpl mImpl = new PipImpl(); + protected final PipImpl mImpl; private final Rect mTmpInsetBounds = new Rect(); private boolean mIsInFixedRotation; - private Consumer<Boolean> mPinnedStackAnimationRecentsCallback; + private IPipAnimationListener mPinnedStackAnimationRecentsCallback; protected PhonePipMenuController mMenuController; protected PipTaskOrganizer mPipTaskOrganizer; @@ -264,6 +273,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb } mContext = context; + mImpl = new PipImpl(); mWindowManagerShellWrapper = windowManagerShellWrapper; mDisplayController = displayController; mPipBoundsAlgorithm = pipBoundsAlgorithm; @@ -366,6 +376,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb }); } + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + private void onConfigurationChanged(Configuration newConfig) { mPipBoundsAlgorithm.onConfigurationChanged(mContext); mTouchHandler.onConfigurationChanged(); @@ -474,7 +494,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipTaskOrganizer.setOneShotAnimationType(animationType); } - private void setPinnedStackAnimationListener(Consumer<Boolean> callback) { + private void setPinnedStackAnimationListener(IPipAnimationListener callback) { mPinnedStackAnimationRecentsCallback = callback; } @@ -512,7 +532,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Disable touches while the animation is running mTouchHandler.setTouchEnabled(false); if (mPinnedStackAnimationRecentsCallback != null) { - mPinnedStackAnimationRecentsCallback.accept(true); + try { + mPinnedStackAnimationRecentsCallback.onPipAnimationStarted(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call onPinnedStackAnimationStarted()", e); + } } } @@ -638,7 +662,21 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipInputConsumer.dump(pw, innerPrefix); } + /** + * The interface for calls from outside the Shell, within the host process. + */ private class PipImpl implements Pip { + private IPipImpl mIPip; + + @Override + public IPip createExternalInterface() { + if (mIPip != null) { + mIPip.invalidate(); + } + mIPip = new IPipImpl(PipController.this); + return mIPip; + } + @Override public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) { mMainExecutor.execute(() -> { @@ -696,13 +734,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void setPinnedStackAnimationListener(Consumer<Boolean> callback) { - mMainExecutor.execute(() -> { - PipController.this.setPinnedStackAnimationListener(callback); - }); - } - - @Override public void setPinnedStackAnimationType(int animationType) { mMainExecutor.execute(() -> { PipController.this.setPinnedStackAnimationType(animationType); @@ -724,37 +755,99 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, - PictureInPictureParams pictureInPictureParams, int launcherRotation, - int shelfHeight) { - Rect[] result = new Rect[1]; + public void dump(PrintWriter pw) { try { mMainExecutor.executeBlocking(() -> { - result[0] = PipController.this.startSwipePipToHome(componentName, activityInfo, - pictureInPictureParams, launcherRotation, shelfHeight); + PipController.this.dump(pw); }); } catch (InterruptedException e) { - Slog.e(TAG, "Failed to start swipe pip to home"); + Slog.e(TAG, "Failed to dump PipController in 2s"); } + } + } + + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IPipImpl extends IPip.Stub { + private PipController mController; + private IPipAnimationListener mListener; + private final IBinder.DeathRecipient mListenerDeathRecipient = + new IBinder.DeathRecipient() { + @Override + @BinderThread + public void binderDied() { + final PipController controller = mController; + controller.getRemoteCallExecutor().execute(() -> { + mListener = null; + controller.setPinnedStackAnimationListener(null); + }); + } + }; + + IPipImpl(PipController controller) { + mController = controller; + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; + } + + @Override + public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, + PictureInPictureParams pictureInPictureParams, int launcherRotation, + int shelfHeight) { + Rect[] result = new Rect[1]; + executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome", + (controller) -> { + result[0] = controller.startSwipePipToHome(componentName, activityInfo, + pictureInPictureParams, launcherRotation, shelfHeight); + }, true /* blocking */); return result[0]; } @Override public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { - mMainExecutor.execute(() -> { - PipController.this.stopSwipePipToHome(componentName, destinationBounds); - }); + executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome", + (controller) -> { + controller.stopSwipePipToHome(componentName, destinationBounds); + }); } @Override - public void dump(PrintWriter pw) { - try { - mMainExecutor.executeBlocking(() -> { - PipController.this.dump(pw); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to dump PipController in 2s"); - } + public void setShelfHeight(boolean visible, int height) { + executeRemoteCallWithTaskPermission(mController, "setShelfHeight", + (controller) -> { + controller.setShelfHeight(visible, height); + }); + } + + @Override + public void setPinnedStackAnimationListener(IPipAnimationListener listener) { + executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener", + (controller) -> { + if (mListener != null) { + // Reset the old death recipient + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } + if (listener != null) { + // Register the death recipient for the new listener to clear the listener + try { + listener.asBinder().linkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death"); + return; + } + } + mListener = listener; + controller.setPinnedStackAnimationListener(listener); + }); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl new file mode 100644 index 000000000000..0c46eaba18ae --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.splitscreen; + +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; +import android.os.UserHandle; + +import com.android.wm.shell.splitscreen.ISplitScreenListener; + +/** + * Interface that is exposed to remote callers to manipulate the splitscreen feature. + */ +interface ISplitScreen { + + /** + * Registers a split screen listener. + */ + oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1; + + /** + * Unregisters a split screen listener. + */ + oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2; + + /** + * Hides the side-stage if it is currently visible. + */ + oneway void setSideStageVisibility(boolean visible) = 3; + + /** + * Removes a task from the side stage. + */ + oneway void removeFromSideStage(int taskId) = 4; + + /** + * Removes the split-screen stages. + */ + oneway void exitSplitScreen() = 5; + + /** + * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. + */ + oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6; + + /** + * Starts a task in a stage. + */ + oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7; + + /** + * Starts a shortcut in a stage. + */ + oneway void startShortcut(String packageName, String shortcutId, int stage, int position, + in Bundle options, in UserHandle user) = 8; + + /** + * Starts an activity in a stage. + */ + oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage, + int position, in Bundle options) = 9; +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl index 54242bece2b6..faab4c2009cf 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl @@ -14,12 +14,20 @@ * limitations under the License. */ -package com.android.systemui.shared.recents; +package com.android.wm.shell.splitscreen; /** * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. */ oneway interface ISplitScreenListener { + + /** + * Called when the stage position changes. + */ void onStagePositionChanged(int stage, int position); + + /** + * Called when a task changes stages. + */ void onTaskStageChanged(int taskId, int stage, boolean visible); -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 25a84bd46484..340b55d7f446 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -35,7 +35,7 @@ import com.android.wm.shell.draganddrop.DragAndDropPolicy; * TODO: Figure out which of these are actually needed outside of the Shell */ @ExternalThread -public interface SplitScreen extends DragAndDropPolicy.Starter { +public interface SplitScreen { /** * Stage position isn't specified normally meaning to use what ever it is currently set to. */ @@ -89,35 +89,10 @@ public interface SplitScreen extends DragAndDropPolicy.Starter { void onTaskStageChanged(int taskId, @StageType int stage, boolean visible); } - /** @return {@code true} if split-screen is currently visible. */ - boolean isSplitScreenVisible(); - /** Moves a task in the side-stage of split-screen. */ - boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition); - /** Moves a task in the side-stage of split-screen. */ - boolean moveToSideStage(ActivityManager.RunningTaskInfo task, - @StagePosition int sideStagePosition); - /** Removes a task from the side-stage of split-screen. */ - boolean removeFromSideStage(int taskId); - /** Sets the position of the side-stage. */ - void setSideStagePosition(@StagePosition int sideStagePosition); - /** Hides the side-stage if it is currently visible. */ - void setSideStageVisibility(boolean visible); - - /** Removes the split-screen stages. */ - void exitSplitScreen(); - /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */ - void exitSplitScreenOnHide(boolean exitSplitScreenOnHide); - /** Gets the stage bounds. */ - void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds); - - void registerSplitScreenListener(SplitScreenListener listener); - void unregisterSplitScreenListener(SplitScreenListener listener); - - void startTask(int taskId, - @StageType int stage, @StagePosition int position, @Nullable Bundle options); - void startShortcut(String packageName, String shortcutId, @StageType int stage, - @StagePosition int position, @Nullable Bundle options, UserHandle user); - void startIntent(PendingIntent intent, Context context, - @Nullable Intent fillInIntent, @StageType int stage, - @StagePosition int position, @Nullable Bundle options); + /** + * Returns a binder that can be passed to an external process to manipulate SplitScreen. + */ + default ISplitScreen createExternalInterface() { + return null; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index bb6f6f259a1e..d4362efe462d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED; @@ -34,19 +35,24 @@ import android.content.Intent; import android.content.pm.LauncherApps; import android.graphics.Rect; import android.os.Bundle; +import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; +import androidx.annotation.BinderThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropPolicy; +import com.android.wm.shell.splitscreen.ISplitScreenListener; import java.io.PrintWriter; @@ -55,7 +61,8 @@ import java.io.PrintWriter; * {@link SplitScreen}. * @see StageCoordinator */ -public class SplitScreenController implements DragAndDropPolicy.Starter { +public class SplitScreenController implements DragAndDropPolicy.Starter, + RemoteCallable<SplitScreenController> { private static final String TAG = SplitScreenController.class.getSimpleName(); private final ShellTaskOrganizer mTaskOrganizer; @@ -84,6 +91,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { return mImpl; } + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + public void onOrganizerRegistered() { if (mStageCoordinator == null) { // TODO: Multi-display @@ -172,13 +189,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { } } - public void startIntent(PendingIntent intent, Context context, - Intent fillInIntent, @SplitScreen.StageType int stage, - @SplitScreen.StagePosition int position, @Nullable Bundle options) { + public void startIntent(PendingIntent intent, Intent fillInIntent, + @SplitScreen.StageType int stage, @SplitScreen.StagePosition int position, + @Nullable Bundle options) { options = resolveStartStage(stage, position, options); try { - intent.send(context, 0, fillInIntent, null, null, null, options); + intent.send(mContext, 0, fillInIntent, null, null, null, options); } catch (PendingIntent.CanceledException e) { Slog.e(TAG, "Failed to launch activity", e); } @@ -242,121 +259,170 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { } } + /** + * The interface for calls from outside the Shell, within the host process. + */ + @ExternalThread private class SplitScreenImpl implements SplitScreen { - @Override - public boolean isSplitScreenVisible() { - return mMainExecutor.executeBlockingForResult(() -> { - return SplitScreenController.this.isSplitScreenVisible(); - }, Boolean.class); - } + private ISplitScreenImpl mISplitScreen; @Override - public boolean moveToSideStage(int taskId, int sideStagePosition) { - return mMainExecutor.executeBlockingForResult(() -> { - return SplitScreenController.this.moveToSideStage(taskId, sideStagePosition); - }, Boolean.class); + public ISplitScreen createExternalInterface() { + if (mISplitScreen != null) { + mISplitScreen.invalidate(); + } + mISplitScreen = new ISplitScreenImpl(SplitScreenController.this); + return mISplitScreen; } + } - @Override - public boolean moveToSideStage(ActivityManager.RunningTaskInfo task, - int sideStagePosition) { - return mMainExecutor.executeBlockingForResult(() -> { - return SplitScreenController.this.moveToSideStage(task, sideStagePosition); - }, Boolean.class); - } + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class ISplitScreenImpl extends ISplitScreen.Stub { + private SplitScreenController mController; + private ISplitScreenListener mListener; + private final SplitScreen.SplitScreenListener mSplitScreenListener = + new SplitScreen.SplitScreenListener() { + @Override + public void onStagePositionChanged(int stage, int position) { + try { + if (mListener != null) { + mListener.onStagePositionChanged(stage, position); + } + } catch (RemoteException e) { + Slog.e(TAG, "onStagePositionChanged", e); + } + } - @Override - public boolean removeFromSideStage(int taskId) { - return mMainExecutor.executeBlockingForResult(() -> { - return SplitScreenController.this.removeFromSideStage(taskId); - }, Boolean.class); + @Override + public void onTaskStageChanged(int taskId, int stage, boolean visible) { + try { + if (mListener != null) { + mListener.onTaskStageChanged(taskId, stage, visible); + } + } catch (RemoteException e) { + Slog.e(TAG, "onTaskStageChanged", e); + } + } + }; + private final IBinder.DeathRecipient mListenerDeathRecipient = + new IBinder.DeathRecipient() { + @Override + @BinderThread + public void binderDied() { + final SplitScreenController controller = mController; + controller.getRemoteCallExecutor().execute(() -> { + mListener = null; + controller.unregisterSplitScreenListener(mSplitScreenListener); + }); + } + }; + + public ISplitScreenImpl(SplitScreenController controller) { + mController = controller; } - @Override - public void setSideStagePosition(int sideStagePosition) { - mMainExecutor.execute(() -> { - SplitScreenController.this.setSideStagePosition(sideStagePosition); - }); + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; } @Override - public void setSideStageVisibility(boolean visible) { - mMainExecutor.execute(() -> { - SplitScreenController.this.setSideStageVisibility(visible); - }); + public void registerSplitScreenListener(ISplitScreenListener listener) { + executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener", + (controller) -> { + if (mListener != null) { + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } + if (listener != null) { + try { + listener.asBinder().linkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death"); + return; + } + } + mListener = listener; + controller.registerSplitScreenListener(mSplitScreenListener); + }); } @Override - public void enterSplitScreen(int taskId, boolean leftOrTop) { - mMainExecutor.execute(() -> { - SplitScreenController.this.enterSplitScreen(taskId, leftOrTop); - }); + public void unregisterSplitScreenListener(ISplitScreenListener listener) { + executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener", + (controller) -> { + if (mListener != null) { + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } + mListener = null; + controller.unregisterSplitScreenListener(mSplitScreenListener); + }); } @Override public void exitSplitScreen() { - mMainExecutor.execute(() -> { - SplitScreenController.this.exitSplitScreen(); - }); + executeRemoteCallWithTaskPermission(mController, "exitSplitScreen", + (controller) -> { + controller.exitSplitScreen(); + }); } @Override public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { - mMainExecutor.execute(() -> { - SplitScreenController.this.exitSplitScreenOnHide(exitSplitScreenOnHide); - }); + executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide", + (controller) -> { + controller.exitSplitScreenOnHide(exitSplitScreenOnHide); + }); } @Override - public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { - try { - mMainExecutor.executeBlocking(() -> { - SplitScreenController.this.getStageBounds(outTopOrLeftBounds, - outBottomOrRightBounds); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to get stage bounds in 2s"); - } - } - - @Override - public void registerSplitScreenListener(SplitScreenListener listener) { - mMainExecutor.execute(() -> { - SplitScreenController.this.registerSplitScreenListener(listener); - }); + public void setSideStageVisibility(boolean visible) { + executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility", + (controller) -> { + controller.setSideStageVisibility(visible); + }); } @Override - public void unregisterSplitScreenListener(SplitScreenListener listener) { - mMainExecutor.execute(() -> { - SplitScreenController.this.unregisterSplitScreenListener(listener); - }); + public void removeFromSideStage(int taskId) { + executeRemoteCallWithTaskPermission(mController, "removeFromSideStage", + (controller) -> { + controller.removeFromSideStage(taskId); + }); } @Override public void startTask(int taskId, int stage, int position, @Nullable Bundle options) { - mMainExecutor.execute(() -> { - SplitScreenController.this.startTask(taskId, stage, position, options); - }); + executeRemoteCallWithTaskPermission(mController, "startTask", + (controller) -> { + controller.startTask(taskId, stage, position, options); + }); } @Override public void startShortcut(String packageName, String shortcutId, int stage, int position, @Nullable Bundle options, UserHandle user) { - mMainExecutor.execute(() -> { - SplitScreenController.this.startShortcut(packageName, shortcutId, stage, position, - options, user); - }); + executeRemoteCallWithTaskPermission(mController, "startShortcut", + (controller) -> { + controller.startShortcut(packageName, shortcutId, stage, position, + options, user); + }); } @Override - public void startIntent(PendingIntent intent, Context context, Intent fillInIntent, - int stage, int position, @Nullable Bundle options) { - mMainExecutor.execute(() -> { - SplitScreenController.this.startIntent(intent, context, fillInIntent, stage, - position, options); - }); + public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position, + @Nullable Bundle options) { + executeRemoteCallWithTaskPermission(mController, "startIntent", + (controller) -> { + controller.startIntent(intent, fillInIntent, stage, position, options); + }); } } - } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl new file mode 100644 index 000000000000..546c406ef453 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.startingsurface; + +import com.android.wm.shell.startingsurface.IStartingWindowListener; + +/** + * Interface that is exposed to remote callers to manipulate starting windows. + */ +interface IStartingWindow { + /** + * Sets listener to get task launching callbacks. + */ + oneway void setStartingWindowListener(IStartingWindowListener listener) = 43; +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IStartingWindowListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl index eb3e60cec5c5..f562c8fc4f85 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IStartingWindowListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.shared.recents; +package com.android.wm.shell.startingsurface; /** * Listener interface that Launcher attaches to SystemUI to get diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java new file mode 100644 index 000000000000..5bc2afd11fe8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.startingsurface; + +import static android.view.View.GONE; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.graphics.BlendMode; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.RadialGradient; +import android.graphics.Rect; +import android.graphics.Shader; +import android.util.Slog; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; +import android.view.animation.Transformation; +import android.view.animation.TranslateYAnimation; +import android.window.SplashScreenView; + +import com.android.wm.shell.common.TransactionPool; + +/** + * Default animation for exiting the splash screen window. + * @hide + */ +public class SplashScreenExitAnimation implements Animator.AnimatorListener { + private static final boolean DEBUG_EXIT_ANIMATION = false; + private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false; + private static final String TAG = StartingSurfaceDrawer.TAG; + + private static final Interpolator ICON_EXIT_INTERPOLATOR = new PathInterpolator(1f, 0f, 1f, 1f); + private static final Interpolator APP_EXIT_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f); + + private static final int EXTRA_REVEAL_DELAY = 133; + private final Matrix mTmpTransform = new Matrix(); + private final float[] mTmpFloat9 = new float[9]; + private SurfaceControl mFirstWindowSurface; + private final Rect mFirstWindowFrame = new Rect(); + private final SplashScreenView mSplashScreenView; + private final int mMainWindowShiftLength; + private final int mIconShiftLength; + private final int mAppDuration; + private final int mIconDuration; + private final TransactionPool mTransactionPool; + + private ValueAnimator mMainAnimator; + private Animation mShiftUpAnimation; + private AnimationSet mIconAnimationSet; + private Runnable mFinishCallback; + + SplashScreenExitAnimation(SplashScreenView view, SurfaceControl leash, Rect frame, + int appDuration, int iconDuration, int mainWindowShiftLength, int iconShiftLength, + TransactionPool pool, Runnable handleFinish) { + mSplashScreenView = view; + mFirstWindowSurface = leash; + if (frame != null) { + mFirstWindowFrame.set(frame); + } + mAppDuration = appDuration; + mIconDuration = iconDuration; + mMainWindowShiftLength = mainWindowShiftLength; + mIconShiftLength = iconShiftLength; + mFinishCallback = handleFinish; + mTransactionPool = pool; + } + + void prepareAnimations() { + prepareRevealAnimation(); + prepareShiftAnimation(); + } + + void startAnimations() { + if (mIconAnimationSet != null) { + mIconAnimationSet.start(); + } + if (mMainAnimator != null) { + mMainAnimator.start(); + } + if (mShiftUpAnimation != null) { + mShiftUpAnimation.start(); + } + } + + // reveal splash screen, shift up main window + private void prepareRevealAnimation() { + // splash screen + mMainAnimator = ValueAnimator.ofFloat(0f, 1f); + mMainAnimator.setDuration(mAppDuration); + mMainAnimator.setInterpolator(APP_EXIT_INTERPOLATOR); + mMainAnimator.addListener(this); + + final int startDelay = mIconDuration + EXTRA_REVEAL_DELAY; + final float transparentRatio = 0.95f; + final int globalHeight = mSplashScreenView.getHeight(); + final int verticalCircleCenter = 0; + final int finalVerticalLength = globalHeight - verticalCircleCenter; + final int halfWidth = mSplashScreenView.getWidth() / 2; + final int endRadius = (int) (0.5 + (1f / transparentRatio * (int) + Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth))); + final RadialVanishAnimation radialVanishAnimation = new RadialVanishAnimation( + mSplashScreenView, mMainAnimator); + radialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter); + radialVanishAnimation.setRadius(0/* initRadius */, endRadius); + final int[] colors = {Color.TRANSPARENT, Color.TRANSPARENT, Color.WHITE}; + final float[] stops = {0f, transparentRatio, 1f}; + radialVanishAnimation.setRadialPaintParam(colors, stops); + radialVanishAnimation.setReady(); + mMainAnimator.setStartDelay(startDelay); + + if (mFirstWindowSurface != null) { + // shift up main window + View occludeHoleView = new View(mSplashScreenView.getContext()); + if (DEBUG_EXIT_ANIMATION_BLEND) { + occludeHoleView.setBackgroundColor(Color.BLUE); + } else { + occludeHoleView.setBackgroundColor(mSplashScreenView.getInitBackgroundColor()); + } + final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength); + mSplashScreenView.addView(occludeHoleView, params); + + mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength); + mShiftUpAnimation.setDuration(mAppDuration); + mShiftUpAnimation.setInterpolator(APP_EXIT_INTERPOLATOR); + mShiftUpAnimation.setStartOffset(startDelay); + + occludeHoleView.setAnimation(mShiftUpAnimation); + } + } + + // shift down icon and branding view + private void prepareShiftAnimation() { + final View iconView = mSplashScreenView.getIconView(); + if (iconView == null) { + return; + } + if (mIconShiftLength > 0) { + mIconAnimationSet = new AnimationSet(true /* shareInterpolator */); + if (DEBUG_EXIT_ANIMATION) { + Slog.v(TAG, "first exit animation, shift length: " + mIconShiftLength); + } + mIconAnimationSet.addAnimation(new TranslateYAnimation(0, mIconShiftLength)); + mIconAnimationSet.addAnimation(new AlphaAnimation(1, 0)); + mIconAnimationSet.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + if (DEBUG_EXIT_ANIMATION) { + Slog.v(TAG, "first exit animation finished"); + } + iconView.post(() -> iconView.setVisibility(GONE)); + } + + @Override + public void onAnimationRepeat(Animation animation) { + // ignore + } + }); + mIconAnimationSet.setDuration(mIconDuration); + mIconAnimationSet.setInterpolator(ICON_EXIT_INTERPOLATOR); + iconView.setAnimation(mIconAnimationSet); + final View brandingView = mSplashScreenView.getBrandingView(); + if (brandingView != null) { + brandingView.setAnimation(mIconAnimationSet); + } + } + } + + private static class RadialVanishAnimation extends View { + private SplashScreenView mView; + private int mInitRadius; + private int mFinishRadius; + private boolean mReady; + + private final Point mCircleCenter = new Point(); + private final Matrix mVanishMatrix = new Matrix(); + private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + RadialVanishAnimation(SplashScreenView target, ValueAnimator animator) { + super(target.getContext()); + mView = target; + animator.addUpdateListener((animation) -> { + if (mVanishPaint.getShader() == null) { + return; + } + final float value = (float) animation.getAnimatedValue(); + final float scale = (mFinishRadius - mInitRadius) * value + mInitRadius; + mVanishMatrix.setScale(scale, scale); + mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y); + mVanishPaint.getShader().setLocalMatrix(mVanishMatrix); + mView.postInvalidate(); + }); + mView.addView(this); + } + + void setRadius(int initRadius, int finishRadius) { + if (DEBUG_EXIT_ANIMATION) { + Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius + + " final " + finishRadius); + } + mInitRadius = initRadius; + mFinishRadius = finishRadius; + } + + void setCircleCenter(int x, int y) { + if (DEBUG_EXIT_ANIMATION) { + Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y); + } + mCircleCenter.set(x, y); + } + + void setRadialPaintParam(int[] colors, float[] stops) { + // setup gradient shader + final RadialGradient rShader = + new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP); + mVanishPaint.setShader(rShader); + if (!DEBUG_EXIT_ANIMATION_BLEND) { + mVanishPaint.setBlendMode(BlendMode.MODULATE); + } + } + + void setReady() { + mReady = true; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mReady) { + canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint); + } + } + } + + private final class ShiftUpAnimation extends TranslateYAnimation { + ShiftUpAnimation(float fromYDelta, float toYDelta) { + super(fromYDelta, toYDelta); + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + super.applyTransformation(interpolatedTime, t); + + if (mFirstWindowSurface == null) { + return; + } + mTmpTransform.set(t.getMatrix()); + final SurfaceControl.Transaction tx = mTransactionPool.acquire(); + mTmpTransform.postTranslate(mFirstWindowFrame.left, + mFirstWindowFrame.top + mMainWindowShiftLength); + tx.setMatrix(mFirstWindowSurface, mTmpTransform, mTmpFloat9); + // TODO set the vsyncId to ensure the transaction doesn't get applied too early. + // Additionally, do you want to have this synchronized with your view animations? + // If so, you'll need to use SyncRtSurfaceTransactionApplier + tx.apply(); + mTransactionPool.release(tx); + } + } + + private void reset() { + if (DEBUG_EXIT_ANIMATION) { + Slog.v(TAG, "vanish animation finished"); + } + mSplashScreenView.post(() -> { + mSplashScreenView.setVisibility(GONE); + if (mFinishCallback != null) { + mFinishCallback.run(); + mFinishCallback = null; + } + }); + if (mFirstWindowSurface != null) { + final SurfaceControl.Transaction tx = mTransactionPool.acquire(); + tx.setWindowCrop(mFirstWindowSurface, null); + tx.apply(); + mFirstWindowSurface.release(); + mFirstWindowSurface = null; + } + } + + @Override + public void onAnimationStart(Animator animation) { + // ignore + } + + @Override + public void onAnimationEnd(Animator animation) { + reset(); + } + + @Override + public void onAnimationCancel(Animator animation) { + reset(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + // ignore + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 2973b5080ae6..3f9c2717731a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -31,12 +31,14 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.Build; import android.util.Slog; +import android.view.SurfaceControl; import android.window.SplashScreenView; import com.android.internal.R; import com.android.internal.graphics.palette.Palette; import com.android.internal.graphics.palette.Quantizer; import com.android.internal.graphics.palette.VariationalKMeansQuantizer; +import com.android.wm.shell.common.TransactionPool; import java.util.List; @@ -56,15 +58,25 @@ public class SplashscreenContentDrawer { // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon. private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f); private final Context mContext; - private final int mMaxIconAnimationDuration; + private final int mMaxAnimatableIconDuration; private int mIconSize; private int mBrandingImageWidth; private int mBrandingImageHeight; - - SplashscreenContentDrawer(Context context, int maxIconAnimationDuration) { + private final int mAppRevealDuration; + private final int mIconExitDuration; + private int mMainWindowShiftLength; + private int mIconNormalExitDistance; + private int mIconEarlyExitDistance; + private final TransactionPool mTransactionPool; + + SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration, + int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) { mContext = context; - mMaxIconAnimationDuration = maxIconAnimationDuration; + mMaxAnimatableIconDuration = maxAnimatableIconDuration; + mAppRevealDuration = appRevealAnimDuration; + mIconExitDuration = iconExitAnimDuration; + mTransactionPool = pool; } private void updateDensity() { @@ -74,6 +86,12 @@ public class SplashscreenContentDrawer { com.android.wm.shell.R.dimen.starting_surface_brand_image_width); mBrandingImageHeight = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.starting_surface_brand_image_height); + mMainWindowShiftLength = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length); + mIconNormalExitDistance = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.starting_surface_normal_exit_icon_distance); + mIconEarlyExitDistance = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.starting_surface_early_exit_icon_distance); } private int getSystemBGColor() { @@ -119,7 +137,7 @@ public class SplashscreenContentDrawer { if (attrs.mReplaceIcon != null) { iconDrawable = attrs.mReplaceIcon; animationDuration = Math.max(0, - Math.min(attrs.mAnimationDuration, mMaxIconAnimationDuration)); + Math.min(attrs.mAnimationDuration, mMaxAnimatableIconDuration)); } else { iconDrawable = iconRes != 0 ? context.getDrawable(iconRes) : context.getPackageManager().getDefaultActivityIcon(); @@ -439,8 +457,8 @@ public class SplashscreenContentDrawer { } /** - * For ColorDrawable only. - * There will be only one color so don't spend too much resource for it. + * For ColorDrawable only. There will be only one color so don't spend too much resource for + * it. */ private static class SingleColorTester implements ColorTester { private final ColorDrawable mColorDrawable; @@ -472,9 +490,8 @@ public class SplashscreenContentDrawer { } /** - * For any other Drawable except ColorDrawable. - * This will use the Palette API to check the color information and use a quantizer to - * filter out transparent colors when needed. + * For any other Drawable except ColorDrawable. This will use the Palette API to check the + * color information and use a quantizer to filter out transparent colors when needed. */ private static class ComplexDrawableTester implements ColorTester { private static final int MAX_BITMAP_SIZE = 40; @@ -593,4 +610,17 @@ public class SplashscreenContentDrawer { } } } + + /** + * Create and play the default exit animation for splash screen view. + */ + void applyExitAnimation(SplashScreenView view, SurfaceControl leash, + Rect frame, boolean isEarlyExit, Runnable finishCallback) { + final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(view, leash, + frame, mAppRevealDuration, mIconExitDuration, mMainWindowShiftLength, + isEarlyExit ? mIconEarlyExitDistance : mIconNormalExitDistance, mTransactionPool, + finishCallback); + animation.prepareAnimations(); + animation.startAnimations(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java index a594a9f31dde..079d68973852 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java @@ -16,32 +16,15 @@ package com.android.wm.shell.startingsurface; -import android.os.IBinder; -import android.window.StartingWindowInfo; - -import java.util.function.BiConsumer; /** * Interface to engage starting window feature. */ public interface StartingSurface { - /** - * Called when a task need a starting window. - */ - void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken); - /** - * Called when the content of a task is ready to show, starting window can be removed. - */ - void removeStartingWindow(int taskId); - /** - * Called when the Task wants to copy the splash screen. - * @param taskId - */ - void copySplashScreenView(int taskId); /** - * Registers the starting window listener. - * - * @param listener The callback when need a starting window. + * Returns a binder that can be passed to an external process to manipulate starting windows. */ - void setStartingWindowListener(BiConsumer<Integer, Integer> listener); + default IStartingWindow createExternalInterface() { + return null; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 2d1d65b87718..14fbaacb9613 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -29,14 +29,18 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.hardware.display.DisplayManager; import android.os.IBinder; +import android.os.SystemClock; import android.util.Slog; import android.util.SparseArray; -import android.view.Choreographer; import android.view.Display; +import android.view.SurfaceControl; import android.view.View; -import android.view.Window; import android.view.WindowManager; import android.window.SplashScreenView; import android.window.SplashScreenView.SplashScreenViewParcelable; @@ -46,6 +50,7 @@ import android.window.TaskSnapshot; import com.android.internal.R; import com.android.internal.policy.PhoneWindow; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TransactionPool; import java.util.function.Consumer; @@ -62,23 +67,23 @@ public class StartingSurfaceDrawer { private final DisplayManager mDisplayManager; private final ShellExecutor mSplashScreenExecutor; private final SplashscreenContentDrawer mSplashscreenContentDrawer; - protected Choreographer mChoreographer; // TODO(b/131727939) remove this when clearing ActivityRecord private static final int REMOVE_WHEN_TIMEOUT = 2000; - public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor) { + public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, + TransactionPool pool) { mContext = context; mDisplayManager = mContext.getSystemService(DisplayManager.class); mSplashScreenExecutor = splashScreenExecutor; - final int maxIconAnimDuration = context.getResources().getInteger( + final int maxAnimatableIconDuration = context.getResources().getInteger( com.android.wm.shell.R.integer.max_starting_window_intro_icon_anim_duration); - mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, maxIconAnimDuration); - mSplashScreenExecutor.execute(this::initChoreographer); - } - - protected void initChoreographer() { - mChoreographer = Choreographer.getInstance(); + final int iconExitAnimDuration = context.getResources().getInteger( + com.android.wm.shell.R.integer.starting_window_icon_exit_anim_duration); + final int appRevealAnimDuration = context.getResources().getInteger( + com.android.wm.shell.R.integer.starting_window_app_reveal_anim_duration); + mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, + maxAnimatableIconDuration, iconExitAnimDuration, appRevealAnimDuration, pool); } private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>(); @@ -195,6 +200,7 @@ public class StartingSurfaceDrawer { } final PhoneWindow win = new PhoneWindow(context); + win.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); win.setIsStartingWindow(true); CharSequence label = context.getResources().getText(labelRes, null); @@ -211,7 +217,7 @@ public class StartingSurfaceDrawer { // the keyguard is being hidden. This is okay because starting windows never show // secret information. // TODO(b/113840485): Occluded may not only happen on default display - if (displayId == DEFAULT_DISPLAY) { + if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) { windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; } @@ -247,6 +253,7 @@ public class StartingSurfaceDrawer { // Setting as trusted overlay to let touches pass through. This is safe because this // window is controlled by the system. params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; + params.format = PixelFormat.RGBA_8888; final Resources res = context.getResources(); final boolean supportsScreen = res != null && (res.getCompatibilityInfo() != null @@ -257,98 +264,25 @@ public class StartingSurfaceDrawer { params.setTitle("Splash Screen " + activityInfo.packageName); - // TODO(b/173975965) If the target activity doesn't request FLAG_HARDWARE_ACCELERATED, we - // cannot replace the content view after first view was drawn, sounds like an issue. - new AddSplashScreenViewRunnable(taskInfo.taskId, win, context, appToken, params, iconRes, - splashscreenContentResId[0], enableHardAccelerated).run(); - } - - private class AddSplashScreenViewRunnable implements Runnable { - private final int mTaskId; - private final Window mWin; - private final IBinder mAppToken; - private final WindowManager.LayoutParams mLayoutParams; - private final Context mContext; - private final int mIconRes; - private final int mSplashscreenContentResId; - private final boolean mReplaceSplashScreenView; - private int mSequence; - - AddSplashScreenViewRunnable(int taskId, Window window, Context context, - IBinder appToken, WindowManager.LayoutParams params, int iconRes, - int splashscreenContentResId, boolean replaceSplashScreenView) { - mTaskId = taskId; - mWin = window; - mAppToken = appToken; - mContext = context; - mLayoutParams = params; - mIconRes = iconRes; - mSplashscreenContentResId = splashscreenContentResId; - mReplaceSplashScreenView = replaceSplashScreenView; - } - - private void createInitialView() { - View tempView = new View(mContext); - mWin.setContentView(tempView); - mSequence++; - final View view = mWin.getDecorView(); - final WindowManager wm = mContext.getSystemService(WindowManager.class); - if (postAddWindow(mTaskId, mAppToken, view, wm, mLayoutParams)) { - mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, this, null); - } - } - - private SplashScreenView replaceRealView() { - final SplashScreenView sView = - mSplashscreenContentDrawer.makeSplashScreenContentView(mContext, - mIconRes, mSplashscreenContentResId); - mWin.setContentView(sView); - sView.cacheRootWindow(mWin); - return sView; - } - - private SplashScreenView initiateOnce() { - final SplashScreenView sView = - mSplashscreenContentDrawer.makeSplashScreenContentView(mContext, mIconRes, - mSplashscreenContentResId); - final View view = mWin.getDecorView(); + // TODO(b/173975965) tracking performance + final int taskId = taskInfo.taskId; + SplashScreenView sView = null; + try { + sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes, + splashscreenContentResId[0]); + final View view = win.getDecorView(); final WindowManager wm = mContext.getSystemService(WindowManager.class); - if (postAddWindow(mTaskId, mAppToken, view, wm, mLayoutParams)) { - mWin.setContentView(sView); - sView.cacheRootWindow(mWin); - } - return sView; - } - - @Override - public void run() { - SplashScreenView view = null; - boolean setRecord = false; - try { - if (mReplaceSplashScreenView) { - // Tricky way to make animation start faster... create the real content after - // first window drawn. The first empty window won't been see because wm will - // still need to wait for transition ready. - if (mSequence == 0) { - createInitialView(); - } else if (mSequence == 1) { - setRecord = true; - view = replaceRealView(); - } - } else { - setRecord = true; - view = initiateOnce(); - } - } catch (RuntimeException e) { - // don't crash if something else bad happens, for example a - // failure loading resources because we are loading from an app - // on external storage that has been unmounted. - Slog.w(TAG, " failed creating starting window", e); - } finally { - if (setRecord) { - setSplashScreenRecord(mTaskId, view); - } + if (postAddWindow(taskId, appToken, view, wm, params)) { + win.setContentView(sView); + sView.cacheRootWindow(win); } + } catch (RuntimeException e) { + // don't crash if something else bad happens, for example a + // failure loading resources because we are loading from an app + // on external storage that has been unmounted. + Slog.w(TAG, " failed creating starting window", e); + } finally { + setSplashScreenRecord(taskId, sView); } } @@ -359,8 +293,10 @@ public class StartingSurfaceDrawer { TaskSnapshot snapshot) { final int taskId = startingWindowInfo.taskInfo.taskId; final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken, - snapshot, mSplashScreenExecutor, () -> removeWindowSynced(taskId)); - mSplashScreenExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT); + snapshot, mSplashScreenExecutor, + () -> removeWindowNoAnimate(taskId)); + mSplashScreenExecutor.executeDelayed(() -> removeWindowNoAnimate(taskId), + REMOVE_WHEN_TIMEOUT); final StartingWindowRecord tView = new StartingWindowRecord(null/* decorView */, surface); mStartingWindowRecords.put(taskId, tView); @@ -369,11 +305,12 @@ public class StartingSurfaceDrawer { /** * Called when the content of a task is ready to show, starting window can be removed. */ - public void removeStartingWindow(int taskId) { + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { Slog.d(TAG, "Task start finish, remove starting surface for task " + taskId); } - removeWindowSynced(taskId); + removeWindowSynced(taskId, leash, frame, playRevealAnimation); } /** @@ -383,13 +320,6 @@ public class StartingSurfaceDrawer { public void copySplashScreenView(int taskId) { final StartingWindowRecord preView = mStartingWindowRecords.get(taskId); SplashScreenViewParcelable parcelable; - if (preView != null) { - if (preView.isWaitForContent()) { - mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, - () -> copySplashScreenView(taskId), null); - return; - } - } if (preView != null && preView.mContentView != null && preView.mContentView.isCopyable()) { parcelable = new SplashScreenViewParcelable(preView.mContentView); @@ -413,12 +343,6 @@ public class StartingSurfaceDrawer { Slog.w(TAG, appToken + " already running, starting window not displayed. " + e.getMessage()); shouldSaveView = false; - } catch (RuntimeException e) { - // don't crash if something else bad happens, for example a - // failure loading resources because we are loading from an app - // on external storage that has been unmounted. - Slog.w(TAG, appToken + " failed creating starting window", e); - shouldSaveView = false; } finally { if (view != null && view.getParent() == null) { Slog.w(TAG, "view not successfully added to wm, removing view"); @@ -427,9 +351,9 @@ public class StartingSurfaceDrawer { } } if (shouldSaveView) { - removeWindowSynced(taskId); - mSplashScreenExecutor.executeDelayed(() -> removeWindowSynced(taskId), - REMOVE_WHEN_TIMEOUT); + removeWindowNoAnimate(taskId); + mSplashScreenExecutor.executeDelayed( + () -> removeWindowNoAnimate(taskId), REMOVE_WHEN_TIMEOUT); saveSplashScreenRecord(taskId, view); } return shouldSaveView; @@ -449,24 +373,30 @@ public class StartingSurfaceDrawer { } } - protected void removeWindowSynced(int taskId) { + private void removeWindowNoAnimate(int taskId) { + removeWindowSynced(taskId, null, null, false); + } + + protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { final StartingWindowRecord record = mStartingWindowRecords.get(taskId); if (record != null) { - if (record.isWaitForContent()) { - if (DEBUG_SPLASH_SCREEN) { - Slog.v(TAG, "splash screen window haven't been draw yet"); - } - mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, - () -> removeWindowSynced(taskId), null); - return; - } if (record.mDecorView != null) { if (DEBUG_SPLASH_SCREEN) { Slog.v(TAG, "Removing splash screen window for task: " + taskId); } - final WindowManager wm = record.mDecorView.getContext() - .getSystemService(WindowManager.class); - wm.removeView(record.mDecorView); + if (record.mContentView != null) { + final HandleExitFinish exitFinish = new HandleExitFinish(record.mDecorView); + if (leash != null || playRevealAnimation) { + mSplashscreenContentDrawer.applyExitAnimation(record.mContentView, + leash, frame, record.isEarlyExit(), exitFinish); + mSplashScreenExecutor.executeDelayed(exitFinish, REMOVE_WHEN_TIMEOUT); + } else { + // the SplashScreenView has been copied to client, skip default exit + // animation + exitFinish.run(); + } + } } if (record.mTaskSnapshotWindow != null) { if (DEBUG_TASK_SNAPSHOT) { @@ -478,6 +408,26 @@ public class StartingSurfaceDrawer { } } + private static class HandleExitFinish implements Runnable { + private View mDecorView; + + HandleExitFinish(View decorView) { + mDecorView = decorView; + } + + @Override + public void run() { + if (mDecorView == null) { + return; + } + final WindowManager wm = mDecorView.getContext().getSystemService(WindowManager.class); + if (wm != null) { + wm.removeView(mDecorView); + } + mDecorView = null; + } + } + private void getWindowResFromContext(Context ctx, Consumer<TypedArray> consumer) { final TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window); consumer.accept(a); @@ -488,10 +438,12 @@ public class StartingSurfaceDrawer { * Record the view or surface for a starting window. */ private static class StartingWindowRecord { + private static final long EARLY_START_MINIMUM_TIME_MS = 250; private final View mDecorView; private final TaskSnapshotWindow mTaskSnapshotWindow; private SplashScreenView mContentView; private boolean mSetSplashScreen; + private long mContentCreateTime; StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow) { mDecorView = decorView; @@ -503,11 +455,12 @@ public class StartingSurfaceDrawer { return; } mContentView = splashScreenView; + mContentCreateTime = SystemClock.uptimeMillis(); mSetSplashScreen = true; } - private boolean isWaitForContent() { - return mDecorView != null && !mSetSplashScreen; + boolean isEarlyExit() { + return SystemClock.uptimeMillis() - mContentCreateTime < EARLY_START_MINIMUM_TIME_MS; } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index 5eb7071fbd63..b6ca86989690 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -25,17 +25,25 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK; import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING; import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; + import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; +import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import android.view.SurfaceControl; import android.window.StartingWindowInfo; import android.window.TaskOrganizer; import android.window.TaskSnapshot; +import androidx.annotation.BinderThread; + +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TransactionPool; import java.util.function.BiConsumer; @@ -55,7 +63,7 @@ import java.util.function.BiConsumer; * constructor to keep everything synchronized. * @hide */ -public class StartingWindowController { +public class StartingWindowController implements RemoteCallable<StartingWindowController> { private static final String TAG = StartingWindowController.class.getSimpleName(); static final boolean DEBUG_SPLASH_SCREEN = false; static final boolean DEBUG_TASK_SNAPSHOT = false; @@ -65,10 +73,18 @@ public class StartingWindowController { private BiConsumer<Integer, Integer> mTaskLaunchingCallback; private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl(); + private final Context mContext; private final ShellExecutor mSplashScreenExecutor; + // For Car Launcher public StartingWindowController(Context context, ShellExecutor splashScreenExecutor) { - mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor); + this(context, splashScreenExecutor, new TransactionPool()); + } + + public StartingWindowController(Context context, ShellExecutor splashScreenExecutor, + TransactionPool pool) { + mContext = context; + mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, pool); mSplashScreenExecutor = splashScreenExecutor; } @@ -79,6 +95,16 @@ public class StartingWindowController { return mImpl; } + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mSplashScreenExecutor; + } + private static class StartingTypeChecker { TaskSnapshot mSnapshot; @@ -112,7 +138,8 @@ public class StartingWindowController { + " allowTaskSnapshot " + allowTaskSnapshot + " activityCreated " + activityCreated); } - if (newTask || !processRunning || (taskSwitch && !activityCreated)) { + if ((newTask || !processRunning || (taskSwitch && !activityCreated)) + && windowInfo.taskInfo.topActivityType != ACTIVITY_TYPE_HOME) { return STARTING_WINDOW_TYPE_SPLASH_SCREEN; } if (taskSwitch && allowTaskSnapshot) { @@ -176,56 +203,121 @@ public class StartingWindowController { /** * Called when a task need a starting window. */ - void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { - final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo); - final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo; - if (mTaskLaunchingCallback != null) { - mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType); - } - if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) { - mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken); - } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) { - final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot; - mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot); - } - // If prefer don't show, then don't show! + public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { + mSplashScreenExecutor.execute(() -> { + final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo); + final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo; + if (mTaskLaunchingCallback != null) { + mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType); + } + if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) { + mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken); + } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) { + final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot; + mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot); + } + // If prefer don't show, then don't show! + }); } - void copySplashScreenView(int taskId) { - mStartingSurfaceDrawer.copySplashScreenView(taskId); + public void copySplashScreenView(int taskId) { + mSplashScreenExecutor.execute(() -> { + mStartingSurfaceDrawer.copySplashScreenView(taskId); + }); } /** * Called when the content of a task is ready to show, starting window can be removed. */ - void removeStartingWindow(int taskId) { - mStartingSurfaceDrawer.removeStartingWindow(taskId); + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { + mSplashScreenExecutor.execute(() -> { + mStartingSurfaceDrawer.removeStartingWindow(taskId, leash, frame, playRevealAnimation); + }); } + /** + * The interface for calls from outside the Shell, within the host process. + */ private class StartingSurfaceImpl implements StartingSurface { + private IStartingWindowImpl mIStartingWindow; @Override - public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { - mSplashScreenExecutor.execute(() -> - StartingWindowController.this.addStartingWindow(windowInfo, appToken)); + public IStartingWindowImpl createExternalInterface() { + if (mIStartingWindow != null) { + mIStartingWindow.invalidate(); + } + mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this); + return mIStartingWindow; } + } - @Override - public void removeStartingWindow(int taskId) { - mSplashScreenExecutor.execute(() -> - StartingWindowController.this.removeStartingWindow(taskId)); + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IStartingWindowImpl extends IStartingWindow.Stub { + private StartingWindowController mController; + private IStartingWindowListener mListener; + private final BiConsumer<Integer, Integer> mStartingWindowListener = + this::notifyIStartingWindowListener; + private final IBinder.DeathRecipient mListenerDeathRecipient = + new IBinder.DeathRecipient() { + @Override + @BinderThread + public void binderDied() { + final StartingWindowController controller = mController; + controller.getRemoteCallExecutor().execute(() -> { + mListener = null; + controller.setStartingWindowListener(null); + }); + } + }; + + public IStartingWindowImpl(StartingWindowController controller) { + mController = controller; } - @Override - public void copySplashScreenView(int taskId) { - mSplashScreenExecutor.execute(() -> - StartingWindowController.this.copySplashScreenView(taskId)); + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; } @Override - public void setStartingWindowListener(BiConsumer<Integer, Integer> listener) { - mSplashScreenExecutor.execute(() -> - StartingWindowController.this.setStartingWindowListener(listener)); + public void setStartingWindowListener(IStartingWindowListener listener) { + executeRemoteCallWithTaskPermission(mController, "setStartingWindowListener", + (controller) -> { + if (mListener != null) { + // Reset the old death recipient + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } + if (listener != null) { + try { + listener.asBinder().linkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death"); + return; + } + } + mListener = listener; + controller.setStartingWindowListener(mStartingWindowListener); + }); + } + + private void notifyIStartingWindowListener(int taskId, int supportedType) { + if (mListener == null) { + return; + } + + try { + mListener.onTaskLaunching(taskId, supportedType); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify task launching", e); + } } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl new file mode 100644 index 000000000000..dffc700a3690 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import android.window.IRemoteTransition; +import android.window.TransitionFilter; + +/** + * Interface that is exposed to remote callers to manipulate the transitions feature. + */ +interface IShellTransitions { + + /** + * Registers a remote transition handler. + */ + oneway void registerRemote(in TransitionFilter filter, + in IRemoteTransition remoteTransition) = 1; + + /** + * Unregisters a remote transition handler. + */ + oneway void unregisterRemote(in IRemoteTransition remoteTransition) = 2; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index ac93a17b4014..9667f4b6a8c5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -16,6 +16,8 @@ package com.android.wm.shell.transition; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; + import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; @@ -23,6 +25,7 @@ import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; +import android.util.Slog; import android.view.SurfaceControl; import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; @@ -31,6 +34,8 @@ import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; +import androidx.annotation.BinderThread; + import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -42,6 +47,8 @@ import java.util.ArrayList; * if the request includes a specific remote. */ public class RemoteTransitionHandler implements Transitions.TransitionHandler { + private static final String TAG = "RemoteTransitionHandler"; + private final ShellExecutor mMainExecutor; /** Includes remotes explicitly requested by, eg, ActivityOptions */ @@ -51,15 +58,33 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { private final ArrayList<Pair<TransitionFilter, IRemoteTransition>> mFilters = new ArrayList<>(); + private final IBinder.DeathRecipient mTransitionDeathRecipient = + new IBinder.DeathRecipient() { + @Override + @BinderThread + public void binderDied() { + mMainExecutor.execute(() -> { + mFilters.clear(); + }); + } + }; + RemoteTransitionHandler(@NonNull ShellExecutor mainExecutor) { mMainExecutor = mainExecutor; } void addFiltered(TransitionFilter filter, IRemoteTransition remote) { + try { + remote.asBinder().linkToDeath(mTransitionDeathRecipient, 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death"); + return; + } mFilters.add(new Pair<>(filter, remote)); } void removeFiltered(IRemoteTransition remote) { + remote.asBinder().unlinkToDeath(mTransitionDeathRecipient, 0 /* flags */); for (int i = mFilters.size() - 1; i >= 0; --i) { if (mFilters.get(i).second == remote) { mFilters.remove(i); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java index 85bbf74d56b9..bc42c6e2f12c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java @@ -26,7 +26,15 @@ import com.android.wm.shell.common.annotations.ExternalThread; * Interface to manage remote transitions. */ @ExternalThread -public interface RemoteTransitions { +public interface ShellTransitions { + + /** + * Returns a binder that can be passed to an external process to manipulate remote transitions. + */ + default IShellTransitions createExternalInterface() { + return null; + } + /** * Registers a remote transition. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 677db10d07a7..ca1b53d4d46b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -23,6 +23,8 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; @@ -51,6 +53,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ExternalThread; @@ -60,7 +63,7 @@ import java.util.ArrayList; import java.util.Arrays; /** Plays transition animations */ -public class Transitions { +public class Transitions implements RemoteCallable<Transitions> { static final String TAG = "ShellTransitions"; /** Set to {@code true} to enable shell transitions. */ @@ -73,7 +76,7 @@ public class Transitions { private final ShellExecutor mAnimExecutor; private final TransitionPlayerImpl mPlayerImpl; private final RemoteTransitionHandler mRemoteTransitionHandler; - private final RemoteTransitionImpl mImpl = new RemoteTransitionImpl(); + private final ShellTransitionImpl mImpl = new ShellTransitionImpl(); /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); @@ -87,10 +90,6 @@ public class Transitions { /** Keeps track of currently tracked transitions and all the animations associated with each */ private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>(); - public static RemoteTransitions asRemoteTransitions(Transitions transitions) { - return transitions.mImpl; - } - public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull Context context, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { @@ -126,6 +125,20 @@ public class Transitions { mRemoteTransitionHandler = null; } + public ShellTransitions asRemoteTransitions() { + return mImpl; + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + private void dispatchAnimScaleSetting(float scale) { for (int i = mHandlers.size() - 1; i >= 0; --i) { mHandlers.get(i).setAnimScaleSetting(scale); @@ -134,8 +147,8 @@ public class Transitions { /** Create an empty/non-registering transitions object for system-ui tests. */ @VisibleForTesting - public static RemoteTransitions createEmptyForTesting() { - return new RemoteTransitions() { + public static ShellTransitions createEmptyForTesting() { + return new ShellTransitions() { @Override public void registerRemote(@androidx.annotation.NonNull TransitionFilter filter, @androidx.annotation.NonNull IRemoteTransition remoteTransition) { @@ -426,24 +439,74 @@ public class Transitions { } } + /** + * The interface for calls from outside the Shell, within the host process. + */ @ExternalThread - private class RemoteTransitionImpl implements RemoteTransitions { + private class ShellTransitionImpl implements ShellTransitions { + private IShellTransitionsImpl mIShellTransitions; + + @Override + public IShellTransitions createExternalInterface() { + if (mIShellTransitions != null) { + mIShellTransitions.invalidate(); + } + mIShellTransitions = new IShellTransitionsImpl(Transitions.this); + return mIShellTransitions; + } + @Override public void registerRemote(@NonNull TransitionFilter filter, @NonNull IRemoteTransition remoteTransition) { mMainExecutor.execute(() -> { - Transitions.this.registerRemote(filter, remoteTransition); + mRemoteTransitionHandler.addFiltered(filter, remoteTransition); }); } @Override public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) { mMainExecutor.execute(() -> { - Transitions.this.unregisterRemote(remoteTransition); + mRemoteTransitionHandler.removeFiltered(remoteTransition); }); } } + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IShellTransitionsImpl extends IShellTransitions.Stub { + private Transitions mTransitions; + + IShellTransitionsImpl(Transitions transitions) { + mTransitions = transitions; + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mTransitions = null; + } + + @Override + public void registerRemote(@NonNull TransitionFilter filter, + @NonNull IRemoteTransition remoteTransition) { + executeRemoteCallWithTaskPermission(mTransitions, "registerRemote", + (transitions) -> { + transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition); + }); + } + + @Override + public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) { + executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote", + (transitions) -> { + transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition); + }); + } + } + private class SettingsObserver extends ContentObserver { SettingsObserver() { diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml index f06d57c6c789..ad4ccc0288ad 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml @@ -11,6 +11,8 @@ <option name="force-skip-system-props" value="true" /> <!-- set WM tracing verbose level to all --> <option name="run-command" value="cmd window tracing level all" /> + <!-- set WM tracing to frame (avoid incomplete states) --> + <option name="run-command" value="cmd window tracing frame" /> <!-- restart launcher to activate TAPL --> <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" /> </target_preparer> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt index 90e71373b1fd..98ce2747d532 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt @@ -18,6 +18,8 @@ package com.android.wm.shell.flicker.apppairs import android.os.SystemClock import android.platform.test.annotations.Presubmit +import android.provider.Settings +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -25,6 +27,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.wm.shell.flicker.appPairsDividerIsInvisible import com.android.wm.shell.flicker.helpers.AppPairsHelper +import org.junit.After +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -32,11 +36,10 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test AppPairs launch. - * To run this test: `atest WMShellFlickerTests:AppPairsTest` - */ -/** - * Test cold launch app from launcher. + * Test cold launch app from launcher. When the device doesn't support non-resizable in multi window + * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs should not pair + * non-resizable apps. + * * To run this test: `atest WMShellFlickerTests:AppPairsTestCannotPairNonResizeableApps` */ @RequiresDevice @@ -46,6 +49,7 @@ import org.junit.runners.Parameterized class AppPairsTestCannotPairNonResizeableApps( testSpec: FlickerTestParameter ) : AppPairsTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { @@ -59,6 +63,32 @@ class AppPairsTestCannotPairNonResizeableApps( } } + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 1) { + // Not support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } + + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Presubmit @Test fun appPairsDividerIsInvisible() = testSpec.appPairsDividerIsInvisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt index dc51b4fb5a9e..63e9a787aa17 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt @@ -56,6 +56,14 @@ class AppPairsTestPairPrimaryAndSecondaryApps( } } + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Presubmit @Test fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt new file mode 100644 index 000000000000..1e3595c17f48 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.apppairs + +import android.os.SystemClock +import android.platform.test.annotations.Presubmit +import android.provider.Settings +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.appPairsDividerIsVisible +import com.android.wm.shell.flicker.helpers.AppPairsHelper +import org.junit.After +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launch app from launcher. When the device supports non-resizable in multi window + * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs can pair + * non-resizable apps. + * + * To run this test: `atest WMShellFlickerTests:AppPairsTestSupportPairNonResizeableApps` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class AppPairsTestSupportPairNonResizeableApps( + testSpec: FlickerTestParameter +) : AppPairsTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + transitions { + nonResizeableApp?.launchViaIntent(wmHelper) + // TODO pair apps through normal UX flow + executeShellCommand( + composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true)) + SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + } + } + + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 0) { + // Support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } + + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + + @Presubmit + @Test + fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() + + @Presubmit + @Test + fun bothAppWindowVisible() { + val nonResizeableApp = nonResizeableApp + require(nonResizeableApp != null) { + "Non resizeable app not initialized" + } + testSpec.assertWmEnd { + isVisible(nonResizeableApp.defaultWindowName) + isVisible(primaryApp.defaultWindowName) + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + repetitions = AppPairsHelper.TEST_REPETITIONS) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt index 5bb9b2f8b8ca..234dda448cc8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt @@ -61,6 +61,14 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( } } + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Presubmit @Test fun appPairsDividerIsInvisible() = testSpec.appPairsDividerIsInvisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt index 91e080f65550..134d00be73e8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt @@ -17,22 +17,27 @@ package com.android.wm.shell.flicker.apppairs import android.app.Instrumentation +import android.content.Context import android.platform.test.annotations.Presubmit import android.system.helpers.ActivityHelper import android.util.Log +import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import com.android.compatibility.common.util.SystemUtil import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.isRotated import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -42,6 +47,7 @@ import java.io.IOException abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) { protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + protected val context: Context = instrumentation.context protected val isRotated = testSpec.config.startRotation.isRotated() protected val activityHelper = ActivityHelper.getInstance() protected val appPairsHelper = AppPairsHelper(instrumentation, @@ -134,17 +140,39 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) @Presubmit @Test - open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + open fun navBarLayerIsAlwaysVisible() { + testSpec.navBarLayerIsAlwaysVisible() + } + + @Presubmit + @Test + open fun statusBarLayerIsAlwaysVisible() { + testSpec.statusBarLayerIsAlwaysVisible() + } + + @Presubmit + @Test + open fun navBarWindowIsAlwaysVisible() { + testSpec.navBarWindowIsAlwaysVisible() + } @Presubmit @Test - open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + open fun statusBarWindowIsAlwaysVisible() { + testSpec.statusBarWindowIsAlwaysVisible() + } @Presubmit @Test - open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + open fun navBarLayerRotatesAndScales() { + testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, + testSpec.config.endRotation) + } @Presubmit @Test - open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + open fun statusBarLayerRotatesScales() { + testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, + testSpec.config.endRotation) + } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt index 5f003ba62b2d..d341bb1e6aa9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt @@ -27,8 +27,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.wm.shell.flicker.appPairsDividerIsVisible import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisible import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisible @@ -75,16 +73,6 @@ class RotateTwoLaunchedAppsInAppPairsMode( @Test fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, - testSpec.config.endRotation) - - @Presubmit - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, - testSpec.config.endRotation) - @FlakyTest(bugId = 172776659) @Test fun appPairsPrimaryBoundsIsVisible() = diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt index d4792088ac31..3bf0296fee20 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt @@ -27,16 +27,13 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.appPairsDividerIsVisible import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisible import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisible import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -67,20 +64,10 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, - testSpec.config.endRotation) - - @Presubmit - @Test fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() @Presubmit @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales( - Surface.ROTATION_0, testSpec.config.endRotation) - - @Presubmit - @Test override fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt index 75c33c671008..033322786d8f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt @@ -23,13 +23,7 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.WindowUtils import com.android.wm.shell.flicker.helpers.FixedAppHelper -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -48,7 +42,6 @@ class EnterExitPipTest( testSpec: FlickerTestParameter ) : PipTransition(testSpec) { private val testApp = FixedAppHelper(instrumentation) - private val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true) { @@ -84,14 +77,6 @@ class EnterExitPipTest( @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test fun showBothAppLayersThenHidePip() { testSpec.assertLayers { isVisible(testApp.defaultWindowName) @@ -118,14 +103,6 @@ class EnterExitPipTest( } } - @Presubmit - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index 2f08db1b7d0a..4847c98f54e9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -24,14 +25,6 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.startRotation import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -57,15 +50,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun pipWindowBecomesVisible() { + fun pipAppWindowAlwaysVisible() { testSpec.assertWm { this.showsAppWindow(pipApp.defaultWindowName) } @@ -73,34 +58,37 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit - @Test fun pipLayerBecomesVisible() { testSpec.assertLayers { this.isVisible(pipApp.launcherName) } } + @Postsubmit + @Test + fun pipWindowBecomesVisible() { + testSpec.assertWm { + invoke("pipWindowIsNotVisible") { !it.wmState.hasPipWindow() } + .then() + .invoke("pipWindowIsVisible") { it.wmState.hasPipWindow() } + } + } + + @FlakyTest(bugId = 140855415) + @Test + override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible() + @FlakyTest(bugId = 140855415) @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() @FlakyTest(bugId = 140855415) @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() @FlakyTest(bugId = 140855415) @Test - fun noUncoveredRegions() = - testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) + override fun noUncoveredRegions() = super.noUncoveredRegions() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index 9011f1a9fb6a..ba88ee5751de 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -18,16 +18,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.view.Surface +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.helpers.FixedAppHelper import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT @@ -83,6 +80,14 @@ class EnterPipToOtherOrientationTest( } } + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Presubmit @Test fun pipAppWindowIsAlwaysOnTop() { @@ -109,14 +114,6 @@ class EnterPipToOtherOrientationTest( @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test fun pipAppLayerHidesTestApp() { testSpec.assertLayersStart { coversExactly(startingBounds, pipApp.defaultWindowName) @@ -132,14 +129,6 @@ class EnterPipToOtherOrientationTest( } } - @Presubmit - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt index 3e331761f767..eae7e973711c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt @@ -24,14 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import org.junit.Test import org.junit.runners.Parameterized @@ -52,22 +45,6 @@ abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransitio @Presubmit @Test - open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @Presubmit - @Test - open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test open fun pipWindowBecomesInvisible() { testSpec.assertWm { this.showsAppWindow(PIP_WINDOW_TITLE) @@ -86,21 +63,6 @@ abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransitio } } - @Presubmit - @Test - open fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit - @Test - open fun noUncoveredRegions() = - testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit - @Test - open fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - @FlakyTest(bugId = 151179149) @Test open fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt index 97afc65b8b61..3309e10f61a6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt @@ -24,17 +24,11 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.helpers.ImeAppHelper import com.android.wm.shell.flicker.helpers.FixedAppHelper import com.android.server.wm.flicker.repetitions -import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.removeAllTasksButHome import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP import org.junit.FixMethodOrder @@ -55,7 +49,6 @@ import org.junit.runners.Parameterized class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val imeApp = ImeAppHelper(instrumentation) private val testApp = FixedAppHelper(instrumentation) - private val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { @@ -105,11 +98,11 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Postsubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() @Postsubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() @Postsubmit @Test @@ -130,11 +123,11 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Postsubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible() @Postsubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible() companion object { const val TEST_REPETITIONS = 2 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt index 4c95da284d9e..d011419150e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt @@ -24,8 +24,6 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.google.common.truth.Truth import org.junit.FixMethodOrder import org.junit.Test @@ -59,11 +57,11 @@ class PipMovesInAllApps(testSpec: FlickerTestParameter) : PipTransition(testSpec @Postsubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() @Postsubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() @Postsubmit @Test @@ -71,6 +69,14 @@ class PipMovesInAllApps(testSpec: FlickerTestParameter) : PipTransition(testSpec @Postsubmit @Test + fun pipLayerInsideDisplay() { + testSpec.assertLayersStart { + coversAtMost(displayBounds, pipApp.defaultWindowName) + } + } + + @Postsubmit + @Test fun pipWindowMovesUp() = testSpec.assertWmEnd { val initialState = this.trace?.first()?.wmState ?: error("Trace should not be empty") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index df835d21e73f..49a1055af4a0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -29,10 +29,6 @@ import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.startRotation import com.android.wm.shell.flicker.helpers.FixedAppHelper -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.statusBarLayerRotatesScales @@ -77,34 +73,18 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, + override fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, testSpec.config.endRotation, allStates = false) - @Presubmit - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - @FlakyTest(bugId = 140855415) @Test - fun navBarLayerRotatesAndScales() = + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, testSpec.config.endRotation) @FlakyTest(bugId = 140855415) @Test - fun statusBarLayerRotatesScales() = + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, testSpec.config.endRotation) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt index 1bb1d2861f3f..945a20b28ff0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt @@ -26,14 +26,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -68,22 +61,6 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test fun appReplacesPipWindow() { testSpec.assertWm { this.showsAppWindow(PIP_WINDOW_TITLE) @@ -94,11 +71,6 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit - @Test fun appReplacesPipLayer() { testSpec.assertLayers { this.isVisible(PIP_WINDOW_TITLE) @@ -107,15 +79,13 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { } } - @Presubmit - @Test - fun noUncoveredRegions() = - testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit + @FlakyTest @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + fun testAppCoversFullScreen() { + testSpec.assertLayersStart { + coversExactly(displayBounds, pipApp.defaultWindowName) + } + } @FlakyTest(bugId = 151179149) @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index b0a9afef9215..7dc7e7d25b79 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -18,27 +18,37 @@ package com.android.wm.shell.flicker.pip import android.app.Instrumentation import android.content.Intent +import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.isRotated import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerRotatesScales +import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.helpers.PipAppHelper import com.android.wm.shell.flicker.removeAllTasksButHome import com.android.wm.shell.flicker.testapp.Components +import org.junit.Test abstract class PipTransition(protected val testSpec: FlickerTestParameter) { protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() protected val isRotated = testSpec.config.startRotation.isRotated() protected val pipApp = PipAppHelper(instrumentation) + protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit - // Helper class to process test actions by broadcast. protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) { private fun createIntentWithAction(broadcastAction: String): Intent { @@ -148,4 +158,35 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { extraSpec(this, configuration) } } + + @Presubmit + @Test + open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + + @Presubmit + @Test + open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + + @Presubmit + @Test + open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + + @Presubmit + @Test + open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + + @Presubmit + @Test + open fun navBarLayerRotatesAndScales() = + testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + + @Presubmit + @Test + open fun statusBarLayerRotatesScales() = + testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + + @Presubmit + @Test + open fun noUncoveredRegions() = + testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 7916ce59af52..67e1768f3a9f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -25,10 +26,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP @@ -83,6 +80,14 @@ class SetRequestedOrientationWhilePinnedTest( } } + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Presubmit @Test fun pipWindowInsideDisplay() { @@ -101,20 +106,18 @@ class SetRequestedOrientationWhilePinnedTest( @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test fun pipLayerInsideDisplay() { testSpec.assertLayersStart { coversAtMost(startingBounds, pipApp.defaultWindowName) } } + @Postsubmit + @Test + fun pipAlwaysVisible() = testSpec.assertWm { + this.showsAppWindow(pipApp.windowName) + } + @Presubmit @Test fun pipAppLayerCoversFullScreen() { @@ -123,14 +126,6 @@ class SetRequestedOrientationWhilePinnedTest( } } - @Presubmit - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index c1c4c6dd08d7..2f2bbba11646 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -205,7 +205,7 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); } @@ -217,12 +217,12 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); reset(mSplitScreenStarter); mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } @@ -234,12 +234,12 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); reset(mSplitScreenStarter); mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } @@ -251,7 +251,7 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); } @@ -263,7 +263,7 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); } @@ -276,13 +276,13 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); reset(mSplitScreenStarter); // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } @@ -295,13 +295,13 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); reset(mSplitScreenStarter); // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index a531ef58725d..207db9e80511 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package unittest.src.com.android.wm.shell.startingsurface; +package com.android.wm.shell.startingsurface; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -33,11 +33,12 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.testing.TestableContext; -import android.view.Choreographer; +import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; import android.view.WindowMetrics; @@ -49,7 +50,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.startingsurface.StartingSurfaceDrawer; +import com.android.wm.shell.common.TransactionPool; import org.junit.Before; import org.junit.Test; @@ -68,7 +69,7 @@ public class StartingSurfaceDrawerTests { @Mock private WindowManager mMockWindowManager; @Mock - private static Choreographer sFakeChoreographer; + private TransactionPool mTransactionPool; TestStartingSurfaceDrawer mStartingSurfaceDrawer; @@ -76,13 +77,9 @@ public class StartingSurfaceDrawerTests { int mAddWindowForTask = 0; int mViewThemeResId; - TestStartingSurfaceDrawer(Context context, ShellExecutor executor) { - super(context, executor); - } - - @Override - protected void initChoreographer() { - mChoreographer = sFakeChoreographer; + TestStartingSurfaceDrawer(Context context, ShellExecutor animExecutor, + TransactionPool pool) { + super(context, animExecutor, pool); } @Override @@ -95,7 +92,8 @@ public class StartingSurfaceDrawerTests { } @Override - protected void removeWindowSynced(int taskId) { + protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { // listen for removeView if (mAddWindowForTask == taskId) { mAddWindowForTask = 0; @@ -123,7 +121,8 @@ public class StartingSurfaceDrawerTests { doNothing().when(mMockWindowManager).addView(any(), any()); mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(context, - new HandlerExecutor(new Handler(Looper.getMainLooper())))); + new HandlerExecutor(new Handler(Looper.getMainLooper())), + mTransactionPool)); } @Test @@ -137,9 +136,9 @@ public class StartingSurfaceDrawerTests { verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any()); assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId); - mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId); + mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId, null, null, false); waitHandlerIdle(mainLoop); - verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId)); + verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId), any(), any(), eq(false)); assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java index 27e5f51d88b8..b908df20d3c0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package unittest.src.com.android.wm.shell.startingsurface; +package com.android.wm.shell.startingsurface; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -48,7 +48,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.wm.shell.TestShellExecutor; -import com.android.wm.shell.startingsurface.TaskSnapshotWindow; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index aba0f1b47673..63b831de5da1 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -97,8 +97,8 @@ cc_library { "libincfs", "libutils", "libz", - "libziparchive", ], + static_libs: ["libziparchive_for_incfs"], static: { enabled: false, }, diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index c0ef7be8b673..7e45f952d389 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -226,8 +226,6 @@ void AssetManager2::BuildDynamicRefTable() { } void AssetManager2::DumpToLog() const { - base::ScopedLogSeverity _log(base::INFO); - LOG(INFO) << base::StringPrintf("AssetManager2(this=%p)", this); std::string list; @@ -1721,7 +1719,6 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& o) { } void Theme::Dump() const { - base::ScopedLogSeverity _log(base::INFO); LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_); for (int p = 0; p < packages_.size(); p++) { diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index d663c52b2c08..607ef72df96a 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -388,11 +388,10 @@ cc_defaults { "liblog", "libminikin", "libz", - "libziparchive", "libjpeg", ], - static_libs: ["libnativehelper_lazy"], + static_libs: ["libnativehelper_lazy", "libziparchive_for_incfs"], target: { android: { diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 9793300b406d..800c58095041 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -576,6 +576,7 @@ void CanvasContext::draw() { if (requireSwap) { if (mExpectSurfaceStats) { + reportMetricsWithPresentTime(); std::lock_guard lock(mLast4FrameInfosMutex); std::pair<FrameInfo*, int64_t>& next = mLast4FrameInfos.next(); next.first = mCurrentFrameInfo; @@ -656,8 +657,6 @@ void CanvasContext::onSurfaceStatsAvailable(void* context, ASurfaceControl* cont } } - instance->reportMetricsWithPresentTime(); - if (frameInfo != nullptr) { if (gpuCompleteTime == -1) { gpuCompleteTime = frameInfo->get(FrameInfoIndex::SwapBuffersCompleted); diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h index 09c6a4fdf50d..a8f2d9a28d67 100644 --- a/libs/hwui/utils/PaintUtils.h +++ b/libs/hwui/utils/PaintUtils.h @@ -52,18 +52,10 @@ public: return mode == SkBlendMode::kSrcOver || mode == SkBlendMode::kSrc; } - static bool isBlendedShader(const SkShader* shader) { - if (shader == nullptr) { - return false; - } - return !shader->isOpaque(); - } + static bool isBlendedShader(const SkShader* shader) { return shader && !shader->isOpaque(); } static bool isBlendedColorFilter(const SkColorFilter* filter) { - if (filter == nullptr) { - return false; - } - return (filter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag) == 0; + return filter && !filter->isAlphaUnchanged(); } static inline SkBlendMode getBlendModeDirect(const SkPaint* paint) { diff --git a/media/Android.bp b/media/Android.bp index 9268b22a929a..a66236e6f4ea 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -27,6 +27,9 @@ aidl_interface { aidl_interface { name: "media_permission-aidl", unstable: true, + host_supported: true, + vendor_available: true, + double_loadable: true, local_include_dir: "aidl", srcs: [ "aidl/android/media/permission/Identity.aidl", diff --git a/media/aidl/android/media/permission/Identity.aidl b/media/aidl/android/media/permission/Identity.aidl index 361497d59ea9..36389047cee8 100644 --- a/media/aidl/android/media/permission/Identity.aidl +++ b/media/aidl/android/media/permission/Identity.aidl @@ -22,11 +22,11 @@ package android.media.permission; */ parcelable Identity { /** Linux user ID. */ - int uid; + int uid = -1; /** Linux process ID. */ - int pid; + int pid = -1; /** Package name. If null, the first package owned by the given uid will be assumed. */ - @nullable String packageName; + @nullable @utf8InCpp String packageName; /** Attribution tag. Mostly used for diagnostic purposes. */ - @nullable String attributionTag; + @nullable @utf8InCpp String attributionTag; } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 1cef0922a48e..e8e263147c6e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6729,7 +6729,7 @@ public class AudioManager { */ public Map<Integer, Boolean> getSurroundFormats() { Map<Integer, Boolean> surroundFormats = new HashMap<>(); - int status = AudioSystem.getSurroundFormats(surroundFormats, false); + int status = AudioSystem.getSurroundFormats(surroundFormats); if (status != AudioManager.SUCCESS) { // fail and bail! Log.e(TAG, "getSurroundFormats failed:" + status); @@ -6762,20 +6762,17 @@ public class AudioManager { /** * @hide * Returns all surround formats that are reported by the connected HDMI device. - * The keys are not affected by calling setSurroundFormatEnabled(), and the values - * are not affected by calling setSurroundFormatEnabled() when in AUTO mode. - * This information can used to show the AUTO setting for SurroundSound. + * The return values are not affected by calling setSurroundFormatEnabled. * - * @return a map where the key is a surround format and - * the value indicates the surround format is enabled or not + * @return a list of surround formats */ - public Map<Integer, Boolean> getReportedSurroundFormats() { - Map<Integer, Boolean> reportedSurroundFormats = new HashMap<>(); - int status = AudioSystem.getSurroundFormats(reportedSurroundFormats, true); + public ArrayList<Integer> getReportedSurroundFormats() { + ArrayList<Integer> reportedSurroundFormats = new ArrayList<>(); + int status = AudioSystem.getReportedSurroundFormats(reportedSurroundFormats); if (status != AudioManager.SUCCESS) { // fail and bail! Log.e(TAG, "getReportedSurroundFormats failed:" + status); - return new HashMap<Integer, Boolean>(); // Always return a map. + return new ArrayList<Integer>(); // Always return a list. } return reportedSurroundFormats; } @@ -7087,26 +7084,22 @@ public class AudioManager { * <pre class="prettyprint"> * // Get an AudioManager instance * AudioManager audioManager = Context.getSystemService(AudioManager.class); - * try { - * AudioDeviceInfo speakerDevice = null; - * List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices(); - * for (AudioDeviceInfo device : devices) { - * if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { - * speakerDevice = device; - * break; - * } + * AudioDeviceInfo speakerDevice = null; + * List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices(); + * for (AudioDeviceInfo device : devices) { + * if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { + * speakerDevice = device; + * break; * } - * if (speakerDevice != null) { - * // Turn speakerphone ON. - * boolean result = audioManager.setCommunicationDevice(speakerDevice); - * if (!result) { - * // Handle error. - * } - * // Turn speakerphone OFF. - * audioManager.clearCommunicationDevice(); + * } + * if (speakerDevice != null) { + * // Turn speakerphone ON. + * boolean result = audioManager.setCommunicationDevice(speakerDevice); + * if (!result) { + * // Handle error. * } - * } catch (IllegalArgumentException e) { - * // Handle exception. + * // Turn speakerphone OFF. + * audioManager.clearCommunicationDevice(); * } * </pre> * @param device the requested audio device. diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index bf04b660425b..d7112d6dfa63 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.permission.PermissionUtil.myIdentity; + import android.annotation.CallbackExecutor; import android.annotation.FloatRange; import android.annotation.IntDef; @@ -26,9 +28,11 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; import android.media.MediaRecorder.Source; import android.media.audiopolicy.AudioMix; import android.media.audiopolicy.AudioPolicy; +import android.media.permission.Identity; import android.media.projection.MediaProjection; import android.os.Binder; import android.os.Build; @@ -54,6 +58,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -352,6 +357,32 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int sessionId) throws IllegalArgumentException { + this(attributes, format, bufferSizeInBytes, sessionId, ActivityThread.currentApplication()); + } + + /** + * @hide + * Class constructor with {@link AudioAttributes} and {@link AudioFormat}. + * @param attributes a non-null {@link AudioAttributes} instance. Use + * {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the audio + * source for this instance. + * @param format a non-null {@link AudioFormat} instance describing the format of the data + * that will be recorded through this AudioRecord. See {@link AudioFormat.Builder} for + * configuring the audio format parameters such as encoding, channel mask and sample rate. + * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written + * to during the recording. New audio data can be read from this buffer in smaller chunks + * than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum + * required buffer size for the successful creation of an AudioRecord instance. Using values + * smaller than getMinBufferSize() will result in an initialization failure. + * @param sessionId ID of audio session the AudioRecord must be attached to, or + * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at construction + * time. See also {@link AudioManager#generateAudioSessionId()} to obtain a session ID before + * construction. + * @param context An optional context to pull an attribution tag from. + * @throws IllegalArgumentException + */ + private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, + int sessionId, @Nullable Context context) throws IllegalArgumentException { mRecordingState = RECORDSTATE_STOPPED; if (attributes == null) { @@ -414,15 +445,21 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, audioBuffSizeCheck(bufferSizeInBytes); + Identity identity = myIdentity(context); + if (identity.packageName == null) { + // Command line utility + identity.packageName = "uid:" + Binder.getCallingUid(); + } + int[] sampleRate = new int[] {mSampleRate}; int[] session = new int[1]; session[0] = sessionId; //TODO: update native initialization when information about hardware init failure // due to capture device already open is available. - int initResult = native_setup( new WeakReference<AudioRecord>(this), + int initResult = native_setup(new WeakReference<AudioRecord>(this), mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat, mNativeBufferSizeInBytes, - session, getCurrentOpPackageName(), 0 /*nativeRecordInJavaObj*/); + session, identity, 0 /*nativeRecordInJavaObj*/); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing native AudioRecord object."); return; // with mState == STATE_UNINITIALIZED @@ -434,15 +471,6 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, mState = STATE_INITIALIZED; } - private String getCurrentOpPackageName() { - String opPackageName = ActivityThread.currentOpPackageName(); - if (opPackageName != null) { - return opPackageName; - } - // Command line utility - return "uid:" + Binder.getCallingUid(); - } - /** * A constructor which explicitly connects a Native (C++) AudioRecord. For use by * the AudioRecordRoutingProxy subclass. @@ -492,7 +520,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, 0 /*mAudioFormat*/, 0 /*mNativeBufferSizeInBytes*/, session, - ActivityThread.currentOpPackageName(), + myIdentity(null), nativeRecordInJavaObj); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing native AudioRecord object."); @@ -548,6 +576,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, private AudioPlaybackCaptureConfiguration mAudioPlaybackCaptureConfiguration; private AudioAttributes mAttributes; private AudioFormat mFormat; + private Context mContext; private int mBufferSizeInBytes; private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE; private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT; @@ -583,6 +612,18 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, } /** + * Sets the context the record belongs to. + * @param context a non-null {@link Context} instance + * @return the same Builder instance. + */ + public @NonNull Builder setContext(@NonNull Context context) { + Objects.requireNonNull(context); + // keep reference, we only copy the data when building + mContext = context; + return this; + } + + /** * @hide * To be only used by system components. Allows specifying non-public capture presets * @param attributes a non-null {@link AudioAttributes} instance that contains the capture @@ -793,7 +834,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * mFormat.getBytesPerSample(mFormat.getEncoding()); } final AudioRecord record = new AudioRecord( - mAttributes, mFormat, mBufferSizeInBytes, mSessionId); + mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext); if (record.getState() == STATE_UNINITIALIZED) { // release is not necessary throw new UnsupportedOperationException("Cannot create AudioRecord"); @@ -2035,15 +2076,32 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, // Native methods called from the Java side //-------------------- - @UnsupportedAppUsage - private native final int native_setup(Object audiorecord_this, + /** + * @deprecated Use native_setup that takes an Identity object + * @return + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, + publicAlternatives = "{@code AudioRecord.Builder}") + @Deprecated + private int native_setup(Object audiorecordThis, Object /*AudioAttributes*/ attributes, int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, int buffSizeInBytes, int[] sessionId, String opPackageName, - long nativeRecordInJavaObj); + long nativeRecordInJavaObj) { + Identity identity = myIdentity(null); + identity.packageName = opPackageName; + + return native_setup(audiorecordThis, attributes, sampleRate, channelMask, channelIndexMask, + audioFormat, buffSizeInBytes, sessionId, identity, nativeRecordInJavaObj); + } + + private native int native_setup(Object audiorecordThis, + Object /*AudioAttributes*/ attributes, + int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, + int buffSizeInBytes, int[] sessionId, Identity identity, long nativeRecordInJavaObj); // TODO remove: implementation calls directly into implementation of native_release() - private native final void native_finalize(); + private native void native_finalize(); /** * @hide diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index f1f6cb1f42be..8134d6f8352d 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1699,8 +1699,10 @@ public class AudioSystem public static native int getMicrophones(ArrayList<MicrophoneInfo> microphonesInfo); /** @hide */ - public static native int getSurroundFormats(Map<Integer, Boolean> surroundFormats, - boolean reported); + public static native int getSurroundFormats(Map<Integer, Boolean> surroundFormats); + + /** @hide */ + public static native int getReportedSurroundFormats(ArrayList<Integer> surroundFormats); /** * @hide diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 9176dae8609f..3de78bb9ef9f 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -18,6 +18,7 @@ package android.media; import static android.Manifest.permission.BIND_IMS_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.media.permission.PermissionUtil.myIdentity; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -34,6 +35,7 @@ import android.content.res.AssetFileDescriptor; import android.graphics.SurfaceTexture; import android.media.SubtitleController.Anchor; import android.media.SubtitleTrack.RenderingWidget; +import android.media.permission.Identity; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -53,6 +55,7 @@ import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; @@ -684,11 +687,14 @@ public class MediaPlayer extends PlayerBase mTimeProvider = new TimeProvider(this); mOpenSubtitleSources = new Vector<InputStream>(); + Identity identity = myIdentity(null); + // set the package name to empty if it was null + identity.packageName = TextUtils.emptyIfNull(identity.packageName); + /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. */ - native_setup(new WeakReference<MediaPlayer>(this), - getCurrentOpPackageName()); + native_setup(new WeakReference<MediaPlayer>(this), identity); baseRegisterPlayer(sessionId); } @@ -2471,7 +2477,7 @@ public class MediaPlayer extends PlayerBase private native final int native_setMetadataFilter(Parcel request); private static native final void native_init(); - private native void native_setup(Object mediaplayerThis, @NonNull String opPackageName); + private native void native_setup(Object mediaplayerThis, @NonNull Identity identity); private native final void native_finalize(); /** diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 49a4cc6239bb..87e1e5bdb9ce 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.permission.PermissionUtil.myIdentity; + import android.annotation.CallbackExecutor; import android.annotation.FloatRange; import android.annotation.IntDef; @@ -25,7 +27,9 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; import android.hardware.Camera; +import android.media.permission.Identity; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -48,6 +52,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -127,9 +132,21 @@ public class MediaRecorder implements AudioRouting, /** * Default constructor. + * + * @deprecated Use {@link #MediaRecorder(Context)} instead */ + @Deprecated public MediaRecorder() { + this(ActivityThread.currentApplication()); + } + /** + * Default constructor. + * + * @param context Context the recorder belongs to + */ + public MediaRecorder(@NonNull Context context) { + Objects.requireNonNull(context); Looper looper; if ((looper = Looper.myLooper()) != null) { mEventHandler = new EventHandler(this, looper); @@ -140,12 +157,11 @@ public class MediaRecorder implements AudioRouting, } mChannelCount = 1; - String packageName = ActivityThread.currentPackageName(); /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. */ - native_setup(new WeakReference<MediaRecorder>(this), packageName, - ActivityThread.currentOpPackageName()); + native_setup(new WeakReference<MediaRecorder>(this), + ActivityThread.currentPackageName(), myIdentity(context)); } /** @@ -1740,12 +1756,22 @@ public class MediaRecorder implements AudioRouting, @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private static native final void native_init(); - @UnsupportedAppUsage - private native final void native_setup(Object mediarecorder_this, - String clientName, String opPackageName) throws IllegalStateException; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, + publicAlternatives = "{@link MediaRecorder}") + private void native_setup(Object mediarecorderThis, + String clientName, String opPackageName) throws IllegalStateException { + Identity identity = myIdentity(null); + identity.packageName = opPackageName; + + native_setup(mediarecorderThis, clientName, identity); + } + + private native void native_setup(Object mediarecorderThis, + String clientName, Identity identity) + throws IllegalStateException; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private native final void native_finalize(); + private native void native_finalize(); @UnsupportedAppUsage private native void setParameter(String nameValuePair); diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index dc9c58ebf18c..b4db3055e58c 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -60,6 +60,7 @@ import java.util.stream.Collectors; public final class MediaRouter2 { private static final String TAG = "MR2"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final Object sSystemRouterLock = new Object(); private static final Object sRouterLock = new Object(); // The maximum time for the old routing controller available after transfer. @@ -67,8 +68,8 @@ public final class MediaRouter2 { // The manager request ID representing that no manager is involved. private static final long MANAGER_REQUEST_ID_NONE = MediaRoute2ProviderService.REQUEST_ID_NONE; - @GuardedBy("sRouterLock") - private static Map<String, MediaRouter2> sMediaRouter2Map = new ArrayMap<>(); + @GuardedBy("sSystemRouterLock") + private static Map<String, MediaRouter2> sSystemMediaRouter2Map = new ArrayMap<>(); private static MediaRouter2Manager sManager; @GuardedBy("sRouterLock") @@ -76,6 +77,7 @@ public final class MediaRouter2 { private final Context mContext; private final IMediaRouterService mMediaRouterService; + private final Object mLock = new Object(); private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords = new CopyOnWriteArrayList<>(); @@ -88,27 +90,29 @@ public final class MediaRouter2 { new CopyOnWriteArrayList<>(); private final String mClientPackageName; + private final ManagerCallback mManagerCallback; + private final String mPackageName; - @GuardedBy("sRouterLock") + @GuardedBy("mLock") final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>(); final RoutingController mSystemController; - @GuardedBy("sRouterLock") + @GuardedBy("mLock") private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY; // TODO: Make MediaRouter2 is always connected to the MediaRouterService. - @GuardedBy("sRouterLock") + @GuardedBy("mLock") MediaRouter2Stub mStub; - @GuardedBy("sRouterLock") + @GuardedBy("mLock") private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>(); private final AtomicInteger mNextRequestId = new AtomicInteger(1); final Handler mHandler; - @GuardedBy("sRouterLock") + @GuardedBy("mLock") private boolean mShouldUpdateRoutes = true; private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList(); private volatile OnGetControllerHintsListener mOnGetControllerHintsListener; @@ -130,6 +134,11 @@ public final class MediaRouter2 { /** * Gets an instance of the media router which controls the app's media routing. * Returns {@code null} if the given package name is invalid. + * <p> + * Note: For media routers created with this method, the discovery preference passed to + * {@link #registerRouteCallback} will have no effect. The callback will be called accordingly + * with the client app's discovery preference. Therefore, it is recommended to pass + * {@link RouteDiscoveryPreference#EMPTY} there. * * @param clientPackageName the package name of the app to control * @hide @@ -149,15 +158,17 @@ public final class MediaRouter2 { return null; } - synchronized (sRouterLock) { - MediaRouter2 instance = sMediaRouter2Map.get(clientPackageName); + synchronized (sSystemRouterLock) { + MediaRouter2 instance = sSystemMediaRouter2Map.get(clientPackageName); if (instance == null) { // TODO: Add permission check here using MODIFY_AUDIO_ROUTING. if (sManager == null) { sManager = MediaRouter2Manager.getInstance(context.getApplicationContext()); } instance = new MediaRouter2(context, clientPackageName); - sMediaRouter2Map.put(clientPackageName, instance); + sSystemMediaRouter2Map.put(clientPackageName, instance); + // TODO: Remove router instance once it is not needed. + instance.registerManagerCallback(); } return instance; } @@ -192,11 +203,14 @@ public final class MediaRouter2 { } mSystemController = new SystemRoutingController(currentSystemSessionInfo); + // Only used by system MediaRouter2. mClientPackageName = null; + mManagerCallback = null; } private MediaRouter2(Context context, String clientPackageName) { mClientPackageName = clientPackageName; + mManagerCallback = new ManagerCallback(); mContext = context; mMediaRouterService = null; mPackageName = null; @@ -220,8 +234,8 @@ public final class MediaRouter2 { } /** - * Gets the target package name of the app which this media router controls. - * This is only non-null when the router instance is created with the target package name. + * Gets the client package name of the app which this media router controls. + * This is only non-null when the router instance is created with the client package name. * * @see #getInstance(Context, String) * @hide @@ -245,6 +259,9 @@ public final class MediaRouter2 { Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(routeCallback, "callback must not be null"); Objects.requireNonNull(preference, "preference must not be null"); + if (isSystemRouter()) { + preference = RouteDiscoveryPreference.EMPTY; + } RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference); @@ -253,7 +270,11 @@ public final class MediaRouter2 { // is happening but it's okay because either this or the other registration should be done. mRouteCallbackRecords.addIfAbsent(record); - synchronized (sRouterLock) { + if (isSystemRouter()) { + return; + } + + synchronized (mLock) { if (mStub == null) { MediaRouter2Stub stub = new MediaRouter2Stub(); try { @@ -289,7 +310,11 @@ public final class MediaRouter2 { return; } - synchronized (sRouterLock) { + if (isSystemRouter()) { + return; + } + + synchronized (mLock) { if (mStub == null) { return; } @@ -326,22 +351,37 @@ public final class MediaRouter2 { } /** + * Gets the list of all discovered routes. + * This list includes the routes that are not related to the client app. + * <p> + * This will return an empty list for non-system media routers. + * + * @hide + */ + //@SystemApi + public List<MediaRoute2Info> getAllRoutes() { + if (isSystemRouter()) { + return sManager.getAllRoutes(); + } + return Collections.emptyList(); + } + + /** * Gets the unmodifiable list of {@link MediaRoute2Info routes} currently * known to the media router. * <p> * Please note that the list can be changed before callbacks are invoked. * </p> - * * @return the list of routes that contains at least one of the route features in discovery * preferences registered by the application */ @NonNull public List<MediaRoute2Info> getRoutes() { - if (mClientPackageName != null) { + if (isSystemRouter()) { return sManager.getAvailableRoutes(mClientPackageName); } - synchronized (sRouterLock) { + synchronized (mLock) { if (mShouldUpdateRoutes) { mShouldUpdateRoutes = false; @@ -449,7 +489,7 @@ public final class MediaRouter2 { * @see TransferCallback#onTransferFailure */ public void transferTo(@NonNull MediaRoute2Info route) { - if (mClientPackageName != null) { + if (isSystemRouter()) { sManager.selectRoute(mClientPackageName, route); return; } @@ -464,7 +504,7 @@ public final class MediaRouter2 { * controls the media routing, this method is a no-op. */ public void stop() { - if (mClientPackageName != null) { + if (isSystemRouter()) { List<RoutingSessionInfo> sessionInfos = sManager.getRoutingSessions(mClientPackageName); RoutingSessionInfo sessionToRelease = sessionInfos.get(sessionInfos.size() - 1); sManager.releaseSession(sessionToRelease); @@ -481,7 +521,7 @@ public final class MediaRouter2 { */ //@SystemApi public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) { - if (mClientPackageName != null) { + if (isSystemRouter()) { sManager.transfer(controller.getRoutingSessionInfo(), route); return; } @@ -490,7 +530,7 @@ public final class MediaRouter2 { Objects.requireNonNull(route, "route must not be null"); boolean routeFound; - synchronized (sRouterLock) { + synchronized (mLock) { // TODO: Check thread-safety routeFound = mRoutes.containsKey(route.getId()); } @@ -526,7 +566,7 @@ public final class MediaRouter2 { } MediaRouter2Stub stub; - synchronized (sRouterLock) { + synchronized (mLock) { stub = mStub; } if (stub != null) { @@ -576,7 +616,7 @@ public final class MediaRouter2 { public List<RoutingController> getControllers() { // TODO: Do not create the controller instances every time, // Instead, update the list using the sessions' ID and session related callbacks. - if (mClientPackageName != null) { + if (isSystemRouter()) { return sManager.getRoutingSessions(mClientPackageName).stream() .map(info -> new RoutingController(info)) .collect(Collectors.toList()); @@ -584,7 +624,7 @@ public final class MediaRouter2 { List<RoutingController> result = new ArrayList<>(); result.add(0, mSystemController); - synchronized (sRouterLock) { + synchronized (mLock) { result.addAll(mNonSystemRoutingControllers.values()); } return result; @@ -603,7 +643,7 @@ public final class MediaRouter2 { Objects.requireNonNull(route, "route must not be null"); MediaRouter2Stub stub; - synchronized (sRouterLock) { + synchronized (mLock) { stub = mStub; } if (stub != null) { @@ -627,7 +667,7 @@ public final class MediaRouter2 { List<MediaRoute2Info> removedRoutes = new ArrayList<>(); List<MediaRoute2Info> changedRoutes = new ArrayList<>(); - synchronized (sRouterLock) { + synchronized (mLock) { List<String> currentRoutesIds = currentRoutes.stream().map(MediaRoute2Info::getId) .collect(Collectors.toList()); @@ -685,7 +725,7 @@ public final class MediaRouter2 { void addRoutesOnHandler(List<MediaRoute2Info> routes) { List<MediaRoute2Info> addedRoutes = new ArrayList<>(); - synchronized (sRouterLock) { + synchronized (mLock) { for (MediaRoute2Info route : routes) { mRoutes.put(route.getId(), route); if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { @@ -701,7 +741,7 @@ public final class MediaRouter2 { void removeRoutesOnHandler(List<MediaRoute2Info> routes) { List<MediaRoute2Info> removedRoutes = new ArrayList<>(); - synchronized (sRouterLock) { + synchronized (mLock) { for (MediaRoute2Info route : routes) { mRoutes.remove(route.getId()); if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { @@ -717,7 +757,7 @@ public final class MediaRouter2 { void changeRoutesOnHandler(List<MediaRoute2Info> routes) { List<MediaRoute2Info> changedRoutes = new ArrayList<>(); - synchronized (sRouterLock) { + synchronized (mLock) { for (MediaRoute2Info route : routes) { mRoutes.put(route.getId(), route); if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { @@ -789,7 +829,7 @@ public final class MediaRouter2 { newController.setRoutingSessionInfo(sessionInfo); } else { newController = new RoutingController(sessionInfo); - synchronized (sRouterLock) { + synchronized (mLock) { mNonSystemRoutingControllers.put(newController.getId(), newController); } } @@ -812,7 +852,7 @@ public final class MediaRouter2 { } RoutingController matchingController; - synchronized (sRouterLock) { + synchronized (mLock) { matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId()); } @@ -840,7 +880,7 @@ public final class MediaRouter2 { } RoutingController matchingController; - synchronized (sRouterLock) { + synchronized (mLock) { matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId()); } @@ -868,7 +908,7 @@ public final class MediaRouter2 { if (oldSession.isSystemSession()) { controller = getSystemController(); } else { - synchronized (sRouterLock) { + synchronized (mLock) { controller = mNonSystemRoutingControllers.get(oldSession.getId()); } } @@ -878,6 +918,22 @@ public final class MediaRouter2 { requestCreateController(controller, route, managerRequestId); } + /** + * Returns whether this router is created with {@link #getInstance(Context, String)}. + * This kind of router can control the target app's media routing. + */ + private boolean isSystemRouter() { + return mClientPackageName != null; + } + + /** + * Registers {@link MediaRouter2Manager.Callback} for getting events. + */ + private void registerManagerCallback() { + // Using direct executor here, since MediaRouter2Manager also posts to the main handler. + sManager.registerCallback(Runnable::run, mManagerCallback); + } + private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryRequest) { return routes.stream() @@ -1236,7 +1292,7 @@ public final class MediaRouter2 { } MediaRouter2Stub stub; - synchronized (sRouterLock) { + synchronized (mLock) { stub = mStub; } if (stub != null) { @@ -1283,7 +1339,7 @@ public final class MediaRouter2 { } MediaRouter2Stub stub; - synchronized (sRouterLock) { + synchronized (mLock) { stub = mStub; } if (stub != null) { @@ -1318,7 +1374,7 @@ public final class MediaRouter2 { } MediaRouter2Stub stub; - synchronized (sRouterLock) { + synchronized (mLock) { stub = mStub; } if (stub != null) { @@ -1352,7 +1408,7 @@ public final class MediaRouter2 { return; } MediaRouter2Stub stub; - synchronized (sRouterLock) { + synchronized (mLock) { stub = mStub; } if (stub != null) { @@ -1386,7 +1442,7 @@ public final class MediaRouter2 { mState = CONTROLLER_STATE_RELEASING; } - synchronized (sRouterLock) { + synchronized (mLock) { // It could happen if the controller is released by the another thread // in between two locks if (!mNonSystemRoutingControllers.remove(getId(), this)) { @@ -1415,7 +1471,7 @@ public final class MediaRouter2 { mState = CONTROLLER_STATE_RELEASED; } - synchronized (sRouterLock) { + synchronized (mLock) { mNonSystemRoutingControllers.remove(getId(), this); if (shouldReleaseSession && mStub != null) { @@ -1483,7 +1539,7 @@ public final class MediaRouter2 { } private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) { - synchronized (sRouterLock) { + synchronized (mLock) { return routeIds.stream().map(mRoutes::get) .filter(Objects::nonNull) .collect(Collectors.toList()); @@ -1665,4 +1721,79 @@ public final class MediaRouter2 { MediaRouter2.this, oldSession, route, managerRequestId)); } } + + class ManagerCallback implements MediaRouter2Manager.Callback { + + @Override + public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) { + List<MediaRoute2Info> filteredRoutes = + sManager.filterRoutesForPackage(routes, mClientPackageName); + if (filteredRoutes.isEmpty()) { + return; + } + for (RouteCallbackRecord record: mRouteCallbackRecords) { + record.mExecutor.execute( + () -> record.mRouteCallback.onRoutesAdded(filteredRoutes)); + } + } + + @Override + public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) { + List<MediaRoute2Info> filteredRoutes = + sManager.filterRoutesForPackage(routes, mClientPackageName); + if (filteredRoutes.isEmpty()) { + return; + } + for (RouteCallbackRecord record: mRouteCallbackRecords) { + record.mExecutor.execute( + () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes)); + } + } + + @Override + public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) { + List<MediaRoute2Info> filteredRoutes = + sManager.filterRoutesForPackage(routes, mClientPackageName); + if (filteredRoutes.isEmpty()) { + return; + } + for (RouteCallbackRecord record: mRouteCallbackRecords) { + record.mExecutor.execute( + () -> record.mRouteCallback.onRoutesChanged(filteredRoutes)); + } + } + + @Override + public void onSessionUpdated(@NonNull RoutingSessionInfo session) { + // TODO: Call ControllerCallback.onControllerUpdated + } + + @Override + public void onTransferred(@NonNull RoutingSessionInfo oldSession, + @Nullable RoutingSessionInfo newSession) { + // TODO: Call TransferCallback.onTransfer + } + + @Override + public void onTransferFailed(@NonNull RoutingSessionInfo session, + @NonNull MediaRoute2Info route) { + // TODO: Call TransferCallback.onTransferFailure + } + + @Override + public void onSessionReleased(@NonNull RoutingSessionInfo session) { + // TODO: Call TransferCallback.onStop() + } + + @Override + public void onPreferredFeaturesChanged(@NonNull String packageName, + @NonNull List<String> preferredFeatures) { + // Does nothing. + } + + @Override + public void onRequestFailed(int reason) { + // Does nothing. + } + } } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 5e732f9be68f..ca619d4072c3 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -237,6 +237,36 @@ public final class MediaRouter2Manager { } /** + * Returns a list of routes which are related to the given package name in the given route list. + */ + @NonNull + public List<MediaRoute2Info> filterRoutesForPackage(@NonNull List<MediaRoute2Info> routes, + @NonNull String packageName) { + Objects.requireNonNull(routes, "routes must not be null"); + Objects.requireNonNull(packageName, "packageName must not be null"); + + List<RoutingSessionInfo> sessions = getRoutingSessions(packageName); + RoutingSessionInfo sessionInfo = sessions.get(sessions.size() - 1); + + List<MediaRoute2Info> result = new ArrayList<>(); + List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName); + if (preferredFeatures == null) { + preferredFeatures = Collections.emptyList(); + } + + synchronized (mRoutesLock) { + for (MediaRoute2Info route : routes) { + if (route.hasAnyFeatures(preferredFeatures) + || sessionInfo.getSelectedRoutes().contains(route.getId()) + || sessionInfo.getTransferableRoutes().contains(route.getId())) { + result.add(route); + } + } + } + return result; + } + + /** * Gets the system routing session associated with no specific application. */ @NonNull diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index 67a4a4b1f851..fd3c4057ad21 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -16,6 +16,8 @@ package android.media.audiofx; +import static android.media.permission.PermissionUtil.myIdentity; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -23,11 +25,11 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioSystem; +import android.media.permission.Identity; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -515,10 +517,11 @@ public class AudioEffect { } // native initialization + // TODO b/182469354: Make consistent with AudioRecord int initResult = native_setup(new WeakReference<AudioEffect>(this), type.toString(), uuid.toString(), priority, audioSession, deviceType, deviceAddress, - id, desc, ActivityThread.currentOpPackageName(), probe); + id, desc, myIdentity(null), probe); if (initResult != SUCCESS && initResult != ALREADY_EXISTS) { Log.e(TAG, "Error code " + initResult + " when initializing AudioEffect."); @@ -1385,7 +1388,7 @@ public class AudioEffect { private native final int native_setup(Object audioeffect_this, String type, String uuid, int priority, int audioSession, int deviceType, String deviceAddress, int[] id, Object[] desc, - String opPackageName, boolean probe); + Identity identity, boolean probe); private native final void native_finalize(); diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java index a5da648cf14a..58c9e650bb90 100644 --- a/media/java/android/media/audiofx/Visualizer.java +++ b/media/java/android/media/audiofx/Visualizer.java @@ -16,8 +16,10 @@ package android.media.audiofx; -import android.app.ActivityThread; +import static android.media.permission.PermissionUtil.myIdentity; + import android.compat.annotation.UnsupportedAppUsage; +import android.media.permission.Identity; import android.os.Handler; import android.os.Looper; import android.util.Log; @@ -217,9 +219,11 @@ public class Visualizer { synchronized (mStateLock) { mState = STATE_UNINITIALIZED; + // native initialization + // TODO b/182469354: make consistent with AudioRecord int result = native_setup(new WeakReference<Visualizer>(this), audioSession, id, - ActivityThread.currentOpPackageName()); + myIdentity(null)); if (result != SUCCESS && result != ALREADY_EXISTS) { Log.e(TAG, "Error code "+result+" when initializing Visualizer."); switch (result) { @@ -686,7 +690,7 @@ public class Visualizer { private native final int native_setup(Object audioeffect_this, int audioSession, int[] id, - String opPackageName); + Identity identity); @GuardedBy("mStateLock") private native final void native_finalize(); diff --git a/media/java/android/media/permission/PermissionUtil.java b/media/java/android/media/permission/PermissionUtil.java index 315ee4f1e998..92fe8820570c 100644 --- a/media/java/android/media/permission/PermissionUtil.java +++ b/media/java/android/media/permission/PermissionUtil.java @@ -17,9 +17,12 @@ package android.media.permission; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityThread; import android.content.Context; import android.content.PermissionChecker; import android.os.Binder; +import android.os.Process; import java.util.Objects; @@ -49,6 +52,25 @@ import java.util.Objects; */ public class PermissionUtil { /** + * Create an identity for the current process and the passed context. + * + * @param context The process the identity is for. If {@code null}, the process's default + * identity is chosen. + * @return The identity for the current process and context + */ + public static @NonNull Identity myIdentity(@Nullable Context context) { + Identity identity = new Identity(); + + identity.pid = Process.myPid(); + identity.uid = Process.myUid(); + identity.packageName = context != null ? context.getOpPackageName() + : ActivityThread.currentOpPackageName(); + identity.attributionTag = context != null ? context.getAttributionTag() : null; + + return identity; + } + + /** * Authenticate an originator, where the binder call is coming from a middleman. * * The middleman is expected to hold a special permission to act as such, or else a diff --git a/media/jni/Android.bp b/media/jni/Android.bp index ce4550492740..f09dcde1ee28 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -84,6 +84,7 @@ cc_library_shared { "android.hardware.drm@1.4", "android.hidl.memory@1.0", "android.hidl.token@1.0-utils", + "media_permission-aidl-cpp", ], header_libs: [ @@ -182,7 +183,7 @@ cc_library_shared { "libnativehelper", "libutils", "tv_tuner_aidl_interface-ndk_platform", - "tv_tuner_resource_manager_aidl_interface-ndk_platform" + "tv_tuner_resource_manager_aidl_interface-ndk_platform", ], static_libs: [ @@ -212,4 +213,3 @@ cc_library_shared { "-Wunreachable-code", ], } - diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 98ac5b983098..a3607597f05e 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -17,6 +17,7 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaPlayer-JNI" +#include "permission_utils.h" #include "utils/Log.h" #include <media/mediaplayer.h> @@ -79,6 +80,8 @@ static StateExceptionFields gStateExceptionFields; using namespace android; using media::VolumeShaper; +using media::permission::Identity; +using media::permission::convertIdentity; // ---------------------------------------------------------------------------- @@ -946,11 +949,11 @@ android_media_MediaPlayer_native_init(JNIEnv *env) static void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jstring opPackageName) + jobject jIdentity) { ALOGV("native_setup"); - ScopedUtfChars opPackageNameStr(env, opPackageName); - sp<MediaPlayer> mp = new MediaPlayer(opPackageNameStr.c_str()); + + sp<MediaPlayer> mp = new MediaPlayer(convertIdentity(env, jIdentity)); if (mp == NULL) { jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; @@ -1406,7 +1409,7 @@ static const JNINativeMethod gMethods[] = { {"native_setMetadataFilter", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_setMetadataFilter}, {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_getMetadata}, {"native_init", "()V", (void *)android_media_MediaPlayer_native_init}, - {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;)V",(void *)android_media_MediaPlayer_native_setup}, + {"native_setup", "(Ljava/lang/Object;Landroid/media/permission/Identity;)V",(void *)android_media_MediaPlayer_native_setup}, {"native_finalize", "()V", (void *)android_media_MediaPlayer_native_finalize}, {"getAudioSessionId", "()I", (void *)android_media_MediaPlayer_get_audio_session_id}, {"native_setAudioSessionId", "(I)V", (void *)android_media_MediaPlayer_set_audio_session_id}, diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index f99dc012be95..66411233216f 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -24,6 +24,7 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaRecorderJNI" +#include "permission_utils.h" #include <utils/Log.h> #include <gui/Surface.h> @@ -50,6 +51,8 @@ using namespace android; +using android::media::permission::convertIdentity; + // ---------------------------------------------------------------------------- // helper function to extract a native Camera object from a Camera Java object @@ -617,13 +620,12 @@ android_media_MediaRecorder_native_init(JNIEnv *env) static void android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jstring packageName, jstring opPackageName) + jstring packageName, jobject jIdentity) { ALOGV("setup"); - ScopedUtfChars opPackageNameStr(env, opPackageName); + sp<MediaRecorder> mr = new MediaRecorder(convertIdentity(env, jIdentity)); - sp<MediaRecorder> mr = new MediaRecorder(String16(opPackageNameStr.c_str())); if (mr == NULL) { jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; @@ -869,7 +871,7 @@ static const JNINativeMethod gMethods[] = { {"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset}, {"release", "()V", (void *)android_media_MediaRecorder_release}, {"native_init", "()V", (void *)android_media_MediaRecorder_native_init}, - {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V", + {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Landroid/media/permission/Identity;)V", (void *)android_media_MediaRecorder_native_setup}, {"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize}, {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface }, diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp index c2fc91d5cfea..bfed983c8bce 100644 --- a/media/jni/audioeffect/Android.bp +++ b/media/jni/audioeffect/Android.bp @@ -27,6 +27,11 @@ cc_library_shared { "libaudioclient", "libaudioutils", "libaudiofoundation", + "media_permission-aidl-cpp", + ], + + export_shared_lib_headers: [ + "media_permission-aidl-cpp", ], version_script: "exports.lds", diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp index a74ae5307a36..8a52456849f0 100644 --- a/media/jni/audioeffect/Visualizer.cpp +++ b/media/jni/audioeffect/Visualizer.cpp @@ -34,8 +34,8 @@ namespace android { // --------------------------------------------------------------------------- -Visualizer::Visualizer (const String16& opPackageName) - : AudioEffect(opPackageName) +Visualizer::Visualizer (const Identity& identity) + : AudioEffect(identity) { } diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h index 8b6a62f25638..3ee91f0f8b1e 100644 --- a/media/jni/audioeffect/Visualizer.h +++ b/media/jni/audioeffect/Visualizer.h @@ -20,6 +20,9 @@ #include <media/AudioEffect.h> #include <system/audio_effects/effect_visualizer.h> #include <utils/Thread.h> +#include "android/media/permission/Identity.h" + +using namespace android::media::permission; /** * The Visualizer class enables application to retrieve part of the currently playing audio for @@ -65,7 +68,7 @@ public: /* Constructor. * See AudioEffect constructor for details on parameters. */ - explicit Visualizer(const String16& opPackageName); + explicit Visualizer(const Identity& identity); ~Visualizer(); diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index 0d53ab152129..953b7e01c983 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -25,6 +25,7 @@ #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> #include "media/AudioEffect.h" +#include "permission_utils.h" #include <nativehelper/ScopedUtfChars.h> @@ -34,6 +35,8 @@ using namespace android; +using media::permission::convertIdentity; + #define AUDIOEFFECT_SUCCESS 0 #define AUDIOEFFECT_ERROR (-1) #define AUDIOEFFECT_ERROR_ALREADY_EXISTS (-2) @@ -270,7 +273,7 @@ static jint android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, jstring type, jstring uuid, jint priority, jint sessionId, jint deviceType, jstring deviceAddress, - jintArray jId, jobjectArray javadesc, jstring opPackageName, jboolean probe) + jintArray jId, jobjectArray javadesc, jobject jIdentity, jboolean probe) { ALOGV("android_media_AudioEffect_native_setup"); AudioEffectJniStorage* lpJniStorage = NULL; @@ -283,8 +286,6 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t jobject jdesc; AudioDeviceTypeAddr device; - ScopedUtfChars opPackageNameStr(env, opPackageName); - setAudioEffect(env, thiz, 0); if (type != NULL) { @@ -337,7 +338,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t } // create the native AudioEffect object - lpAudioEffect = new AudioEffect(String16(opPackageNameStr.c_str())); + lpAudioEffect = new AudioEffect(convertIdentity(env, jIdentity)); if (lpAudioEffect == 0) { ALOGE("Error creating AudioEffect"); goto setup_failure; @@ -773,7 +774,7 @@ android_media_AudioEffect_native_queryPreProcessings(JNIEnv *env, jclass clazz _ // Dalvik VM type signatures static const JNINativeMethod gMethods[] = { {"native_init", "()V", (void *)android_media_AudioEffect_native_init}, - {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Ljava/lang/String;Z)I", + {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Landroid/media/permission/Identity;Z)I", (void *)android_media_AudioEffect_native_setup}, {"native_finalize", "()V", (void *)android_media_AudioEffect_native_finalize}, {"native_release", "()V", (void *)android_media_AudioEffect_native_release}, diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index 4c5970a30a05..439715cbb811 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -25,6 +25,7 @@ #include <android_runtime/AndroidRuntime.h> #include <utils/threads.h> #include "Visualizer.h" +#include "permission_utils.h" #include <nativehelper/ScopedUtfChars.h> @@ -347,7 +348,7 @@ static void android_media_visualizer_effect_callback(int32_t event, static jint android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jint sessionId, jintArray jId, jstring opPackageName) + jint sessionId, jintArray jId, jobject jIdentity) { ALOGV("android_media_visualizer_native_setup"); VisualizerJniStorage* lpJniStorage = NULL; @@ -355,8 +356,6 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th sp<Visualizer> lpVisualizer; jint* nId = NULL; - ScopedUtfChars opPackageNameStr(env, opPackageName); - setVisualizer(env, thiz, 0); lpJniStorage = new VisualizerJniStorage(); @@ -382,7 +381,7 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th } // create the native Visualizer object - lpVisualizer = new Visualizer(String16(opPackageNameStr.c_str())); + lpVisualizer = new Visualizer(convertIdentity(env, jIdentity)); if (lpVisualizer == 0) { ALOGE("Error creating Visualizer"); goto setup_failure; @@ -679,7 +678,7 @@ android_media_setPeriodicCapture(JNIEnv *env, jobject thiz, jint rate, jboolean // Dalvik VM type signatures static const JNINativeMethod gMethods[] = { {"native_init", "()V", (void *)android_media_visualizer_native_init}, - {"native_setup", "(Ljava/lang/Object;I[ILjava/lang/String;)I", + {"native_setup", "(Ljava/lang/Object;I[ILandroid/media/permission/Identity;)I", (void *)android_media_visualizer_native_setup}, {"native_finalize", "()V", (void *)android_media_visualizer_native_finalize}, {"native_release", "()V", (void *)android_media_visualizer_native_release}, diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp index b3406cd89046..4227cd8cbb29 100644 --- a/media/jni/soundpool/Android.bp +++ b/media/jni/soundpool/Android.bp @@ -63,7 +63,7 @@ tidy_errors = [ // Remove some pedantic stylistic requirements. "-google-readability-casting", // C++ casts not always necessary and may be verbose - "-google-readability-todo", // do not require TODO(info) + "-google-readability-todo", // do not require TODO(info) "-google-build-using-namespace", // Reenable and fix later. "-google-explicit-constructor", // found in StreamManager.h @@ -100,7 +100,7 @@ cc_defaults { tidy_checks: tidy_errors, tidy_checks_as_errors: tidy_errors, tidy_flags: [ - "-format-style=file", + "-format-style=file", ], } @@ -135,6 +135,7 @@ cc_library_shared { "libaudioclient", "libmediandk", "libbinder", + "media_permission-aidl-cpp", ], cflags: [ diff --git a/media/jni/soundpool/Sound.cpp b/media/jni/soundpool/Sound.cpp index f8b4bdb1f4d5..50e0d336f981 100644 --- a/media/jni/soundpool/Sound.cpp +++ b/media/jni/soundpool/Sound.cpp @@ -99,8 +99,8 @@ static status_t decode(int fd, int64_t offset, int64_t length, __func__); break; } - int sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize); - ALOGV("%s: read %d", __func__, sampleSize); + ssize_t sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize); + ALOGV("%s: read %zd", __func__, sampleSize); if (sampleSize < 0) { sampleSize = 0; sawInputEOS = true; @@ -124,8 +124,8 @@ static status_t decode(int fd, int64_t offset, int64_t length, } AMediaCodecBufferInfo info; - const int status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1); - ALOGV("%s: dequeueoutput returned: %d", __func__, status); + const ssize_t status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1); + ALOGV("%s: dequeueoutput returned: %zd", __func__, status); if (status >= 0) { if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) { ALOGV("%s: output EOS", __func__); @@ -167,10 +167,10 @@ static status_t decode(int fd, int64_t offset, int64_t length, } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) { ALOGV("%s: no output buffer right now", __func__); } else if (status <= AMEDIA_ERROR_BASE) { - ALOGE("%s: decode error: %d", __func__, status); + ALOGE("%s: decode error: %zd", __func__, status); break; } else { - ALOGV("%s: unexpected info code: %d", __func__, status); + ALOGV("%s: unexpected info code: %zd", __func__, status); } } diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp index 73e319a5902e..95fe000bd2c2 100644 --- a/media/jni/soundpool/Stream.cpp +++ b/media/jni/soundpool/Stream.cpp @@ -17,6 +17,7 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "SoundPool::Stream" #include <utils/Log.h> +#include<android/media/permission/Identity.h> #include "Stream.h" @@ -24,6 +25,8 @@ namespace android::soundpool { +using media::permission::Identity; + Stream::~Stream() { ALOGV("%s(%p)", __func__, this); @@ -317,7 +320,8 @@ void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID, // audio track while the new one is being started and avoids processing them with // wrong audio audio buffer size (mAudioBufferSize) auto toggle = mToggle ^ 1; - void* userData = (void*)((uintptr_t)this | toggle); + // NOLINTNEXTLINE(performance-no-int-to-ptr) + void* userData = reinterpret_cast<void*>((uintptr_t)this | toggle); audio_channel_mask_t soundChannelMask = sound->getChannelMask(); // When sound contains a valid channel mask, use it as is. // Otherwise, use stream count to calculate channel mask. @@ -326,15 +330,17 @@ void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID, // do not create a new audio track if current track is compatible with sound parameters + Identity identity = Identity(); + identity.packageName = mStreamManager->getOpPackageName(); + // TODO b/182469354 make consistent with AudioRecord, add util for native source newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(), channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, staticCallback, userData, 0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE, AudioTrack::TRANSFER_DEFAULT, - nullptr /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, + nullptr /*offloadInfo*/, identity, mStreamManager->getAttributes(), - false /*doNotReconnect*/, 1.0f /*maxRequiredSpeed*/, - mStreamManager->getOpPackageName()); + false /*doNotReconnect*/, 1.0f /*maxRequiredSpeed*/); // Set caller name so it can be logged in destructor. // MediaMetricsConstants.h: AMEDIAMETRICS_PROP_CALLERNAME_VALUE_SOUNDPOOL newTrack->setCallerName("soundpool"); @@ -386,6 +392,7 @@ exit: void Stream::staticCallback(int event, void* user, void* info) { const auto userAsInt = (uintptr_t)user; + // NOLINTNEXTLINE(performance-no-int-to-ptr) auto stream = reinterpret_cast<Stream*>(userAsInt & ~1); stream->callback(event, info, int(userAsInt & 1), 0 /* tries */); } diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp index 502ee00b583e..8b84bf3eb8d8 100644 --- a/media/jni/soundpool/StreamManager.cpp +++ b/media/jni/soundpool/StreamManager.cpp @@ -330,7 +330,7 @@ ssize_t StreamManager::removeFromQueues_l( // streams on mProcessingStreams are undergoing processing by the StreamManager thread // and do not participate in normal stream migration. - return found; + return (ssize_t)found; } void StreamManager::addToRestartQueue_l(Stream *stream) { diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp index 357cc63bd41e..a66d99fbd9f4 100644 --- a/media/jni/soundpool/android_media_SoundPool.cpp +++ b/media/jni/soundpool/android_media_SoundPool.cpp @@ -34,7 +34,8 @@ static struct fields_t { jclass mSoundPoolClass; } fields; static inline SoundPool* MusterSoundPool(JNIEnv *env, jobject thiz) { - return (SoundPool*)env->GetLongField(thiz, fields.mNativeContext); + // NOLINTNEXTLINE(performance-no-int-to-ptr) + return reinterpret_cast<SoundPool*>(env->GetLongField(thiz, fields.mNativeContext)); } static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes"; struct audio_attributes_fields_t { diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt index a8f1a4d2a7f8..243e4ca4295a 100644 --- a/packages/Connectivity/framework/api/current.txt +++ b/packages/Connectivity/framework/api/current.txt @@ -143,6 +143,7 @@ package android.net { public static class ConnectivityManager.NetworkCallback { ctor public ConnectivityManager.NetworkCallback(); + ctor public ConnectivityManager.NetworkCallback(int); method public void onAvailable(@NonNull android.net.Network); method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean); method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities); @@ -150,6 +151,7 @@ package android.net { method public void onLosing(@NonNull android.net.Network, int); method public void onLost(@NonNull android.net.Network); method public void onUnavailable(); + field public static final int FLAG_INCLUDE_LOCATION_INFO = 1; // 0x1 } public static interface ConnectivityManager.OnNetworkActiveListener { @@ -293,6 +295,7 @@ package android.net { method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); method public int getOwnerUid(); method public int getSignalStrength(); + method @NonNull public java.util.Set<java.lang.Integer> getSubIds(); method @Nullable public android.net.TransportInfo getTransportInfo(); method public boolean hasCapability(int); method public boolean hasTransport(int); @@ -399,6 +402,11 @@ package android.net { method public android.net.NetworkRequest.Builder removeTransportType(int); method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String); method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); + method @NonNull public android.net.NetworkRequest.Builder setSubIds(@NonNull java.util.Set<java.lang.Integer>); + } + + public class ParseException extends java.lang.RuntimeException { + field public String response; } public class ProxyInfo implements android.os.Parcelable { diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt index 6df57c132382..4b3336644ef9 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -7,8 +7,9 @@ package android.net { public class ConnectivityManager { method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshot(); + method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange(); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); } diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index a732430e6a9c..a98f14ea9408 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -296,6 +296,7 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); + method @NonNull public android.net.NetworkCapabilities.Builder setSubIds(@NonNull java.util.Set<java.lang.Integer>); method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); } diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index bbf45596e789..7189be10d8f4 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -16,10 +16,10 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; -import static android.net.IpSecManager.INVALID_RESOURCE_ID; import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; import static android.net.NetworkRequest.Type.LISTEN; import static android.net.NetworkRequest.Type.REQUEST; +import static android.net.NetworkRequest.Type.TRACK_BEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; import static android.net.QosCallback.QosCallbackRegistrationException; @@ -43,6 +43,7 @@ import android.net.SocketKeepalive.Callback; import android.net.TetheringManager.StartTetheringCallback; import android.net.TetheringManager.TetheringEventCallback; import android.net.TetheringManager.TetheringRequest; +import android.net.wifi.WifiNetworkSuggestion; import android.os.Binder; import android.os.Build; import android.os.Build.VERSION_CODES; @@ -1314,7 +1315,7 @@ public class ConnectivityManager { } /** - * Returns an array of {@link android.net.NetworkCapabilities} objects, representing + * Returns an array of {@link NetworkCapabilities} objects, representing * the Networks that applications run by the given user will use by default. * @hide */ @@ -1394,11 +1395,19 @@ public class ConnectivityManager { } /** - * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This + * Get the {@link NetworkCapabilities} for the given {@link Network}. This * will return {@code null} if the network is unknown. * + * This will remove any location sensitive data in {@link TransportInfo} embedded in + * {@link NetworkCapabilities#getTransportInfo()}. Some transport info instances like + * {@link android.net.wifi.WifiInfo} contain location sensitive information. Retrieving + * this location sensitive information (subject to app's location permissions) will be + * noted by system. To include any location sensitive data in {@link TransportInfo}, + * use a {@link NetworkCallback} with + * {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} flag. + * * @param network The {@link Network} object identifying the network in question. - * @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}. + * @return The {@link NetworkCapabilities} for the network, or {@code null}. */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @Nullable @@ -1996,7 +2005,7 @@ public class ConnectivityManager { dup = createInvalidFd(); } return new NattSocketKeepalive(mService, network, dup, - INVALID_RESOURCE_ID /* Unused */, source, destination, executor, callback); + -1 /* Unused */, source, destination, executor, callback); } /** @@ -3244,6 +3253,54 @@ public class ConnectivityManager { */ public static class NetworkCallback { /** + * No flags associated with this callback. + * @hide + */ + public static final int FLAG_NONE = 0; + /** + * Use this flag to include any location sensitive data in {@link NetworkCapabilities} sent + * via {@link #onCapabilitiesChanged(Network, NetworkCapabilities)}. + * <p> + * These include: + * <li> Some transport info instances (retrieved via + * {@link NetworkCapabilities#getTransportInfo()}) like {@link android.net.wifi.WifiInfo} + * contain location sensitive information. + * <li> OwnerUid (retrieved via {@link NetworkCapabilities#getOwnerUid()} is location + * sensitive for wifi suggestor apps (i.e using {@link WifiNetworkSuggestion}).</li> + * </p> + * <p> + * Note: + * <li> Retrieving this location sensitive information (subject to app's location + * permissions) will be noted by system. </li> + * <li> Without this flag any {@link NetworkCapabilities} provided via the callback does + * not include location sensitive info. + * </p> + */ + public static final int FLAG_INCLUDE_LOCATION_INFO = 1 << 0; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = "FLAG_", value = { + FLAG_NONE, + FLAG_INCLUDE_LOCATION_INFO + }) + public @interface Flag { } + + /** + * All the valid flags for error checking. + */ + private static final int VALID_FLAGS = FLAG_INCLUDE_LOCATION_INFO; + + public NetworkCallback() { + this(FLAG_NONE); + } + + public NetworkCallback(@Flag int flags) { + Preconditions.checkArgument((flags & VALID_FLAGS) == flags); + mFlags = flags; + } + + /** * Called when the framework connects to a new network to evaluate whether it satisfies this * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable} * callback. There is no guarantee that this new network will satisfy any requests, or that @@ -3380,7 +3437,7 @@ public class ConnectivityManager { * calling these methods while in a callback may return an outdated or even a null object. * * @param network The {@link Network} whose capabilities have changed. - * @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this + * @param networkCapabilities The new {@link NetworkCapabilities} for this * network. */ public void onCapabilitiesChanged(@NonNull Network network, @@ -3449,6 +3506,7 @@ public class ConnectivityManager { public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {} private NetworkRequest networkRequest; + private final int mFlags; } /** @@ -3638,14 +3696,15 @@ public class ConnectivityManager { } Messenger messenger = new Messenger(handler); Binder binder = new Binder(); + final int callbackFlags = callback.mFlags; if (reqType == LISTEN) { request = mService.listenForNetwork( - need, messenger, binder, callingPackageName, + need, messenger, binder, callbackFlags, callingPackageName, getAttributionTag()); } else { request = mService.requestNetwork( need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType, - callingPackageName, getAttributionTag()); + callbackFlags, callingPackageName, getAttributionTag()); } if (request != null) { sCallbacks.put(request, callback); @@ -3692,7 +3751,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * Request a network to satisfy a set of {@link NetworkCapabilities}. * * <p>This method will attempt to find the best network that matches the passed * {@link NetworkRequest}, and to bring up one that does if none currently satisfies the @@ -3776,7 +3835,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * Request a network to satisfy a set of {@link NetworkCapabilities}. * * This method behaves identically to {@link #requestNetwork(NetworkRequest, NetworkCallback)} * but runs all the callbacks on the passed Handler. @@ -3798,7 +3857,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * Request a network to satisfy a set of {@link NetworkCapabilities}, limited * by a timeout. * * This function behaves identically to the non-timed-out version @@ -3833,7 +3892,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * Request a network to satisfy a set of {@link NetworkCapabilities}, limited * by a timeout. * * This method behaves identically to @@ -3878,7 +3937,7 @@ public class ConnectivityManager { /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * Request a network to satisfy a set of {@link NetworkCapabilities}. * * This function behaves identically to the version that takes a NetworkCallback, but instead * of {@link NetworkCallback} a {@link PendingIntent} is used. This means @@ -4190,6 +4249,18 @@ public class ConnectivityManager { } /** + * @hide + */ + // TODO: Make it public api. + @SuppressLint("ExecutorRegistration") + public void registerBestMatchingNetworkCallback(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + final NetworkCapabilities nc = request.networkCapabilities; + final CallbackHandler cbHandler = new CallbackHandler(handler); + sendRequestForNetwork(nc, networkCallback, 0, TRACK_BEST, TYPE_NONE, cbHandler); + } + + /** * Requests bandwidth update for a given {@link Network} and returns whether the update request * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying * network connection for updated bandwidth information. The caller will be notified via @@ -4898,7 +4969,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, but + * Request a network to satisfy a set of {@link NetworkCapabilities}, but * does not cause any networks to retain the NET_CAPABILITY_FOREGROUND capability. This can * be used to request that the system provide a network without causing the network to be * in the foreground. @@ -4979,10 +5050,10 @@ public class ConnectivityManager { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK }) public void requestBackgroundNetwork(@NonNull NetworkRequest request, - @Nullable Handler handler, @NonNull NetworkCallback networkCallback) { + @NonNull Handler handler, @NonNull NetworkCallback networkCallback) { final NetworkCapabilities nc = request.networkCapabilities; sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST, - TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler)); + TYPE_NONE, new CallbackHandler(handler)); } /** @@ -5040,4 +5111,21 @@ public class ConnectivityManager { throw e.rethrowFromSystemServer(); } } + + // The first network ID of IPSec tunnel interface. + private static final int TUN_INTF_NETID_START = 0xFC00; + // The network ID range of IPSec tunnel interface. + private static final int TUN_INTF_NETID_RANGE = 0x0400; + + /** + * Get the network ID range reserved for IPSec tunnel interfaces. + * + * @return A Range which indicates the network ID range of IPSec tunnel interface. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @NonNull + public static Range<Integer> getIpSecNetIdRange() { + return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1); + } } diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index cd49258d1c47..f9393e315b83 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -143,7 +143,7 @@ interface IConnectivityManager NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType, in Messenger messenger, int timeoutSec, in IBinder binder, int legacy, - String callingPackageName, String callingAttributionTag); + int callbackFlags, String callingPackageName, String callingAttributionTag); NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities, in PendingIntent operation, String callingPackageName, String callingAttributionTag); @@ -151,7 +151,7 @@ interface IConnectivityManager void releasePendingNetworkRequest(in PendingIntent operation); NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities, - in Messenger messenger, in IBinder binder, String callingPackageName, + in Messenger messenger, in IBinder binder, int callbackFlags, String callingPackageName, String callingAttributionTag); void pendingListenForNetwork(in NetworkCapabilities networkCapabilities, diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java index c82cd3b4f357..058f3c999dd7 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -25,6 +25,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.ConnectivityManager.NetworkCallback; +import android.net.wifi.WifiNetworkSuggestion; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -131,6 +132,7 @@ public final class NetworkCapabilities implements Parcelable { mPrivateDnsBroken = false; mRequestorUid = Process.INVALID_UID; mRequestorPackageName = null; + mSubIds = new ArraySet<>(); } /** @@ -159,6 +161,7 @@ public final class NetworkCapabilities implements Parcelable { mPrivateDnsBroken = nc.mPrivateDnsBroken; mRequestorUid = nc.mRequestorUid; mRequestorPackageName = nc.mRequestorPackageName; + mSubIds = new ArraySet<>(nc.mSubIds); } /** @@ -1048,6 +1051,16 @@ public final class NetworkCapabilities implements Parcelable { * * Instances of NetworkCapabilities sent to apps without the appropriate permissions will have * this field cleared out. + * + * <p> + * This field will only be populated for VPN and wifi network suggestor apps (i.e using + * {@link WifiNetworkSuggestion}), and only for the network they own. + * In the case of wifi network suggestors apps, this field is also location sensitive, so the + * app needs to hold {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission. If the + * app targets SDK version greater than or equal to {@link Build.VERSION_CODES#S}, then they + * also need to use {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} to get the info in their + * callback. The app will be blamed for location access if this field is included. + * </p> */ public int getOwnerUid() { return mOwnerUid; @@ -1655,6 +1668,7 @@ public final class NetworkCapabilities implements Parcelable { combineSSIDs(nc); combineRequestor(nc); combineAdministratorUids(nc); + combineSubIds(nc); } /** @@ -1674,8 +1688,9 @@ public final class NetworkCapabilities implements Parcelable { && satisfiedBySpecifier(nc) && (onlyImmutable || satisfiedBySignalStrength(nc)) && (onlyImmutable || satisfiedByUids(nc)) - && (onlyImmutable || satisfiedBySSID(nc))) - && (onlyImmutable || satisfiedByRequestor(nc)); + && (onlyImmutable || satisfiedBySSID(nc)) + && (onlyImmutable || satisfiedByRequestor(nc)) + && (onlyImmutable || satisfiedBySubIds(nc))); } /** @@ -1771,7 +1786,8 @@ public final class NetworkCapabilities implements Parcelable { && equalsOwnerUid(that) && equalsPrivateDnsBroken(that) && equalsRequestor(that) - && equalsAdministratorUids(that); + && equalsAdministratorUids(that) + && equalsSubIds(that); } @Override @@ -1793,7 +1809,8 @@ public final class NetworkCapabilities implements Parcelable { + Objects.hashCode(mPrivateDnsBroken) * 47 + Objects.hashCode(mRequestorUid) * 53 + Objects.hashCode(mRequestorPackageName) * 59 - + Arrays.hashCode(mAdministratorUids) * 61; + + Arrays.hashCode(mAdministratorUids) * 61 + + Objects.hashCode(mSubIds) * 67; } @Override @@ -1827,6 +1844,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeInt(mOwnerUid); dest.writeInt(mRequestorUid); dest.writeString(mRequestorPackageName); + dest.writeIntArray(CollectionUtils.toIntArray(mSubIds)); } public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR = @@ -1850,6 +1868,11 @@ public final class NetworkCapabilities implements Parcelable { netCap.mOwnerUid = in.readInt(); netCap.mRequestorUid = in.readInt(); netCap.mRequestorPackageName = in.readString(); + netCap.mSubIds = new ArraySet<>(); + final int[] subIdInts = Objects.requireNonNull(in.createIntArray()); + for (int i = 0; i < subIdInts.length; i++) { + netCap.mSubIds.add(subIdInts[i]); + } return netCap; } @Override @@ -1933,11 +1956,14 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" SSID: ").append(mSSID); } - if (mPrivateDnsBroken) { sb.append(" PrivateDnsBroken"); } + if (!mSubIds.isEmpty()) { + sb.append(" SubscriptionIds: ").append(mSubIds); + } + sb.append("]"); return sb.toString(); } @@ -2251,6 +2277,67 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Set of the subscription IDs that identifies the network or request, empty if none. + */ + @NonNull + private ArraySet<Integer> mSubIds = new ArraySet<>(); + + /** + * Sets the subscription ID set that associated to this network or request. + * + * @hide + */ + @NonNull + public NetworkCapabilities setSubIds(@NonNull Set<Integer> subIds) { + mSubIds = new ArraySet(Objects.requireNonNull(subIds)); + return this; + } + + /** + * Gets the subscription ID set that associated to this network or request. + * @return + */ + @NonNull + public Set<Integer> getSubIds() { + return new ArraySet<>(mSubIds); + } + + /** + * Tests if the subscription ID set of this network is the same as that of the passed one. + */ + private boolean equalsSubIds(@NonNull NetworkCapabilities nc) { + return Objects.equals(mSubIds, nc.mSubIds); + } + + /** + * Check if the subscription ID set requirements of this object are matched by the passed one. + * If specified in the request, the passed one need to have at least one subId and at least + * one of them needs to be in the request set. + */ + private boolean satisfiedBySubIds(@NonNull NetworkCapabilities nc) { + if (mSubIds.isEmpty()) return true; + if (nc.mSubIds.isEmpty()) return false; + for (final Integer subId : nc.mSubIds) { + if (mSubIds.contains(subId)) return true; + } + return false; + } + + /** + * Combine subscription ID set of the capabilities. + * + * <p>This is only legal if the subscription Ids are equal. + * + * <p>If both subscription IDs are not equal, they belong to different subscription + * (or no subscription). In this case, it would not make sense to add them together. + */ + private void combineSubIds(@NonNull NetworkCapabilities nc) { + if (!Objects.equals(mSubIds, nc.mSubIds)) { + throw new IllegalStateException("Can't combine two subscription ID sets"); + } + } + + /** * Builder class for NetworkCapabilities. * * This class is mainly for for {@link NetworkAgent} instances to use. Many fields in @@ -2556,6 +2643,18 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Set the subscription ID set. + * + * @param subIds a set that represent the subscription IDs. Empty if clean up. + * @return this builder. + */ + @NonNull + public Builder setSubIds(@NonNull final Set<Integer> subIds) { + mCaps.setSubIds(subIds); + return this; + } + + /** * Builds the instance of the capabilities. * * @return the built instance of NetworkCapabilities. diff --git a/packages/Connectivity/framework/src/android/net/NetworkInfo.java b/packages/Connectivity/framework/src/android/net/NetworkInfo.java index d752901e2eb0..bb2349459331 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkInfo.java +++ b/packages/Connectivity/framework/src/android/net/NetworkInfo.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Annotation.NetworkType; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; @@ -164,7 +163,7 @@ public class NetworkInfo implements Parcelable { * @param typeName a human-readable string for the network type, or an empty string or null. * @param subtypeName a human-readable string for the subtype, or an empty string or null. */ - public NetworkInfo(int type, @NetworkType int subtype, + public NetworkInfo(int type, int subtype, @Nullable String typeName, @Nullable String subtypeName) { if (!ConnectivityManager.isNetworkTypeValid(type) && type != ConnectivityManager.TYPE_NONE) { diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java index 17a8ee1720c4..3fd95ee58df2 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java +++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java @@ -114,6 +114,10 @@ public class NetworkRequest implements Parcelable { * for the network (if any) that satisfies the default Internet * request. * + * - TRACK_BEST, which causes the framework to send callbacks about + * the single, highest scoring current network (if any) that matches + * the specified NetworkCapabilities. + * * - BACKGROUND_REQUEST, like REQUEST but does not cause any networks * to retain the NET_CAPABILITY_FOREGROUND capability. A network with * no foreground requests is in the background. A network that has @@ -136,6 +140,7 @@ public class NetworkRequest implements Parcelable { REQUEST, BACKGROUND_REQUEST, TRACK_SYSTEM_DEFAULT, + TRACK_BEST, }; /** @@ -456,6 +461,21 @@ public class NetworkRequest implements Parcelable { } nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); } + + /** + * Sets the optional subscription ID set. + * <p> + * This specify the subscription IDs requirement. + * A network will satisfy this request only if it matches one of the subIds in this set. + * An empty set matches all networks, including those without a subId. + * + * @param subIds A {@code Set} that represents subscription IDs. + */ + @NonNull + public Builder setSubIds(@NonNull Set<Integer> subIds) { + mNetworkCapabilities.setSubIds(subIds); + return this; + } } // implement the Parcelable interface diff --git a/packages/Connectivity/framework/src/android/net/NetworkState.java b/packages/Connectivity/framework/src/android/net/NetworkState.java index d01026566ca0..9b69674728a8 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkState.java +++ b/packages/Connectivity/framework/src/android/net/NetworkState.java @@ -22,7 +22,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import android.util.Slog; +import android.util.Log; /** * Snapshot of network state. @@ -83,7 +83,7 @@ public class NetworkState implements Parcelable { if (VALIDATE_ROAMING_STATE && networkInfo != null && networkCapabilities != null) { if (networkInfo.isRoaming() == networkCapabilities .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) { - Slog.wtf("NetworkState", "Roaming state disagreement between " + networkInfo + Log.wtf("NetworkState", "Roaming state disagreement between " + networkInfo + " and " + networkCapabilities); } } diff --git a/core/java/android/net/ParseException.java b/packages/Connectivity/framework/src/android/net/ParseException.java index bcfdd7ef09cc..bcfdd7ef09cc 100644 --- a/core/java/android/net/ParseException.java +++ b/packages/Connectivity/framework/src/android/net/ParseException.java diff --git a/packages/Connectivity/framework/src/android/net/QosSocketInfo.java b/packages/Connectivity/framework/src/android/net/QosSocketInfo.java index d37c4691ddde..53d966937a70 100644 --- a/packages/Connectivity/framework/src/android/net/QosSocketInfo.java +++ b/packages/Connectivity/framework/src/android/net/QosSocketInfo.java @@ -92,7 +92,7 @@ public final class QosSocketInfo implements Parcelable { Objects.requireNonNull(socket, "socket cannot be null"); mNetwork = Objects.requireNonNull(network, "network cannot be null"); - mParcelFileDescriptor = ParcelFileDescriptor.dup(socket.getFileDescriptor$()); + mParcelFileDescriptor = ParcelFileDescriptor.fromSocket(socket); mLocalSocketAddress = new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort()); } @@ -114,10 +114,10 @@ public final class QosSocketInfo implements Parcelable { try { return new InetSocketAddress(InetAddress.getByAddress(address), port); } catch (final UnknownHostException e) { - /* The catch block was purposely left empty. UnknownHostException will never be thrown + /* This can never happen. UnknownHostException will never be thrown since the address provided is numeric and non-null. */ + throw new RuntimeException("UnknownHostException on numeric address", e); } - return new InetSocketAddress(); } @Override diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp index 856f3b85333a..f630ceac3662 100644 --- a/packages/Connectivity/service/Android.bp +++ b/packages/Connectivity/service/Android.bp @@ -60,6 +60,7 @@ java_library { "services.core", "services.net", "unsupportedappusage", + "ServiceConnectivityResources", ], static_libs: [ "modules-utils-os", diff --git a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp new file mode 100644 index 000000000000..f2446b7f7eb8 --- /dev/null +++ b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp @@ -0,0 +1,35 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// APK to hold all the wifi overlayable resources. +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_app { + name: "ServiceConnectivityResources", + sdk_version: "system_current", + resource_dirs: [ + "res", + ], + privileged: true, + export_package_resources: true, + apex_available: [ + "com.android.tethering", + ], + // TODO: use a dedicated cert once generated + certificate: "platform", +} diff --git a/packages/Connectivity/service/ServiceConnectivityResources/AndroidManifest.xml b/packages/Connectivity/service/ServiceConnectivityResources/AndroidManifest.xml new file mode 100644 index 000000000000..2c303026158e --- /dev/null +++ b/packages/Connectivity/service/ServiceConnectivityResources/AndroidManifest.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. + */ +--> +<!-- Manifest for connectivity resources APK --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.connectivity.resources" + coreApp="true" + android:versionCode="1" + android:versionName="S-initial"> + <application + android:label="@string/connectivityResourcesAppLabel" + android:defaultToDeviceProtectedStorage="true" + android:directBootAware="true"> + <!-- This is only used to identify this app by resolving the action. + The activity is never actually triggered. --> + <activity android:name="android.app.Activity" android:exported="true" android:enabled="true"> + <intent-filter> + <action android:name="com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml new file mode 100644 index 000000000000..7d98c76a40ba --- /dev/null +++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- Configuration values for ConnectivityService + DO NOT EDIT THIS FILE for specific device configuration; instead, use a Runtime Resources + Overlay package following the overlayable.xml configuration in the same directory: + https://source.android.com/devices/architecture/rros --> +<resources> + + <!-- Configuration hook for the URL returned by ConnectivityManager#getCaptivePortalServerUrl. + If empty, the returned value is controlled by Settings.Global.CAPTIVE_PORTAL_HTTP_URL, + and if that value is empty, the framework will use a hard-coded default. + This is *NOT* a URL that will always be used by the system network validation to detect + captive portals: NetworkMonitor may use different strategies and will not necessarily use + this URL. NetworkMonitor behaviour should be configured with NetworkStack resource overlays + instead. --> + <!--suppress CheckTagEmptyBody --> + <string translatable="false" name="config_networkCaptivePortalServerUrl"></string> + + <!-- The maximum duration (in milliseconds) we expect a network transition to take --> + <integer name="config_networkTransitionTimeout">60000</integer> + + <!-- Configuration of network interfaces that support WakeOnLAN --> + <string-array translatable="false" name="config_wakeonlan_supported_interfaces"> + <!-- + <item>wlan0</item> + <item>eth0</item> + --> + </string-array> + +</resources>
\ No newline at end of file diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml new file mode 100644 index 000000000000..00ec2df0e6f1 --- /dev/null +++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -0,0 +1,27 @@ +<?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:android="http://schemas.android.com/apk/res/android"> + <overlayable name="ServiceConnectivityResourcesConfig"> + <policy type="product|system|vendor"> + <!-- Configuration values for ConnectivityService --> + <item type="string" name="config_networkCaptivePortalServerUrl"/> + <item type="integer" name="config_networkTransitionTimeout"/> + <item type="array" name="config_wakeonlan_supported_interfaces"/> + + + </policy> + </overlayable> +</resources> diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/strings.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/strings.xml new file mode 100644 index 000000000000..2c7b99265019 --- /dev/null +++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/strings.xml @@ -0,0 +1,22 @@ +<?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> + <!-- The System Connectivity Resources package is an internal system package that provides + configuration values for system networking that were pre-configured in the device. This + is the name of the package to display in the list of system apps. [CHAR LIMIT=40] --> + <string name="connectivityResourcesAppLabel">System Connectivity Resources</string> +</resources>
\ No newline at end of file diff --git a/packages/CtsShim/apk/arm/CtsShim.apk b/packages/CtsShim/apk/arm/CtsShim.apk Binary files differindex 180dfb672c7f..784a74701b4f 100644 --- a/packages/CtsShim/apk/arm/CtsShim.apk +++ b/packages/CtsShim/apk/arm/CtsShim.apk diff --git a/packages/CtsShim/apk/arm/CtsShimPriv.apk b/packages/CtsShim/apk/arm/CtsShimPriv.apk Binary files differindex d87ea7fcef89..5b7bda4d4f84 100644 --- a/packages/CtsShim/apk/arm/CtsShimPriv.apk +++ b/packages/CtsShim/apk/arm/CtsShimPriv.apk diff --git a/packages/CtsShim/apk/x86/CtsShim.apk b/packages/CtsShim/apk/x86/CtsShim.apk Binary files differindex 180dfb672c7f..784a74701b4f 100644 --- a/packages/CtsShim/apk/x86/CtsShim.apk +++ b/packages/CtsShim/apk/x86/CtsShim.apk diff --git a/packages/CtsShim/apk/x86/CtsShimPriv.apk b/packages/CtsShim/apk/x86/CtsShimPriv.apk Binary files differindex 3124651fe425..780cb8a72526 100644 --- a/packages/CtsShim/apk/x86/CtsShimPriv.apk +++ b/packages/CtsShim/apk/x86/CtsShimPriv.apk diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index c1dca5df1b2f..16a946dc7bc6 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -138,9 +138,6 @@ public class DynamicSystemInstallationService extends Service private long mCurrentPartitionSize; private long mCurrentPartitionInstalledSize; - private boolean mJustCancelledByUser; - private boolean mKeepNotification; - // This is for testing only now private boolean mEnableWhenCompleted; @@ -174,11 +171,6 @@ public class DynamicSystemInstallationService extends Service if (cache != null) { cache.flush(); } - - if (!mKeepNotification) { - // Cancel the persistent notification. - mNM.cancel(NOTIFICATION_ID); - } } @Override @@ -231,9 +223,11 @@ public class DynamicSystemInstallationService extends Service return; } + boolean removeNotification = false; switch (result) { case RESULT_CANCELLED: postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null); + removeNotification = true; break; case RESULT_ERROR_IO: @@ -251,7 +245,7 @@ public class DynamicSystemInstallationService extends Service } // if it's not successful, reset the task and stop self. - resetTaskAndStop(); + resetTaskAndStop(removeNotification); } private void executeInstallCommand(Intent intent) { @@ -302,12 +296,12 @@ public class DynamicSystemInstallationService extends Service return; } - stopForeground(true); - mJustCancelledByUser = true; - if (mInstallTask.cancel(false)) { - // Will stopSelf() in onResult() + // onResult() would call resetTaskAndStop() upon task completion. Log.d(TAG, "Cancel request filed successfully"); + // Dismiss the notification as soon as possible as DynamicSystemManager.remove() may + // block. + stopForeground(STOP_FOREGROUND_REMOVE); } else { Log.e(TAG, "Trying to cancel installation while it's already completed."); } @@ -322,8 +316,7 @@ public class DynamicSystemInstallationService extends Service if (!isDynamicSystemInstalled() && (getStatus() != STATUS_READY)) { Log.e(TAG, "Trying to discard AOT while there is no complete installation"); // Stop foreground state and dismiss stale notification. - stopForeground(STOP_FOREGROUND_REMOVE); - resetTaskAndStop(); + resetTaskAndStop(true); return; } @@ -331,8 +324,8 @@ public class DynamicSystemInstallationService extends Service getString(R.string.toast_dynsystem_discarded), Toast.LENGTH_LONG).show(); - resetTaskAndStop(); postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null); + resetTaskAndStop(true); mDynSystem.remove(); } @@ -412,12 +405,13 @@ public class DynamicSystemInstallationService extends Service } private void resetTaskAndStop() { - mInstallTask = null; + resetTaskAndStop(/* removeNotification= */ false); + } - new Handler().postDelayed(() -> { - stopForeground(STOP_FOREGROUND_DETACH); - stopSelf(); - }, 50); + private void resetTaskAndStop(boolean removeNotification) { + mInstallTask = null; + stopForeground(removeNotification ? STOP_FOREGROUND_REMOVE : STOP_FOREGROUND_DETACH); + stopSelf(); } private void prepareNotification() { @@ -525,7 +519,7 @@ public class DynamicSystemInstallationService extends Service private void postStatus(int status, int cause, Throwable detail) { String statusString; String causeString; - mKeepNotification = false; + boolean notifyOnNotificationBar = true; switch (status) { case STATUS_NOT_STARTED: @@ -551,18 +545,16 @@ public class DynamicSystemInstallationService extends Service break; case CAUSE_INSTALL_CANCELLED: causeString = "INSTALL_CANCELLED"; + notifyOnNotificationBar = false; break; case CAUSE_ERROR_IO: causeString = "ERROR_IO"; - mKeepNotification = true; break; case CAUSE_ERROR_INVALID_URL: causeString = "ERROR_INVALID_URL"; - mKeepNotification = true; break; case CAUSE_ERROR_EXCEPTION: causeString = "ERROR_EXCEPTION"; - mKeepNotification = true; break; default: causeString = "CAUSE_NOT_SPECIFIED"; @@ -571,16 +563,6 @@ public class DynamicSystemInstallationService extends Service Log.d(TAG, "status=" + statusString + ", cause=" + causeString + ", detail=" + detail); - boolean notifyOnNotificationBar = true; - - if (status == STATUS_NOT_STARTED - && cause == CAUSE_INSTALL_CANCELLED - && mJustCancelledByUser) { - // if task is cancelled by user, do not notify them - notifyOnNotificationBar = false; - mJustCancelledByUser = false; - } - if (notifyOnNotificationBar) { mNM.notify(NOTIFICATION_ID, buildNotification(status, cause, detail)); } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index 4ef5e2b4f090..59ea9f0150bf 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -320,7 +320,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } } - private void installScratch() throws IOException, InterruptedException { + private void installScratch() throws IOException { final long scratchSize = mDynSystem.suggestScratchSize(); Thread thread = new Thread() { @Override @@ -347,7 +347,11 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog publishProgress(progress); } - Thread.sleep(100); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // Ignore the error. + } } if (mInstallationSession == null) { @@ -361,7 +365,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } } - private void installUserdata() throws IOException, InterruptedException { + private void installUserdata() throws IOException { Thread thread = new Thread(() -> { mInstallationSession = mDynSystem.createPartition("userdata", mUserdataSize, false); }); @@ -383,7 +387,11 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog publishProgress(progress); } - Thread.sleep(100); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // Ignore the error. + } } if (mInstallationSession == null) { @@ -397,8 +405,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } } - private void installImages() - throws IOException, InterruptedException, ImageValidationException { + private void installImages() throws IOException, ImageValidationException { if (mStream != null) { if (mIsZip) { installStreamingZipUpdate(); @@ -410,14 +417,12 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } } - private void installStreamingGzUpdate() - throws IOException, InterruptedException, ImageValidationException { + private void installStreamingGzUpdate() throws IOException, ImageValidationException { Log.d(TAG, "To install a streaming GZ update"); installImage("system", mSystemSize, new GZIPInputStream(mStream)); } - private void installStreamingZipUpdate() - throws IOException, InterruptedException, ImageValidationException { + private void installStreamingZipUpdate() throws IOException, ImageValidationException { Log.d(TAG, "To install a streaming ZIP update"); ZipInputStream zis = new ZipInputStream(mStream); @@ -432,8 +437,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } } - private void installLocalZipUpdate() - throws IOException, InterruptedException, ImageValidationException { + private void installLocalZipUpdate() throws IOException, ImageValidationException { Log.d(TAG, "To install a local ZIP update"); Enumeration<? extends ZipEntry> entries = mZipFile.entries(); @@ -449,7 +453,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } private boolean installImageFromAnEntry(ZipEntry entry, InputStream is) - throws IOException, InterruptedException, ImageValidationException { + throws IOException, ImageValidationException { String name = entry.getName(); Log.d(TAG, "ZipEntry: " + name); @@ -473,7 +477,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog } private void installImage(String partitionName, long uncompressedSize, InputStream is) - throws IOException, InterruptedException, ImageValidationException { + throws IOException, ImageValidationException { SparseInputStream sis = new SparseInputStream(new BufferedInputStream(is)); @@ -504,7 +508,11 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog return; } - Thread.sleep(100); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // Ignore the error. + } } if (mInstallationSession == null) { diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp index ed49bf4d5385..231babea97c2 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp @@ -20,4 +20,8 @@ android_library { ], sdk_version: "system_current", min_sdk_version: "21", + apex_available: [ + "//apex_available:platform", + "com.android.cellbroadcast", + ], } diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp index bda863a71453..73459c277df1 100644 --- a/packages/SettingsLib/SettingsTheme/Android.bp +++ b/packages/SettingsLib/SettingsTheme/Android.bp @@ -18,4 +18,8 @@ android_library { sdk_version: "system_current", min_sdk_version: "21", + apex_available: [ + "//apex_available:platform", + "com.android.cellbroadcast", + ], } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index ed2b6c92530b..e8e10a4def3e 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -153,6 +153,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.DOZE_TAP_SCREEN_GESTURE, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DOZE_WAKE_DISPLAY_GESTURE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.DOZE_QUICK_PICKUP_GESTURE, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.NFC_PAYMENT_DEFAULT_COMPONENT, COMPONENT_NAME_VALIDATOR); VALIDATORS.put( Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, NON_NEGATIVE_INTEGER_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index e427981b87d7..400742ba7d78 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1937,8 +1937,11 @@ public class SettingsProvider extends ContentProvider { if (UserHandle.getAppId(Binder.getCallingUid()) < Process.FIRST_APPLICATION_UID) { return; } - checkReadableAnnotation(settingsType, settingName); ApplicationInfo ai = getCallingApplicationInfoOrThrow(); + if (ai.isSystemApp() || ai.isSignedWithPlatformKey()) { + return; + } + checkReadableAnnotation(settingsType, settingName); if (!ai.isInstantApp()) { return; } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 4dc6d1475c4a..c520568a78e5 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -745,6 +745,7 @@ public class SettingsBackupTest { Settings.Secure.SILENCE_GESTURE, Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE, Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE, + Settings.Secure.DOZE_QUICK_PICKUP_GESTURE, Settings.Secure.FACE_UNLOCK_RE_ENROLL, Settings.Secure.TAP_GESTURE, Settings.Secure.NEARBY_SHARING_COMPONENT, // not user configurable diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index a28a1e32a2a5..b4194fd5bbf9 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -426,6 +426,9 @@ <!-- Permission required for CTS test - ClipboardManagerTest --> <uses-permission android:name="android.permission.SET_CLIP_SOURCE" /> + <!-- Permission required for CTS test - FontManagerTest --> + <uses-permission android:name="android.permission.UPDATE_FONTS" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index b6d942a29d49..e0097df62078 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -578,7 +578,7 @@ <!-- People Space UI Screen --> <activity android:name=".people.PeopleSpaceActivity" - android:label="People" + android:label="@string/people_tile_title" android:enabled="true" android:exported="true" android:theme="@android:style/Theme.Material.NoActionBar"> @@ -592,7 +592,7 @@ <!-- People Space Widget --> <receiver android:name=".people.widget.PeopleSpaceWidgetProvider" - android:label="People Space" + android:label="@string/people_tile_title" android:enabled="true" android:exported="true"> <intent-filter> @@ -751,6 +751,17 @@ android:visibleToInstantApps="true"> </activity> + <activity android:name=".controls.ui.ControlsActivity" + android:label="@string/quick_controls_title" + android:theme="@style/Theme.ControlsActivity" + android:excludeFromRecents="true" + android:showWhenLocked="true" + android:showForAllUsers="true" + android:launchMode="singleInstance" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" + android:visibleToInstantApps="true"> + </activity> + <receiver android:name=".controls.management.ControlsRequestReceiver" android:exported="true"> <intent-filter> diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java index 4fc197340e92..0d348e263545 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java @@ -44,7 +44,7 @@ public interface FalsingManager { /** * Returns true if the FalsingManager thinks the last gesure was not a valid tap. * - * Accepts one parameter, robustCheck, that distinctly changes behavior. When set to false, + * The first parameter, robustCheck, distinctly changes behavior. When set to false, * this method simply looks at the last gesture and returns whether it is a tap or not, (as * opposed to a swipe or other non-tap gesture). When set to true, a more thorough analysis * is performed that can include historical interactions and other contextual cues to see @@ -53,8 +53,11 @@ public interface FalsingManager { * Set robustCheck to true if you want to validate a tap for launching an action, like opening * a notification. Set to false if you simply want to know if the last gesture looked like a * tap. + * + * The second parameter, falsePenalty, indicates how much this should affect future gesture + * classifications if this tap looks like a false. */ - boolean isFalseTap(boolean robustCheck); + boolean isFalseTap(boolean robustCheck, double falsePenalty); /** * Returns true if the last two gestures do not look like a double tap. diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java index 53f7e44bc25a..ca1320448726 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java @@ -50,4 +50,8 @@ public abstract class QSTileView extends LinearLayout { public abstract void onStateChanged(State state); public abstract int getDetailY(); + + public View getLabelContainer() { + return null; + } } diff --git a/packages/SystemUI/res/drawable/controls_dialog_bg.xml b/packages/SystemUI/res/drawable/controls_dialog_bg.xml deleted file mode 100644 index 1ccb176b8689..000000000000 --- a/packages/SystemUI/res/drawable/controls_dialog_bg.xml +++ /dev/null @@ -1,21 +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. ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid android:color="?android:attr/colorBackground" /> - <corners android:radius="@dimen/notification_corner_radius" /> -</shape> diff --git a/packages/SystemUI/res/drawable/qs_tile_background.xml b/packages/SystemUI/res/drawable/qs_tile_background.xml new file mode 100644 index 000000000000..265f575fc99c --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_tile_background.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. + --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask" + android:drawable="@drawable/qs_tile_background_shape" /> + <item android:id="@id/background" + android:drawable="@drawable/qs_tile_background_shape"/> +</ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_tile_background_shape.xml b/packages/SystemUI/res/drawable/qs_tile_background_shape.xml new file mode 100644 index 000000000000..f6b68347124e --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_tile_background_shape.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <corners android:radius="@dimen/qs_corner_radius" /> + <solid android:color="#FFFFFF" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_detail_dialog.xml b/packages/SystemUI/res/layout/controls_detail_dialog.xml index ee5315ad782f..28fc86372092 100644 --- a/packages/SystemUI/res/layout/controls_detail_dialog.xml +++ b/packages/SystemUI/res/layout/controls_detail_dialog.xml @@ -20,8 +20,8 @@ android:id="@+id/control_detail_root" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginTop="@dimen/controls_activity_view_top_offset" - android:orientation="vertical"> + android:orientation="vertical" + android:background="@android:color/black"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" @@ -30,7 +30,7 @@ <ImageView android:id="@+id/control_detail_close" android:contentDescription="@string/accessibility_desc_close" - android:src="@drawable/ic_close" + android:src="@drawable/ic_arrow_back" android:background="?android:attr/selectableItemBackgroundBorderless" android:tint="@color/control_primary_text" android:layout_width="48dp" @@ -56,7 +56,6 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" - android:background="@android:color/black" android:orientation="vertical" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/controls_in_dialog.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml index 983999f9a5f8..1b2d2e2e9c89 100644 --- a/packages/SystemUI/res/layout/controls_in_dialog.xml +++ b/packages/SystemUI/res/layout/controls_fullscreen.xml @@ -20,11 +20,8 @@ android:id="@+id/control_detail_root" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginVertical="@dimen/controls_activity_view_top_offset" - android:layout_marginHorizontal="@dimen/controls_activity_view_side_offset" - android:padding="8dp" android:orientation="vertical" - android:background="@drawable/controls_dialog_bg"> + android:background="@android:color/black"> <com.android.systemui.globalactions.MinHeightScrollView android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index b060afdc18e3..9d011482d011 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -25,8 +25,21 @@ <!-- make sure the header stays centered in the layout by adding a spacer --> <Space + android:id="@+id/controls_spacer" android:layout_width="@dimen/controls_header_menu_size" - android:layout_height="1dp" /> + android:layout_height="1dp" + android:visibility="gone" /> + + <ImageView + android:id="@+id/controls_close" + android:contentDescription="@string/accessibility_desc_close" + android:src="@drawable/ic_close" + android:background="?android:attr/selectableItemBackgroundBorderless" + android:tint="@color/control_primary_text" + android:layout_width="@dimen/controls_header_menu_size" + android:layout_height="@dimen/controls_header_menu_size" + android:padding="12dp" + android:visibility="gone" /> <!-- need to keep this outer view in order to have a correctly sized anchor for the dropdown menu, as well as dropdown background in the right place --> <LinearLayout diff --git a/packages/SystemUI/res/layout/people_space_activity.xml b/packages/SystemUI/res/layout/people_space_activity.xml index 07af01b0db72..1784cae816ce 100644 --- a/packages/SystemUI/res/layout/people_space_activity.xml +++ b/packages/SystemUI/res/layout/people_space_activity.xml @@ -25,19 +25,30 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" android:orientation="vertical" - android:padding="16dp" + android:padding="24dp" android:clipChildren="false" android:clipToPadding="false"> <TextView + android:id="@+id/select_conversation_title" + android:gravity="center" + android:text="@string/select_conversation_title" + android:layout_width="wrap_content" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" + android:textSize="24sp" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal"/> + + <TextView android:id="@+id/select_conversation" + android:gravity="center" android:text="@string/select_conversation_text" android:layout_width="match_parent" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textSize="24sp" + android:textSize="16sp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:paddingBottom="16dp" /> + android:padding="24dp" /> </LinearLayout> </androidx.core.widget.NestedScrollView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/people_space_placeholder_layout.xml b/packages/SystemUI/res/layout/people_space_placeholder_layout.xml new file mode 100644 index 000000000000..3ced1ff6c74b --- /dev/null +++ b/packages/SystemUI/res/layout/people_space_placeholder_layout.xml @@ -0,0 +1,75 @@ +<!-- + ~ 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="match_parent" + android:orientation="vertical"> + <LinearLayout + android:background="@drawable/people_space_tile_view_card" + android:id="@+id/item" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:orientation="horizontal" + android:gravity="center" + android:paddingVertical="2dp" + android:paddingHorizontal="8dp" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView + android:background="@drawable/ic_person" + android:id="@+id/person_icon_only" + android:layout_width="60dp" + android:layout_height="60dp"/> + + <LinearLayout + android:orientation="vertical" + android:paddingStart="8dp" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <ImageView + android:id="@+id/availability" + android:layout_width="10dp" + android:layout_height="10dp" + android:background="@drawable/circle_green_10dp"/> + <TextView + android:id="@+id/name" + android:text="@string/empty_user_name" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" + android:textColor="?android:attr/textColorPrimary" + android:textSize="14sp" + android:maxLines="1" + android:ellipsize="end" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + + <TextView + android:id="@+id/last_interaction" + android:text="@string/empty_status" + android:textColor="?android:attr/textColorSecondary" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" + android:textSize="12sp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="3" + android:ellipsize="end"/> + </LinearLayout> + </LinearLayout> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_carrier.xml b/packages/SystemUI/res/layout/qs_carrier.xml index a5b8cfa5d60b..c521dc2b45a5 100644 --- a/packages/SystemUI/res/layout/qs_carrier.xml +++ b/packages/SystemUI/res/layout/qs_carrier.xml @@ -28,13 +28,6 @@ android:clipToPadding="false" android:focusable="true" > - <include - layout="@layout/mobile_signal_group" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/qs_carrier_margin_width" - android:visibility="gone" /> - <com.android.systemui.util.AutoMarqueeTextView android:id="@+id/qs_carrier_text" android:layout_width="wrap_content" @@ -46,4 +39,11 @@ android:singleLine="true" android:maxEms="7"/> + <include + layout="@layout/mobile_signal_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/qs_carrier_margin_width" + android:visibility="gone" /> + </com.android.systemui.qs.carrier.QSCarrier>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/udfps_animation_view_bp.xml b/packages/SystemUI/res/layout/udfps_bp_view.xml index 0cfbf2e61dd1..f1c55ef16cdc 100644 --- a/packages/SystemUI/res/layout/udfps_animation_view_bp.xml +++ b/packages/SystemUI/res/layout/udfps_bp_view.xml @@ -14,9 +14,9 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.biometrics.UdfpsAnimationViewBp +<com.android.systemui.biometrics.UdfpsBpView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/udfps_animation_view" android:layout_width="match_parent" android:layout_height="match_parent"> -</com.android.systemui.biometrics.UdfpsAnimationViewBp> +</com.android.systemui.biometrics.UdfpsBpView> diff --git a/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml b/packages/SystemUI/res/layout/udfps_enroll_view.xml index 9b5752d2de59..40353052ca85 100644 --- a/packages/SystemUI/res/layout/udfps_animation_view_enroll.xml +++ b/packages/SystemUI/res/layout/udfps_enroll_view.xml @@ -14,13 +14,13 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.biometrics.UdfpsAnimationViewEnroll +<com.android.systemui.biometrics.UdfpsEnrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/udfps_animation_view" android:layout_width="match_parent" android:layout_height="match_parent"> - <!-- Enrollment progress bar--> + <!-- Enrollment progress bar --> <com.android.systemui.biometrics.UdfpsProgressBar android:id="@+id/progress_bar" android:layout_width="match_parent" @@ -31,4 +31,9 @@ android:layout_gravity="center" android:visibility="gone"/> -</com.android.systemui.biometrics.UdfpsAnimationViewEnroll> + <!-- Fingerprint --> + <ImageView + android:id="@+id/udfps_enroll_animation_fp_view" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</com.android.systemui.biometrics.UdfpsEnrollView> diff --git a/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml b/packages/SystemUI/res/layout/udfps_fpm_other_view.xml index 644d1adac46b..6ecbb473d720 100644 --- a/packages/SystemUI/res/layout/udfps_animation_view_keyguard.xml +++ b/packages/SystemUI/res/layout/udfps_fpm_other_view.xml @@ -14,9 +14,15 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.biometrics.UdfpsAnimationViewKeyguard +<com.android.systemui.biometrics.UdfpsFpmOtherView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/udfps_animation_view" android:layout_width="match_parent" android:layout_height="match_parent"> -</com.android.systemui.biometrics.UdfpsAnimationViewKeyguard> + + <!-- Fingerprint --> + <ImageView + android:id="@+id/udfps_fpm_other_fp_view" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</com.android.systemui.biometrics.UdfpsFpmOtherView> diff --git a/packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml b/packages/SystemUI/res/layout/udfps_keyguard_view.xml index f32faa0df867..0199ccb04be6 100644 --- a/packages/SystemUI/res/layout/udfps_animation_view_fpm_other.xml +++ b/packages/SystemUI/res/layout/udfps_keyguard_view.xml @@ -14,9 +14,17 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.biometrics.UdfpsAnimationViewFpmOther +<com.android.systemui.biometrics.UdfpsKeyguardView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/udfps_animation_view" android:layout_width="match_parent" android:layout_height="match_parent"> -</com.android.systemui.biometrics.UdfpsAnimationViewFpmOther> + + <!-- TODO: add background protection --> + + <!-- Fingerprint --> + <ImageView + android:id="@+id/udfps_keyguard_animation_fp_view" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</com.android.systemui.biometrics.UdfpsKeyguardView> diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml index e24c9e99a405..50b2f209d871 100644 --- a/packages/SystemUI/res/layout/udfps_view.xml +++ b/packages/SystemUI/res/layout/udfps_view.xml @@ -22,6 +22,11 @@ android:layout_height="match_parent" systemui:sensorTouchAreaCoefficient="0.5"> + <ViewStub + android:id="@+id/animation_view" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + <com.android.systemui.biometrics.UdfpsSurfaceView android:id="@+id/hbm_view" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index da58a3442e6f..92d9ca9e94d8 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -180,6 +180,9 @@ <!-- Doze: duration to avoid false pickup gestures triggered by notification vibrations --> <integer name="doze_pickup_vibration_threshold">2000</integer> + <!-- Doze: quick pickup duration to stay in AOD until the next gesture is triggered --> + <integer name="doze_quick_pickup_aod_duration">5000</integer> + <!-- Type of a sensor that provides a low-power estimate of the desired display brightness, suitable to listen to while the device is asleep (e.g. during always-on display) --> @@ -577,6 +580,9 @@ <!-- Whether wallet view is shown in landscape / seascape orientations --> <bool name="global_actions_show_landscape_wallet_view">false</bool> + <!-- Package name of the preferred system app to perform eSOS action --> + <string name="config_preferredEmergencySosPackage" translatable="false"></string> + <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c0e474ae7882..2062104be2df 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -171,7 +171,7 @@ <dimen name="scroll_fast_threshold">1500dp</dimen> <!-- Height of a the shelf with the notification icons --> - <dimen name="notification_shelf_height">32dp</dimen> + <dimen name="notification_shelf_height">48dp</dimen> <!-- Minimum height of a notification to be interactable --> <dimen name="notification_min_interaction_height">40dp</dimen> @@ -512,6 +512,7 @@ <!-- The size of the gesture span needed to activate the "pull" notification expansion --> <dimen name="pull_span_min">25dp</dimen> + <dimen name="qs_corner_radius">14dp</dimen> <dimen name="qs_tile_height">96dp</dimen> <!--notification_side_paddings + notification_content_margin_start - (qs_quick_tile_size - qs_tile_background_size) / 2 --> <dimen name="qs_tile_layout_margin_side">18dp</dimen> @@ -528,6 +529,8 @@ <dimen name="qs_tile_margin_top">0dp</dimen> <dimen name="qs_tile_icon_background_stroke_width">-1dp</dimen> <dimen name="qs_tile_background_size">44dp</dimen> + <dimen name="qs_icon_size">20dp</dimen> + <dimen name="qs_label_container_margin">10dp</dimen> <dimen name="qs_quick_tile_size">48dp</dimen> <dimen name="qs_quick_tile_padding">12dp</dimen> <dimen name="qs_header_gear_translation">16dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 10af2a9cbd62..70be7c67ad0d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2811,44 +2811,44 @@ <!-- Text to display when copying the build number off QS [CHAR LIMIT=NONE]--> <string name="build_number_copy_toast">Build number copied to clipboard.</string> - <!-- Status for last interaction with exact time [CHAR LIMIT=120] --> - <string name="last_interaction_status" translatable="false">Last chatted <xliff:g id="duration" example="5 hours">%1$s</xliff:g> ago</string> - <!-- Status for last interaction when less than a certain time window [CHAR LIMIT=120] --> - <string name="last_interaction_status_less_than" translatable="false">Last chatted less than <xliff:g id="duration" example="5 hours">%1$s</xliff:g> ago</string> - <!-- Status for last interaction when over a certain time window [CHAR LIMIT=120] --> - <string name="last_interaction_status_over" translatable="false">Last chatted over <xliff:g id="duration" example="1 week">%1$s</xliff:g> ago</string> - <!-- Status for conversation without interaction data [CHAR LIMIT=120] --> - <string name="basic_status" translatable="false">Open conversation</string> - <!-- Status for conversation without interaction data [CHAR LIMIT=120] --> - <string name="select_conversation_text" translatable="false">Select one conversation to show in your widget:</string> - <!-- Timestamp for notification with exact time [CHAR LIMIT=120] --> - <string name="timestamp" translatable="false"><xliff:g id="duration" example="5 hours">%1$s</xliff:g> ago</string> - <!-- Timestamp for notification when less than a certain time window [CHAR LIMIT=120] --> - <string name="less_than_timestamp" translatable="false">Less than <xliff:g id="duration" example="5 hours">%1$s</xliff:g> ago</string> - <!-- Timestamp for notification when over a certain time window [CHAR LIMIT=120] --> - <string name="over_timestamp" translatable="false">Over <xliff:g id="duration" example="1 week">%1$s</xliff:g> ago</string> - <!-- Status text for a birthday today [CHAR LIMIT=30] --> - <string name="birthday_status" translatable="false">Birthday</string> - <!-- Status text for an upcoming birthday [CHAR LIMIT=30] --> - <string name="upcoming_birthday_status" translatable="false">Birthday soon</string> - <!-- Status text for an anniversary [CHAR LIMIT=30] --> - <string name="anniversary_status" translatable="false">Anniversary</string> - <!-- Status text for sharing location [CHAR LIMIT=30] --> - <string name="location_status" translatable="false">Sharing location</string> - <!-- Status text for a new story posted [CHAR LIMIT=30] --> - <string name="new_story_status" translatable="false">New story</string> - <!-- Status text for watching a video [CHAR LIMIT=30] --> - <string name="video_status" translatable="false">Watching</string> - <!-- Status text for listening to audio [CHAR LIMIT=30] --> - <string name="audio_status" translatable="false">Listening</string> - <!-- Status text for playing a game [CHAR LIMIT=30] --> - <string name="game_status" translatable="false">Playing</string> - <!-- Empty user name before user has selected a friend [CHAR LIMIT=30] --> - <string name="empty_user_name" translatable="false">Your friend</string> - <!-- Empty status shown before user has selected a friend [CHAR LIMIT=30] --> - <string name="empty_status" translatable="false">Their status</string> - <!-- Default text for missed call notifications [CHAR LIMIT=30] --> - <string name="missed_call" translatable="false">Missed call</string> + <!-- Status for conversation without interaction data [CHAR LIMIT=120] --> + <string name="basic_status">Open conversation</string> + <!--Title text for Conversation widget set up screen [CHAR LIMIT=180] --> + <string name="select_conversation_title">Conversation widgets</string> + <!--Text explaining to tap a conversation to select it show in their Conversation widget [CHAR LIMIT=180] --> + <string name="select_conversation_text">Tap a conversation to add it to your Home screen</string> + <!-- Timestamp for notification with exact time [CHAR LIMIT=25] --> + <string name="timestamp"><xliff:g id="duration" example="5 hours">%1$s</xliff:g> ago</string> + <!-- Timestamp for notification when less than a certain time window [CHAR LIMIT=25] --> + <string name="less_than_timestamp">Less than <xliff:g id="duration" example="5 hours">%1$s</xliff:g> ago</string> + <!-- Timestamp for notification when over a certain time window [CHAR LIMIT=25] --> + <string name="over_timestamp">Over <xliff:g id="duration" example="1 week">%1$s</xliff:g> ago</string> + <!-- Status text on the Conversation widget for a birthday today [CHAR LIMIT=20] --> + <string name="birthday_status">Birthday</string> + <!-- Status text on the Conversation widget for an upcoming birthday [CHAR LIMIT=20] --> + <string name="upcoming_birthday_status">Birthday soon</string> + <!-- Status text on the Conversation widget for an anniversary [CHAR LIMIT=20] --> + <string name="anniversary_status">Anniversary</string> + <!-- Status text on the Conversation widget for sharing location [CHAR LIMIT=20] --> + <string name="location_status">Sharing location</string> + <!-- Status text on the Conversation widget for a new story posted [CHAR LIMIT=20] --> + <string name="new_story_status">New story</string> + <!-- Status text on the Conversation widget for watching a video [CHAR LIMIT=20] --> + <string name="video_status">Watching</string> + <!-- Status text on the Conversation widget for listening to audio [CHAR LIMIT=20] --> + <string name="audio_status">Listening</string> + <!-- Status text on the Conversation widget for playing a game [CHAR LIMIT=20] --> + <string name="game_status">Playing</string> + <!-- Empty user name before user has selected a friend for their Conversation widget [CHAR LIMIT=20] --> + <string name="empty_user_name">Friends</string> + <!-- Empty status shown before user has selected a friend for their Conversation widget [CHAR LIMIT=20] --> + <string name="empty_status">Let’s chat tonight!</string> + <!-- Default text for missed call notifications on their Conversation widget [CHAR LIMIT=20] --> + <string name="missed_call">Missed call</string> + <!-- Description text for adding a Conversation widget [CHAR LIMIT=100] --> + <string name="people_tile_description">See recent messages, missed calls, and status updates</string> + <!-- Title text displayed for the Conversation widget [CHAR LIMIT=50] --> + <string name="people_tile_title">Conversation</string> <!-- Title to display in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 3c48548cc0fe..fb885cb3fdbe 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -649,6 +649,16 @@ <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item> </style> + <style name="Theme.ControlsActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar"> + <item name="android:windowActivityTransitions">true</item> + <item name="android:windowContentTransitions">false</item> + <item name="android:windowIsTranslucent">false</item> + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:windowAnimationStyle">@null</item> + <item name="android:statusBarColor">@*android:color/transparent</item> + <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item> + </style> + <style name="Theme.CreateUser" parent="@style/Theme.SystemUI"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">#33000000</item> @@ -660,30 +670,12 @@ <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> </style> - <style name="Theme.SystemUI.Dialog.Control.DetailPanel" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar"> - <item name="android:windowAnimationStyle">@style/Animation.Bottomsheet</item> - <item name="android:windowFullscreen">true</item> - <item name="android:windowIsFloating">false</item> - <item name="android:windowBackground">@null</item> - <item name="android:backgroundDimEnabled">true</item> - </style> - - <style name="Animation.Bottomsheet"> - <item name="android:windowEnterAnimation">@anim/bottomsheet_in</item> - <item name="android:windowExitAnimation">@anim/bottomsheet_out</item> - </style> - - <style name="Theme.SystemUI.Dialog.Control.LockScreen" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar"> - <item name="android:windowAnimationStyle">@style/Animation.ControlDialog</item> - <item name="android:windowFullscreen">true</item> + <style name="Theme.SystemUI.Dialog.Control.DetailPanel" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar"> + <item name="android:windowFullscreen">false</item> <item name="android:windowIsFloating">false</item> - <item name="android:windowBackground">@null</item> - <item name="android:backgroundDimEnabled">true</item> - </style> - - <style name="Animation.ControlDialog"> - <item name="android:windowEnterAnimation">@*android:anim/dialog_enter</item> - <item name="android:windowExitAnimation">@*android:anim/dialog_exit</item> + <item name="android:windowBackground">@android:color/black</item> + <item name="android:backgroundDimEnabled">false</item> + <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> </style> <style name="Control" /> diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml index fbdac5e1789b..35188d8e11ad 100644 --- a/packages/SystemUI/res/xml/people_space_widget_info.xml +++ b/packages/SystemUI/res/xml/people_space_widget_info.xml @@ -20,8 +20,9 @@ android:minResizeWidth="110dp" android:minResizeHeight="55dp" android:updatePeriodMillis="60000" - android:previewImage="@drawable/ic_person" + android:description="@string/people_tile_description" + android:previewLayout="@layout/people_space_placeholder_layout" android:resizeMode="horizontal|vertical" android:configure="com.android.systemui.people.PeopleSpaceActivity" - android:initialLayout="@layout/people_space_widget"> + android:initialLayout="@layout/people_space_placeholder_layout"> </appwidget-provider> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 09e9675a3277..f98a959346d3 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -41,6 +41,7 @@ android_library { srcs: [ "src/**/*.java", "src/**/I*.aidl", + ":wm_shell-aidls", ], static_libs: [ diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 49e86f55bb9e..2103de2ae85c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -16,11 +16,6 @@ package com.android.systemui.shared.recents; -import android.app.PendingIntent; -import android.app.PictureInPictureParams; -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; @@ -28,26 +23,15 @@ import android.os.Bundle; import android.os.UserHandle; import android.view.MotionEvent; -import com.android.systemui.shared.recents.IPinnedStackAnimationListener; -import com.android.systemui.shared.recents.ISplitScreenListener; -import com.android.systemui.shared.recents.IStartingWindowListener; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.RemoteTransitionCompat; /** * Temporary callbacks into SystemUI. - * Next id = 44 */ interface ISystemUiProxy { /** - * Proxies SurfaceControl.screenshotToBuffer(). - * @Removed - * GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer, - * int maxLayer, boolean useIdentityTransform, int rotation) = 0; - */ - - /** * Begins screen pinning on the provided {@param taskId}. */ void startScreenPinning(int taskId) = 1; @@ -121,11 +105,6 @@ interface ISystemUiProxy { void stopScreenPinning() = 17; /** - * Sets the shelf height and visibility. - */ - void setShelfHeight(boolean visible, int shelfHeight) = 20; - - /** * Handle the provided image as if it was a screenshot. * * Deprecated, use handleImageBundleAsScreenshot with image bundle and UserTask @@ -145,27 +124,12 @@ interface ISystemUiProxy { void notifySwipeToHomeFinished() = 23; /** - * Sets listener to get pinned stack animation callbacks. - */ - void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) = 24; - - /** * Notifies that quickstep will switch to a new task * @param rotation indicates which Surface.Rotation the gesture was started in */ void onQuickSwitchToNewTask(int rotation) = 25; /** - * Start the one-handed mode. - */ - void startOneHandedMode() = 26; - - /** - * Stop the one-handed mode. - */ - void stopOneHandedMode() = 27; - - /** * Handle the provided image as if it was a screenshot. */ void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen, @@ -176,88 +140,5 @@ interface ISystemUiProxy { */ void expandNotificationPanel() = 29; - /** - * Notifies that Activity is about to be swiped to home with entering PiP transition and - * queries the destination bounds for PiP depends on Launcher's rotation and shelf height. - * - * @param componentName ComponentName represents the Activity - * @param activityInfo ActivityInfo tied to the Activity - * @param pictureInPictureParams PictureInPictureParams tied to the Activity - * @param launcherRotation Launcher rotation to calculate the PiP destination bounds - * @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds - * @return destination bounds the PiP window should land into - */ - Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo, - in PictureInPictureParams pictureInPictureParams, - int launcherRotation, int shelfHeight) = 30; - - /** - * Notifies the swiping Activity to PiP onto home transition is finished - * - * @param componentName ComponentName represents the Activity - * @param destinationBounds the destination bounds the PiP window lands into - */ - void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds) = 31; - - /** - * Registers a RemoteTransitionCompat that will handle transitions. This parameter bundles an - * IRemoteTransition and a filter that must pass for it. - */ - void registerRemoteTransition(in RemoteTransitionCompat remoteTransition) = 32; - - /** Unegisters a RemoteTransitionCompat that will handle transitions. */ - void unregisterRemoteTransition(in RemoteTransitionCompat remoteTransition) = 33; - -// SplitScreen APIs...copied from SplitScreen.java - /** - * Stage position isn't specified normally meaning to use what ever it is currently set to. - */ - //int STAGE_POSITION_UNDEFINED = -1; - /** - * Specifies that a stage is positioned at the top half of the screen if - * in portrait mode or at the left half of the screen if in landscape mode. - */ - //int STAGE_POSITION_TOP_OR_LEFT = 0; - /** - * Specifies that a stage is positioned at the bottom half of the screen if - * in portrait mode or at the right half of the screen if in landscape mode. - */ - //int STAGE_POSITION_BOTTOM_OR_RIGHT = 1; - - /** - * Stage type isn't specified normally meaning to use what ever the default is. - * E.g. exit split-screen and launch the app in fullscreen. - */ - //int STAGE_TYPE_UNDEFINED = -1; - /** - * The main stage type. - * @see MainStage - */ - //int STAGE_TYPE_MAIN = 0; - /** - * The side stage type. - * @see SideStage - */ - //int STAGE_TYPE_SIDE = 1; - - void registerSplitScreenListener(in ISplitScreenListener listener) = 34; - void unregisterSplitScreenListener(in ISplitScreenListener listener) = 35; - - /** Hides the side-stage if it is currently visible. */ - void setSideStageVisibility(in boolean visible) = 36; - /** Removes the split-screen stages. */ - void exitSplitScreen() = 37; - /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */ - void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 38; - void startTask(in int taskId, in int stage, in int position, in Bundle options) = 39; - void startShortcut(in String packageName, in String shortcutId, in int stage, in int position, - in Bundle options, in UserHandle user) = 40; - void startIntent( - in PendingIntent intent, in Intent fillInIntent, in int stage, in int position, - in Bundle options) = 41; - void removeFromSideStage(in int taskId) = 42; - /** - * Sets listener to get task launching callbacks. - */ - void setStartingWindowListener(IStartingWindowListener listener) = 43; + // Next id = 44 } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index ebb6e30d4b3b..e9e9b2421d4a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -289,15 +289,19 @@ public class Task { /** * Returns the visible width to height ratio. Returns 0f if snapshot data is not available. */ - public float getVisibleThumbnailRatio() { + public float getVisibleThumbnailRatio(boolean clipInsets) { if (lastSnapshotData.taskSize == null || lastSnapshotData.contentInsets == null) { return 0f; } - float availableWidth = lastSnapshotData.taskSize.x - (lastSnapshotData.contentInsets.left - + lastSnapshotData.contentInsets.right); - float availableHeight = lastSnapshotData.taskSize.y - (lastSnapshotData.contentInsets.top - + lastSnapshotData.contentInsets.bottom); + float availableWidth = lastSnapshotData.taskSize.x; + float availableHeight = lastSnapshotData.taskSize.y; + if (clipInsets) { + availableWidth -= + (lastSnapshotData.contentInsets.left + lastSnapshotData.contentInsets.right); + availableHeight -= + (lastSnapshotData.contentInsets.top + lastSnapshotData.contentInsets.bottom); + } return availableWidth / availableHeight; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 937c1df10315..41840afc4995 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -41,6 +41,18 @@ public class QuickStepContract { public static final String KEY_EXTRA_INPUT_MONITOR = "extra_input_monitor"; public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius"; public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners"; + // See IPip.aidl + public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip"; + // See ISplitScreen.aidl + public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen"; + // See IOneHanded.aidl + public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed"; + // See IShellTransitions.aidl + public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS = + "extra_shell_shell_transitions"; + // See IStartingWindow.aidl + public static final String KEY_EXTRA_SHELL_STARTING_WINDOW = + "extra_shell_starting_window"; public static final String NAV_BAR_MODE_2BUTTON_OVERLAY = WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index af7c5da7b878..c2d52a7855f4 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -16,11 +16,11 @@ package com.android.systemui.shared.system; -import android.window.TaskSnapshot; import android.graphics.Rect; import android.os.RemoteException; import android.util.Log; import android.view.IRecentsAnimationController; +import android.window.TaskSnapshot; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -141,9 +141,9 @@ public class RecentsAnimationControllerCompat { /** * @see IRecentsAnimationController#detachNavigationBarFromApp */ - public void detachNavigationBarFromApp() { + public void detachNavigationBarFromApp(boolean moveHomeToTop) { try { - mAnimationController.detachNavigationBarFromApp(); + mAnimationController.detachNavigationBarFromApp(moveHomeToTop); } catch (RemoteException e) { Log.e(TAG, "Failed to detach the navigation bar from app", e); } diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index 1569fff63453..3f0e3eb84424 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -45,6 +45,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie private int mLockScreenColor; private boolean mIsDozing; + private float mDozeAmount; private Locale mLocale; private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my")); @@ -59,6 +60,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie super(view); mStatusBarStateController = statusBarStateController; mIsDozing = mStatusBarStateController.isDozing(); + mDozeAmount = mStatusBarStateController.getDozeAmount(); mBroadcastDispatcher = broadcastDispatcher; mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER); @@ -82,6 +84,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie new IntentFilter(Intent.ACTION_LOCALE_CHANGED)); mStatusBarStateController.addCallback(mStatusBarStateListener); mIsDozing = mStatusBarStateController.isDozing(); + mDozeAmount = mStatusBarStateController.getDozeAmount(); refreshTime(); initColors(); } @@ -136,9 +139,15 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie private final StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override - public void onDozingChanged(boolean isDozing) { - mIsDozing = isDozing; - mView.animateDoze(mIsDozing, true); + public void onDozeAmountChanged(float linear, float eased) { + boolean noAnimation = (mDozeAmount == 0f && linear == 1f) + || (mDozeAmount == 1f && linear == 0f); + boolean isDozing = linear > mDozeAmount; + mDozeAmount = linear; + if (mIsDozing != isDozing) { + mIsDozing = isDozing; + mView.animateDoze(mIsDozing, !noAnimation); + } } }; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 5760565aaab1..a580663cfa91 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -35,12 +35,15 @@ import com.android.keyguard.EmergencyButton.EmergencyButtonCallback; import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; +import com.android.systemui.classifier.FalsingClassifier; +import com.android.systemui.classifier.FalsingCollector; public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKeyInputView> extends KeyguardInputViewController<T> { private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final LockPatternUtils mLockPatternUtils; private final LatencyTracker mLatencyTracker; + private final FalsingCollector mFalsingCollector; private CountDownTimer mCountdownTimer; protected KeyguardMessageAreaController mMessageAreaController; private boolean mDismissing; @@ -70,11 +73,12 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker) { + LatencyTracker latencyTracker, FalsingCollector falsingCollector) { super(view, securityMode, keyguardSecurityCallback); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; + mFalsingCollector = falsingCollector; KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); mMessageAreaController = messageAreaControllerFactory.create(kma); } @@ -256,6 +260,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey } protected void onUserInput() { + mFalsingCollector.updateFalseConfidence(FalsingClassifier.Result.passed(0.6)); getKeyguardSecurityCallback().userActivity(); getKeyguardSecurityCallback().onUserInput(); mMessageAreaController.setMessage(""); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java index 5e02e0440c3d..de64f07259b4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java @@ -26,16 +26,11 @@ import android.widget.LinearLayout; import androidx.annotation.Nullable; import com.android.internal.jank.InteractionJankMonitor; -import com.android.systemui.Gefingerpoken; - -import java.util.ArrayList; -import java.util.List; /** * A Base class for all Keyguard password/pattern/pin related inputs. */ public abstract class KeyguardInputView extends LinearLayout { - private final List<Gefingerpoken> mMotionEventListener = new ArrayList<>(); public KeyguardInputView(Context context) { super(context); @@ -53,7 +48,6 @@ public abstract class KeyguardInputView extends LinearLayout { abstract CharSequence getTitle(); void animateForIme(float interpolatedFraction) { - return; } boolean disallowInterceptTouch(MotionEvent event) { @@ -66,27 +60,6 @@ public abstract class KeyguardInputView extends LinearLayout { return false; } - void addMotionEventListener(Gefingerpoken listener) { - mMotionEventListener.add(listener); - } - - void removeMotionEventListener(Gefingerpoken listener) { - mMotionEventListener.remove(listener); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - return mMotionEventListener.stream().anyMatch(listener -> listener.onTouchEvent(event)) - || super.onTouchEvent(event); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - return mMotionEventListener.stream().anyMatch( - listener -> listener.onInterceptTouchEvent(event)) - || super.onInterceptTouchEvent(event); - } - protected AnimatorListenerAdapter getAnimationListener(int cuj) { return new AnimatorListenerAdapter() { private boolean mIsCancel; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 957882dc9c6b..05f33a9d0997 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -155,8 +155,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final InputMethodManager mInputMethodManager; private final DelayableExecutor mMainExecutor; private final Resources mResources; - private LiftToActivateListener mLiftToActivateListener; - private TelephonyManager mTelephonyManager; + private final LiftToActivateListener mLiftToActivateListener; + private final TelephonyManager mTelephonyManager; private final FalsingCollector mFalsingCollector; private final boolean mIsNewLayoutEnabled; @@ -167,8 +167,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> KeyguardMessageAreaController.Factory messageAreaControllerFactory, InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor, @Main Resources resources, LiftToActivateListener liftToActivateListener, - TelephonyManager telephonyManager, - FalsingCollector falsingCollector, + TelephonyManager telephonyManager, FalsingCollector falsingCollector, FeatureFlags featureFlags) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; @@ -189,12 +188,13 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> if (keyguardInputView instanceof KeyguardPatternView) { return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, - keyguardSecurityCallback, mLatencyTracker, mMessageAreaControllerFactory); + keyguardSecurityCallback, mLatencyTracker, mFalsingCollector, + mMessageAreaControllerFactory); } else if (keyguardInputView instanceof KeyguardPasswordView) { return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mInputMethodManager, mMainExecutor, mResources); + mInputMethodManager, mMainExecutor, mResources, mFalsingCollector); } else if (keyguardInputView instanceof KeyguardPINView) { return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, @@ -204,14 +204,14 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener, mTelephonyManager, - mFalsingCollector, mIsNewLayoutEnabled); + mLiftToActivateListener, mTelephonyManager, mFalsingCollector, + mIsNewLayoutEnabled); } else if (keyguardInputView instanceof KeyguardSimPukView) { return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener, mTelephonyManager, - mFalsingCollector, mIsNewLayoutEnabled); + mLiftToActivateListener, mTelephonyManager, mFalsingCollector, + mIsNewLayoutEnabled); } throw new RuntimeException("Unable to find controller for " + keyguardInputView); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 0f1c3c8a20b7..57b8cf09556e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -40,6 +40,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.settingslib.Utils; import com.android.systemui.R; +import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -112,9 +113,10 @@ public class KeyguardPasswordViewController LatencyTracker latencyTracker, InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor, - @Main Resources resources) { + @Main Resources resources, + FalsingCollector falsingCollector) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker); + messageAreaControllerFactory, latencyTracker, falsingCollector); mKeyguardSecurityCallback = keyguardSecurityCallback; mInputMethodManager = inputMethodManager; mMainExecutor = mainExecutor; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 2aaf748e2415..4f48bb4f3260 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -23,6 +23,7 @@ import android.content.res.ColorStateList; import android.os.AsyncTask; import android.os.CountDownTimer; import android.os.SystemClock; +import android.view.MotionEvent; import android.view.View; import com.android.internal.util.LatencyTracker; @@ -35,6 +36,8 @@ import com.android.keyguard.EmergencyButton.EmergencyButtonCallback; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.settingslib.Utils; import com.android.systemui.R; +import com.android.systemui.classifier.FalsingClassifier; +import com.android.systemui.classifier.FalsingCollector; import java.util.List; @@ -50,6 +53,7 @@ public class KeyguardPatternViewController private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final LockPatternUtils mLockPatternUtils; private final LatencyTracker mLatencyTracker; + private final FalsingCollector mFalsingCollector; private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory; private KeyguardMessageAreaController mMessageAreaController; @@ -102,6 +106,11 @@ public class KeyguardPatternViewController final int userId = KeyguardUpdateMonitor.getCurrentUser(); if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { + // Treat single-sized patterns as erroneous taps. + if (pattern.size() == 1) { + mFalsingCollector.updateFalseConfidence(FalsingClassifier.Result.falsed( + 0.7, "empty pattern input")); + } mLockPatternView.enableInput(); onPatternChecked(userId, false, 0, false /* not valid - too short */); return; @@ -179,11 +188,13 @@ public class KeyguardPatternViewController LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, LatencyTracker latencyTracker, + FalsingCollector falsingCollector, KeyguardMessageAreaController.Factory messageAreaControllerFactory) { super(view, securityMode, keyguardSecurityCallback); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; + mFalsingCollector = falsingCollector; mMessageAreaControllerFactory = messageAreaControllerFactory; KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); mMessageAreaController = mMessageAreaControllerFactory.create(kma); @@ -205,6 +216,12 @@ public class KeyguardPatternViewController KeyguardUpdateMonitor.getCurrentUser())); // vibrate mode will be the same for the life of this screen mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); + mLockPatternView.setOnTouchListener((v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mFalsingCollector.avoidGesture(); + } + return false; + }); EmergencyButton button = mView.findViewById(R.id.emergency_call_button); if (button != null) { @@ -224,6 +241,7 @@ public class KeyguardPatternViewController protected void onViewDetached() { super.onViewDetached(); mLockPatternView.setOnPatternListener(null); + mLockPatternView.setOnTouchListener(null); EmergencyButton button = mView.findViewById(R.id.emergency_call_button); if (button != null) { button.setCallback(null); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 4e06491621cb..09fb8efba4e8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -164,6 +164,10 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView reloadColors(); } + NumPadKey[] getButtons() { + return mButtons; + } + /** * By default, the new layout will be enabled. When false, revert to the old style. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index f2479488db0f..b156f8169b50 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -25,7 +25,6 @@ import android.view.View.OnTouchListener; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; @@ -50,19 +49,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB return false; }; - private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() { - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - mFalsingCollector.avoidGesture(); - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return false; - } - }; - protected KeyguardPinBasedInputViewController(T view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, @@ -73,7 +59,7 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB LiftToActivateListener liftToActivateListener, FalsingCollector falsingCollector) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker); + messageAreaControllerFactory, latencyTracker, falsingCollector); mLiftToActivateListener = liftToActivateListener; mFalsingCollector = falsingCollector; mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); @@ -83,8 +69,14 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB protected void onViewAttached() { super.onViewAttached(); - mView.addMotionEventListener(mGlobalTouchListener); - + for (NumPadKey button: mView.getButtons()) { + button.setOnTouchListener((v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mFalsingCollector.avoidGesture(); + } + return false; + }); + } mPasswordEntry.setOnKeyListener(mOnKeyListener); mPasswordEntry.setUserActivityListener(this::onUserInput); @@ -123,7 +115,10 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB @Override protected void onViewDetached() { super.onViewDetached(); - mView.removeMotionEventListener(mGlobalTouchListener); + + for (NumPadKey button: mView.getButtons()) { + button.setOnTouchListener(null); + } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index a2d7707a1569..eaf8516ac152 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -58,10 +58,12 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.Gefingerpoken; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import java.util.ArrayList; import java.util.List; public class KeyguardSecurityContainer extends FrameLayout { @@ -97,6 +99,7 @@ public class KeyguardSecurityContainer extends FrameLayout { private final ViewConfiguration mViewConfiguration; private final SpringAnimation mSpringAnimation; private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); + private final List<Gefingerpoken> mMotionEventListeners = new ArrayList<>(); private float mLastTouchY = -1; private int mActivePointerId = -1; @@ -388,6 +391,10 @@ public class KeyguardSecurityContainer extends FrameLayout { @Override public boolean onInterceptTouchEvent(MotionEvent event) { + boolean result = mMotionEventListeners.stream().anyMatch( + listener -> listener.onInterceptTouchEvent(event)) + || super.onInterceptTouchEvent(event); + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: int pointerIndex = event.getActionIndex(); @@ -418,12 +425,17 @@ public class KeyguardSecurityContainer extends FrameLayout { mIsDragging = false; break; } - return false; + return result; } @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); + + boolean result = mMotionEventListeners.stream() + .anyMatch(listener -> listener.onTouchEvent(event)) + || super.onTouchEvent(event); + switch (action) { case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); @@ -469,6 +481,14 @@ public class KeyguardSecurityContainer extends FrameLayout { return true; } + void addMotionEventListener(Gefingerpoken listener) { + mMotionEventListeners.add(listener); + } + + void removeMotionEventListener(Gefingerpoken listener) { + mMotionEventListeners.remove(listener); + } + private void handleTap(MotionEvent event) { // If we're using a fullscreen security mode, skip if (!mOneHandedMode) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index fdab8db67431..7eac9034f02a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -33,6 +33,7 @@ import android.metrics.LogMaker; import android.os.UserHandle; import android.util.Log; import android.util.Slog; +import android.view.MotionEvent; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -46,6 +47,8 @@ import com.android.keyguard.KeyguardSecurityContainer.SwipeListener; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.keyguard.dagger.KeyguardBouncerScope; import com.android.settingslib.utils.ThreadUtils; +import com.android.systemui.Gefingerpoken; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -71,9 +74,44 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController; private final SecurityCallback mSecurityCallback; private final ConfigurationController mConfigurationController; + private final KeyguardViewController mKeyguardViewController; + private final FalsingManager mFalsingManager; private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid; + private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() { + private MotionEvent mTouchDown; + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + // Do just a bit of our own falsing. People should only be tapping on the input, not + // swiping. + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + if (mTouchDown != null) { + mTouchDown.recycle(); + mTouchDown = null; + } + mTouchDown = MotionEvent.obtain(ev); + } else if (mTouchDown != null) { + boolean tapResult = mFalsingManager.isFalseTap(true, 0.6); + if (tapResult + || ev.getActionMasked() == MotionEvent.ACTION_UP + || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { + if (tapResult) { + mKeyguardViewController.reset(true); + } + mTouchDown.recycle(); + mTouchDown = null; + } + } + return false; + } + }; + private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() { public void userActivity() { if (mSecurityCallback != null) { @@ -169,7 +207,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard KeyguardStateController keyguardStateController, SecurityCallback securityCallback, KeyguardSecurityViewFlipperController securityViewFlipperController, - ConfigurationController configurationController) { + ConfigurationController configurationController, + KeyguardViewController keyguardViewController, + FalsingManager falsingManager) { super(view); mLockPatternUtils = lockPatternUtils; mUpdateMonitor = keyguardUpdateMonitor; @@ -182,6 +222,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create( mKeyguardSecurityCallback); mConfigurationController = configurationController; + mKeyguardViewController = keyguardViewController; + mFalsingManager = falsingManager; } @Override @@ -192,12 +234,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard @Override protected void onViewAttached() { mView.setSwipeListener(mSwipeListener); + mView.addMotionEventListener(mGlobalTouchListener); mConfigurationController.addCallback(mConfigurationListener); } @Override protected void onViewDetached() { mConfigurationController.removeCallback(mConfigurationListener); + mView.removeMotionEventListener(mGlobalTouchListener); } /** */ @@ -479,6 +523,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final KeyguardStateController mKeyguardStateController; private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController; private final ConfigurationController mConfigurationController; + private final KeyguardViewController mKeyguardViewController; + private final FalsingManager mFalsingManager; @Inject Factory(KeyguardSecurityContainer view, @@ -491,7 +537,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard UiEventLogger uiEventLogger, KeyguardStateController keyguardStateController, KeyguardSecurityViewFlipperController securityViewFlipperController, - ConfigurationController configurationController) { + ConfigurationController configurationController, + KeyguardViewController keyguardViewController, + FalsingManager falsingManager) { mView = view; mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory; mLockPatternUtils = lockPatternUtils; @@ -502,6 +550,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mKeyguardStateController = keyguardStateController; mSecurityViewFlipperController = securityViewFlipperController; mConfigurationController = configurationController; + mKeyguardViewController = keyguardViewController; + mFalsingManager = falsingManager; } public KeyguardSecurityContainerController create( @@ -510,7 +560,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, mKeyguardStateController, securityCallback, mSecurityViewFlipperController, - mConfigurationController); + mConfigurationController, mKeyguardViewController, mFalsingManager); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index cdbbfe643812..b2bf2e674b33 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -78,8 +78,8 @@ public class KeyguardSimPinViewController KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, - TelephonyManager telephonyManager, - FalsingCollector falsingCollector, boolean isNewLayoutEnabled) { + TelephonyManager telephonyManager, FalsingCollector falsingCollector, + boolean isNewLayoutEnabled) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, falsingCollector); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 8fff34278216..620db481e4a2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -85,8 +85,8 @@ public class KeyguardSimPukViewController KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, - TelephonyManager telephonyManager, - FalsingCollector falsingCollector, boolean isNewLayoutEnabled) { + TelephonyManager telephonyManager, FalsingCollector falsingCollector, + boolean isNewLayoutEnabled) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, falsingCollector); diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 2040347de1b5..e53f5c97bb5c 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -20,7 +20,6 @@ import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; -import android.Manifest; import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -43,6 +42,7 @@ import androidx.annotation.WorkerThread; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; @@ -370,13 +370,9 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon } // TODO ntmyren: remove after teamfood is finished - private boolean shouldShowAppPredictor(String pkgName) { - if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "permissions_hub_2_enabled", - false)) { - return false; - } - return mPackageManager.checkPermission(Manifest.permission.MANAGE_APP_PREDICTIONS, pkgName) - == PackageManager.PERMISSION_GRANTED; + private boolean showSystemApps() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false); } /** @@ -399,8 +395,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon return true; } // TODO ntmyren: Replace this with more robust check if this moves beyond teamfood - if ((appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName)) - || shouldShowAppPredictor(packageName) + if (((showSystemApps() && !packageName.equals("android")) + || appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName)) || isSpeechRecognizerUsage(appOpCode, packageName)) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java index 43ecf6778022..2036150d3679 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java @@ -16,57 +16,63 @@ package com.android.systemui.biometrics; -import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.graphics.Canvas; -import android.graphics.PointF; import android.graphics.RectF; import android.util.AttributeSet; import android.widget.FrameLayout; -import com.android.systemui.doze.DozeReceiver; -import com.android.systemui.statusbar.phone.StatusBar; - /** * Base class for views containing UDFPS animations. Note that this is a FrameLayout so that we - * can support multiple child views drawing on the same region around the sensor location. + * can support multiple child views drawing in the same region around the sensor location. + * + * - hides animation view when pausing auth + * - sends illumination events to fingerprint drawable + * - sends sensor rect updates to fingerprint drawable + * - optionally can override dozeTimeTick to adjust views for burn-in mitigation */ -public abstract class UdfpsAnimationView extends FrameLayout implements DozeReceiver, - StatusBar.ExpansionChangedListener { - - private static final String TAG = "UdfpsAnimationView"; +abstract class UdfpsAnimationView extends FrameLayout { - @Nullable protected abstract UdfpsAnimation getUdfpsAnimation(); - - @NonNull private UdfpsView mParent; - @NonNull private RectF mSensorRect; private int mAlpha; + private boolean mPauseAuth; public UdfpsAnimationView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); - mSensorRect = new RectF(); - setWillNotDraw(false); } - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); + /** + * Fingerprint drawable + */ + abstract UdfpsDrawable getDrawable(); - if (getUdfpsAnimation() != null) { - final int alpha = mParent.shouldPauseAuth() ? mAlpha : 255; - getUdfpsAnimation().setAlpha(alpha); - getUdfpsAnimation().draw(canvas); - } + void onSensorRectUpdated(RectF bounds) { + getDrawable().onSensorRectUpdated(bounds); } - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); + void onIlluminationStarting() { + getDrawable().setIlluminationShowing(true); + getDrawable().invalidateSelf(); + } + + void onIlluminationStopped() { + getDrawable().setIlluminationShowing(false); + getDrawable().invalidateSelf(); + } - if (getUdfpsAnimation() != null) { - getUdfpsAnimation().onDestroy(); + /** + * @return true if changed + */ + boolean setPauseAuth(boolean pauseAuth) { + if (pauseAuth != mPauseAuth) { + mPauseAuth = pauseAuth; + updateAlpha(); + return true; } + return false; + } + + private void updateAlpha() { + getDrawable().setAlpha(mPauseAuth ? mAlpha : 255); } private int expansionToAlpha(float expansion) { @@ -81,76 +87,15 @@ public abstract class UdfpsAnimationView extends FrameLayout implements DozeRece return (int) ((1 - percent) * 255); } - void onIlluminationStarting() { - if (getUdfpsAnimation() == null) { - return; - } - - getUdfpsAnimation().setIlluminationShowing(true); - postInvalidate(); - } - - void onIlluminationStopped() { - if (getUdfpsAnimation() == null) { - return; - } - - getUdfpsAnimation().setIlluminationShowing(false); - postInvalidate(); - } - - void setParent(@NonNull UdfpsView parent) { - mParent = parent; - } - - void onSensorRectUpdated(@NonNull RectF sensorRect) { - mSensorRect = sensorRect; - if (getUdfpsAnimation() != null) { - getUdfpsAnimation().onSensorRectUpdated(mSensorRect); - } - } - - void updateColor() { - if (getUdfpsAnimation() != null) { - getUdfpsAnimation().updateColor(); - } - postInvalidate(); - } - - @Override - public void dozeTimeTick() { - if (getUdfpsAnimation() instanceof DozeReceiver) { - ((DozeReceiver) getUdfpsAnimation()).dozeTimeTick(); - } - } - - @Override public void onExpansionChanged(float expansion, boolean expanded) { mAlpha = expansionToAlpha(expansion); - postInvalidate(); - } - - public int getPaddingX() { - if (getUdfpsAnimation() == null) { - return 0; - } - return getUdfpsAnimation().getPaddingX(); - } - - public int getPaddingY() { - if (getUdfpsAnimation() == null) { - return 0; - } - return getUdfpsAnimation().getPaddingY(); + updateAlpha(); } /** - * @return the amount of translation needed if the view currently requires the user to touch - * somewhere other than the exact center of the sensor. For example, this can happen - * during guided enrollment. + * @return true if handled */ - @NonNull - PointF getTouchTranslation() { - return new PointF(0, 0); + boolean dozeTimeTick() { + return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java new file mode 100644 index 000000000000..b6d80ba14dc0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import static com.android.systemui.statusbar.StatusBarState.FULLSCREEN_USER_SWITCHER; +import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; + +import android.annotation.NonNull; +import android.graphics.PointF; +import android.graphics.RectF; + +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.util.ViewController; + +/** + * Handles: + * 1. registering for listeners when its view is attached and unregistering on view detached + * 2. pausing udfps when fingerprintManager may still be running but we temporarily want to hide + * the affordance. this allows us to fade the view in and out nicely (see shouldPauseAuth) + * 3. sending events to its view including: + * - illumination events + * - sensor position changes + * - doze time event + */ +abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView> + extends ViewController<T> { + @NonNull final StatusBarStateController mStatusBarStateController; + @NonNull final StatusBar mStatusBar; + + private boolean mNotificationShadeExpanded; + private int mStatusBarState; + + protected UdfpsAnimationViewController( + T view, + StatusBarStateController statusBarStateController, + StatusBar statusBar) { + super(view); + mStatusBarStateController = statusBarStateController; + mStatusBar = statusBar; + } + + @Override + protected void onViewAttached() { + mStatusBarStateController.addCallback(mStateListener); + mStateListener.onStateChanged(mStatusBarStateController.getState()); + mStatusBar.addExpansionChangedListener(mStatusBarExpansionChangedListener); + } + + @Override + protected void onViewDetached() { + mStatusBarStateController.removeCallback(mStateListener); + mStatusBar.removeExpansionChangedListener(mStatusBarExpansionChangedListener); + } + + /** + * Returns true if the fingerprint manager is running but we want to temporarily pause + * authentication. + */ + boolean shouldPauseAuth() { + return (mNotificationShadeExpanded && mStatusBarState != KEYGUARD) + || mStatusBarState == SHADE_LOCKED + || mStatusBarState == FULLSCREEN_USER_SWITCHER; + } + + /** + * Send pause auth update to our view. + */ + void updatePauseAuth() { + if (mView.setPauseAuth(shouldPauseAuth())) { + mView.postInvalidate(); + } + } + + /** + * Send sensor position change to our view. This rect contains paddingX and paddingY. + */ + void onSensorRectUpdated(RectF sensorRect) { + mView.onSensorRectUpdated(sensorRect); + } + + /** + * Send dozeTimeTick to view in case it wants to handle its burn-in offset. + */ + void dozeTimeTick() { + if (mView.dozeTimeTick()) { + mView.postInvalidate(); + } + } + + /** + * @return the amount of translation needed if the view currently requires the user to touch + * somewhere other than the exact center of the sensor. For example, this can happen + * during guided enrollment. + */ + PointF getTouchTranslation() { + return new PointF(0, 0); + } + + /** + * X-Padding to add to left and right of the sensor rectangle area to increase the size of our + * window to draw within. + * @return + */ + int getPaddingX() { + return 0; + } + + /** + * Y-Padding to add to top and bottom of the sensor rectangle area to increase the size of our + * window to draw within. + */ + int getPaddingY() { + return 0; + } + + /** + * Udfps has started illuminating and the fingerprint manager is working on authenticating. + */ + void onIlluminationStarting() { + mView.onIlluminationStarting(); + mView.postInvalidate(); + } + + /** + * Udfps has stopped illuminating and the fingerprint manager is no longer attempting to + * authenticate. + */ + void onIlluminationStopped() { + mView.onIlluminationStopped(); + mView.postInvalidate(); + } + + private final StatusBar.ExpansionChangedListener mStatusBarExpansionChangedListener = + new StatusBar.ExpansionChangedListener() { + @Override + public void onExpansionChanged(float expansion, boolean expanded) { + mNotificationShadeExpanded = expanded; + mView.onExpansionChanged(expansion, expanded); + updatePauseAuth(); + } + }; + + private final StatusBarStateController.StateListener mStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onStateChanged(int newState) { + mStatusBarState = newState; + updatePauseAuth(); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java deleted file mode 100644 index 543df33dd5d7..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewEnroll.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.PointF; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; - -import com.android.systemui.R; - -/** - * Class that coordinates non-HBM animations during enrollment. - */ -public class UdfpsAnimationViewEnroll extends UdfpsAnimationView - implements UdfpsEnrollHelper.Listener { - - private static final String TAG = "UdfpsAnimationViewEnroll"; - - @NonNull private UdfpsAnimationEnroll mUdfpsAnimation; - @NonNull private UdfpsProgressBar mProgressBar; - @Nullable private UdfpsEnrollHelper mEnrollHelper; - - @NonNull - @Override - protected UdfpsAnimation getUdfpsAnimation() { - return mUdfpsAnimation; - } - - public UdfpsAnimationViewEnroll(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - mUdfpsAnimation = new UdfpsAnimationEnroll(context); - } - - public void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { - mEnrollHelper = helper; - mUdfpsAnimation.setEnrollHelper(helper); - } - - @Override - protected void onFinishInflate() { - mProgressBar = findViewById(R.id.progress_bar); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (mEnrollHelper == null) { - Log.e(TAG, "Enroll helper is null"); - return; - } - - if (mEnrollHelper.shouldShowProgressBar()) { - mProgressBar.setVisibility(View.VISIBLE); - - // Only need enrollment updates if the progress bar is showing :) - mEnrollHelper.setListener(this); - } - } - - @Override - public void onEnrollmentProgress(int remaining, int totalSteps) { - final int interpolatedProgress = mProgressBar.getMax() - * Math.max(0, totalSteps + 1 - remaining) / (totalSteps + 1); - - mProgressBar.setProgress(interpolatedProgress, true); - } - - @NonNull - @Override - PointF getTouchTranslation() { - if (!mEnrollHelper.isCenterEnrollmentComplete()) { - return new PointF(0, 0); - } else { - return mEnrollHelper.getNextGuidedEnrollmentPoint(); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewBp.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.java index 515b442b61f6..70be907228c8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewBp.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.java @@ -24,19 +24,22 @@ import androidx.annotation.Nullable; /** * Class that coordinates non-HBM animations during BiometricPrompt. * + * Currently doesn't draw anything. + * * Note that {@link AuthBiometricUdfpsView} also shows UDFPS animations. At some point we should - * de-dupe this if necessary. This will probably happen once the top-level TODO in UdfpsController - * is completed (inflate operation-specific views, instead of inflating generic udfps_view and - * adding operation-specific animations to it). + * de-dupe this if necessary. */ -public class UdfpsAnimationViewBp extends UdfpsAnimationView { - public UdfpsAnimationViewBp(Context context, @Nullable AttributeSet attrs) { +public class UdfpsBpView extends UdfpsAnimationView { + private UdfpsFpDrawable mFingerprintDrawable; + + public UdfpsBpView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); + // Drawable isn't ever added to the view, so we don't currently show anything + mFingerprintDrawable = new UdfpsFpDrawable(mContext); } - @Nullable @Override - protected UdfpsAnimation getUdfpsAnimation() { - return null; + UdfpsDrawable getDrawable() { + return mFingerprintDrawable; } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java new file mode 100644 index 000000000000..b712c655a6e7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.StatusBar; + +/** + * Class that coordinates non-HBM animations for biometric prompt. + */ +class UdfpsBpViewController extends UdfpsAnimationViewController<UdfpsBpView> { + protected UdfpsBpViewController( + UdfpsBpView view, + StatusBarStateController statusBarStateController, + StatusBar statusBar) { + super(view, statusBarStateController, statusBar); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 4b6a8f639cc4..94aeb73c4b42 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -31,9 +31,9 @@ import android.graphics.RectF; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IUdfpsOverlayController; -import android.os.SystemClock; import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; import android.os.RemoteException; +import android.os.SystemClock; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -195,17 +195,6 @@ public class UdfpsController implements DozeReceiver, HbmCallback { } } - @VisibleForTesting final StatusBar.ExpansionChangedListener mStatusBarExpansionListener = - (expansion, expanded) -> mView.onExpansionChanged(expansion, expanded); - - @VisibleForTesting final StatusBarStateController.StateListener mStatusBarStateListener = - new StatusBarStateController.StateListener() { - @Override - public void onStateChanged(int newState) { - mView.onStateChanged(newState); - } - }; - private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { final float vx = tracker.getXVelocity(pointerId); final float vy = tracker.getYVelocity(pointerId); @@ -360,10 +349,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @Override public void dozeTimeTick() { - if (mView == null) { - return; + if (mView != null) { + mView.dozeTimeTick(); } - mView.dozeTimeTick(); } /** @@ -387,7 +375,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback { } } - private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimationView animation) { + private WindowManager.LayoutParams computeLayoutParams( + @Nullable UdfpsAnimationViewController animation) { final int paddingX = animation != null ? animation.getPaddingX() : 0; final int paddingY = animation != null ? animation.getPaddingY() : 0; @@ -438,20 +427,13 @@ public class UdfpsController implements DozeReceiver, HbmCallback { mFgExecutor.execute(() -> { if (mView == null) { try { - Log.v(TAG, "showUdfpsOverlay | adding window"); - // TODO: Eventually we should refactor the code to inflate an - // operation-specific view here, instead of inflating a generic udfps_view - // and adding operation-specific animations to it. + Log.v(TAG, "showUdfpsOverlay | adding window reason=" + reason); mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false); mView.setSensorProperties(mSensorProps); mView.setHbmCallback(this); - - final UdfpsAnimationView animation = getUdfpsAnimationViewForReason(reason); - mView.setAnimationView(animation); - - mStatusBar.addExpansionChangedListener(mStatusBarExpansionListener); - mStatusBarStateController.addCallback(mStatusBarStateListener); - mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState()); + UdfpsAnimationViewController animation = inflateUdfpsAnimation(reason); + animation.init(); + mView.setAnimationViewController(animation); mWindowManager.addView(mView, computeLayoutParams(animation)); mView.setOnTouchListener(mOnTouchListener); @@ -464,40 +446,46 @@ public class UdfpsController implements DozeReceiver, HbmCallback { }); } - @NonNull - private UdfpsAnimationView getUdfpsAnimationViewForReason(int reason) { - Log.d(TAG, "getUdfpsAnimationForReason: " + reason); - - final LayoutInflater inflater = LayoutInflater.from(mContext); - + private UdfpsAnimationViewController inflateUdfpsAnimation(int reason) { switch (reason) { case IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR: - case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: { - final UdfpsAnimationViewEnroll view = (UdfpsAnimationViewEnroll) - inflater.inflate(R.layout.udfps_animation_view_enroll, null, false); - view.setEnrollHelper(mServerRequest.mEnrollHelper); - return view; - } - - case IUdfpsOverlayController.REASON_AUTH_BP: { - final UdfpsAnimationViewBp view = (UdfpsAnimationViewBp) - inflater.inflate(R.layout.udfps_animation_view_bp, null, false); - return view; - } - - case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD: { - final UdfpsAnimationViewKeyguard view = (UdfpsAnimationViewKeyguard) - inflater.inflate(R.layout.udfps_animation_view_keyguard, null, false); - view.setStatusBarStateController(mStatusBarStateController); - return view; - } - - case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER: { - final UdfpsAnimationViewFpmOther view = (UdfpsAnimationViewFpmOther) - inflater.inflate(R.layout.udfps_animation_view_fpm_other, null, false); - return view; - } - + case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: + UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate( + R.layout.udfps_enroll_view, null); + mView.addView(enrollView); + return new UdfpsEnrollViewController( + enrollView, + mServerRequest.mEnrollHelper, + mStatusBarStateController, + mStatusBar + ); + case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD: + UdfpsKeyguardView keyguardView = (UdfpsKeyguardView) + mInflater.inflate(R.layout.udfps_keyguard_view, null); + mView.addView(keyguardView); + return new UdfpsKeyguardViewController( + keyguardView, + mStatusBarStateController, + mStatusBar + ); + case IUdfpsOverlayController.REASON_AUTH_BP: + // note: empty controller, currently shows no visual affordance + UdfpsBpView bpView = (UdfpsBpView) mInflater.inflate(R.layout.udfps_bp_view, null); + mView.addView(bpView); + return new UdfpsBpViewController( + bpView, + mStatusBarStateController, + mStatusBar + ); + case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER: + UdfpsFpmOtherView authOtherView = (UdfpsFpmOtherView) + mInflater.inflate(R.layout.udfps_fpm_other_view, null); + mView.addView(authOtherView); + return new UdfpsFpmOtherViewController( + authOtherView, + mStatusBarStateController, + mStatusBar + ); default: Log.d(TAG, "Animation for reason " + reason + " not supported yet"); return null; @@ -510,11 +498,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback { Log.v(TAG, "hideUdfpsOverlay | removing window"); // Reset the controller back to its starting state. onFingerUp(); - - mStatusBar.removeExpansionChangedListener(mStatusBarExpansionListener); - mStatusBarStateController.removeCallback(mStatusBarStateListener); - mWindowManager.removeView(mView); + mView.setOnTouchListener(null); + mView.setAnimationViewController(null); mView = null; } else { Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden"); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java index a51b6fd16445..13d31cb87fdc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java @@ -17,10 +17,10 @@ package com.android.systemui.biometrics; import android.content.Context; +import android.graphics.ColorFilter; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; -import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -28,24 +28,24 @@ import androidx.annotation.Nullable; import com.android.systemui.R; /** - * Abstract base class for animations that should be drawn when the finger is not touching the + * Abstract base class for drawable displayed when the finger is not touching the * sensor area. */ -public abstract class UdfpsAnimation extends Drawable { - protected abstract void updateColor(); - protected abstract void onDestroy(); - +public abstract class UdfpsDrawable extends Drawable { @NonNull protected final Context mContext; @NonNull protected final Drawable mFingerprintDrawable; - @Nullable private View mView; private boolean mIlluminationShowing; - public UdfpsAnimation(@NonNull Context context) { + int mAlpha = 255; // 0 - 255 + public UdfpsDrawable(@NonNull Context context) { mContext = context; mFingerprintDrawable = context.getResources().getDrawable(R.drawable.ic_fingerprint, null); mFingerprintDrawable.mutate(); } + /** + * @param sensorRect the rect coordinates for the sensor area + */ public void onSensorRectUpdated(@NonNull RectF sensorRect) { final int margin = (int) sensorRect.height() / 8; @@ -56,17 +56,17 @@ public abstract class UdfpsAnimation extends Drawable { updateFingerprintIconBounds(bounds); } + /** + * Bounds for the fingerprint icon + */ protected void updateFingerprintIconBounds(@NonNull Rect bounds) { mFingerprintDrawable.setBounds(bounds); } @Override public void setAlpha(int alpha) { - mFingerprintDrawable.setAlpha(alpha); - } - - public void setAnimationView(UdfpsAnimationView view) { - mView = view; + mAlpha = alpha; + mFingerprintDrawable.setAlpha(mAlpha); } boolean isIlluminationShowing() { @@ -77,23 +77,12 @@ public abstract class UdfpsAnimation extends Drawable { mIlluminationShowing = showing; } - /** - * @return The amount of padding that's needed on each side of the sensor, in pixels. - */ - public int getPaddingX() { - return 0; + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { } - /** - * @return The amount of padding that's needed on each side of the sensor, in pixels. - */ - public int getPaddingY() { + @Override + public int getOpacity() { return 0; } - - protected void postInvalidateView() { - if (mView != null) { - mView.postInvalidate(); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index 015a598e972b..d80e085bdc70 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Rect; @@ -33,23 +32,23 @@ import androidx.annotation.Nullable; import com.android.systemui.R; /** - * UDFPS animations that should be shown when enrolling. + * UDFPS fingerprint drawable that is shown when enrolling */ -public class UdfpsAnimationEnroll extends UdfpsAnimation { +public class UdfpsEnrollDrawable extends UdfpsDrawable { private static final String TAG = "UdfpsAnimationEnroll"; private static final float SHADOW_RADIUS = 5.f; - private static final float PROGRESS_BAR_RADIUS = 140.f; + static final float PROGRESS_BAR_RADIUS = 140.f; @NonNull private final Drawable mMovingTargetFpIcon; @NonNull private final Paint mSensorPaint; @NonNull private final Paint mBlueFill; - @NonNull private final Paint mBlueStroke;; + @NonNull private final Paint mBlueStroke; @Nullable private RectF mSensorRect; @Nullable private UdfpsEnrollHelper mEnrollHelper; - UdfpsAnimationEnroll(@NonNull Context context) { + UdfpsEnrollDrawable(@NonNull Context context) { super(context); mSensorPaint = new Paint(0 /* flags */); @@ -72,20 +71,12 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { mMovingTargetFpIcon = context.getResources().getDrawable(R.drawable.ic_fingerprint, null); mMovingTargetFpIcon.setTint(Color.WHITE); mMovingTargetFpIcon.mutate(); - } - - void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { - mEnrollHelper = helper; - } - @Override - protected void updateColor() { mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon)); } - @Override - protected void onDestroy() { - + void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { + mEnrollHelper = helper; } @Override @@ -98,6 +89,7 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { protected void updateFingerprintIconBounds(@NonNull Rect bounds) { super.updateFingerprintIconBounds(bounds); mMovingTargetFpIcon.setBounds(bounds); + invalidateSelf(); } @Override @@ -117,7 +109,7 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { // Draw moving target if (mEnrollHelper.isCenterEnrollmentComplete()) { - mFingerprintDrawable.setAlpha(64); + mFingerprintDrawable.setAlpha(mAlpha == 255 ? 64 : mAlpha); canvas.save(); final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint(); @@ -130,33 +122,16 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { mMovingTargetFpIcon.draw(canvas); canvas.restore(); } else { - mFingerprintDrawable.setAlpha(255); + mFingerprintDrawable.setAlpha(mAlpha); } } @Override - public int getPaddingX() { - return (int) Math.ceil(PROGRESS_BAR_RADIUS); - } - - @Override - public int getPaddingY() { - return (int) Math.ceil(PROGRESS_BAR_RADIUS); - } - - @Override public void setAlpha(int alpha) { super.setAlpha(alpha); mSensorPaint.setAlpha(alpha); - } - - @Override - public void setColorFilter(@Nullable ColorFilter colorFilter) { - - } - - @Override - public int getOpacity() { - return 0; + mBlueFill.setAlpha(alpha); + mBlueStroke.setAlpha(alpha); + invalidateSelf(); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index 667b7a7cf0a3..98a703f595d2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -21,6 +21,9 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.PointF; import android.hardware.fingerprint.IUdfpsOverlayController; +import android.os.Build; +import android.os.UserHandle; +import android.provider.Settings; import android.util.TypedValue; import java.util.ArrayList; @@ -32,6 +35,10 @@ import java.util.List; public class UdfpsEnrollHelper { private static final String TAG = "UdfpsEnrollHelper"; + private static final String SCALE_OVERRIDE = + "com.android.systemui.biometrics.UdfpsEnrollHelper.scale"; + private static final float SCALE = 0.5f; + // Enroll with two center touches before going to guided enrollment private static final int NUM_CENTER_TOUCHES = 2; @@ -39,9 +46,10 @@ public class UdfpsEnrollHelper { void onEnrollmentProgress(int remaining, int totalSteps); } + @NonNull private final Context mContext; // IUdfpsOverlayController reason private final int mEnrollReason; - private final List<PointF> mGuidedEnrollmentPoints; + @NonNull private final List<PointF> mGuidedEnrollmentPoints; private int mTotalSteps = -1; private int mRemainingSteps = -1; @@ -53,6 +61,7 @@ public class UdfpsEnrollHelper { @Nullable Listener mListener; public UdfpsEnrollHelper(@NonNull Context context, int reason) { + mContext = context; mEnrollReason = reason; mGuidedEnrollmentPoints = new ArrayList<>(); @@ -100,13 +109,13 @@ public class UdfpsEnrollHelper { } - void setListener(@NonNull Listener listener) { + void setListener(Listener listener) { mListener = listener; // Only notify during setListener if enrollment is already in progress, so the progress // bar can be updated. If enrollment has not started yet, the progress bar will be empty // anyway. - if (mTotalSteps != -1) { + if (mListener != null && mTotalSteps != -1) { mListener.onEnrollmentProgress(mRemainingSteps, mTotalSteps); } } @@ -121,9 +130,15 @@ public class UdfpsEnrollHelper { @NonNull PointF getNextGuidedEnrollmentPoint() { + float scale = SCALE; + if (Build.IS_ENG || Build.IS_USERDEBUG) { + scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(), + SCALE_OVERRIDE, SCALE, + UserHandle.USER_CURRENT); + } final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES; final PointF originalPoint = mGuidedEnrollmentPoints .get(index % mGuidedEnrollmentPoints.size()); - return new PointF(originalPoint.x * 0.5f, originalPoint.y * 0.5f); + return new PointF(originalPoint.x * scale, originalPoint.y * scale); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java index 7d0b3e59feb1..7985d95c7c61 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewKeyguard.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java @@ -18,32 +18,36 @@ package com.android.systemui.biometrics; import android.content.Context; import android.util.AttributeSet; +import android.widget.ImageView; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.R; /** - * Class that coordinates non-HBM animations during keyguard authentication. + * View corresponding with udfps_enroll_view.xml */ -public class UdfpsAnimationViewKeyguard extends UdfpsAnimationView { - @Nullable private UdfpsAnimationKeyguard mAnimation; +public class UdfpsEnrollView extends UdfpsAnimationView { + private final UdfpsEnrollDrawable mFingerprintDrawable; + private ImageView mFingerprintView; - public UdfpsAnimationViewKeyguard(Context context, @Nullable AttributeSet attrs) { + public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); + mFingerprintDrawable = new UdfpsEnrollDrawable(mContext); } - void setStatusBarStateController(@NonNull StatusBarStateController statusBarStateController) { - if (mAnimation == null) { - mAnimation = new UdfpsAnimationKeyguard(getContext(), statusBarStateController); - mAnimation.setAnimationView(this); - } + @Override + protected void onFinishInflate() { + mFingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view); + mFingerprintView.setImageDrawable(mFingerprintDrawable); } - @Nullable @Override - protected UdfpsAnimation getUdfpsAnimation() { - return mAnimation; + public UdfpsDrawable getDrawable() { + return mFingerprintDrawable; + } + + void setEnrollHelper(UdfpsEnrollHelper enrollHelper) { + mFingerprintDrawable.setEnrollHelper(enrollHelper); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java new file mode 100644 index 000000000000..da8d712ebbdc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import android.annotation.NonNull; +import android.graphics.PointF; +import android.view.View; + +import com.android.systemui.R; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.StatusBar; + +/** + * Class that coordinates non-HBM animations during enrollment. + */ +public class UdfpsEnrollViewController extends UdfpsAnimationViewController<UdfpsEnrollView> { + @NonNull private final UdfpsProgressBar mProgressBar; + @NonNull private final UdfpsEnrollHelper mEnrollHelper; + + protected UdfpsEnrollViewController( + UdfpsEnrollView view, + @NonNull UdfpsEnrollHelper enrollHelper, + StatusBarStateController statusBarStateController, + StatusBar statusBar) { + super(view, statusBarStateController, statusBar); + mEnrollHelper = enrollHelper; + mProgressBar = mView.findViewById(R.id.progress_bar); + mView.setEnrollHelper(mEnrollHelper); + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + if (mEnrollHelper.shouldShowProgressBar()) { + mProgressBar.setVisibility(View.VISIBLE); + + // Only need enrollment updates if the progress bar is showing :) + mEnrollHelper.setListener(mEnrollHelperListener); + } + } + + @Override + protected void onViewDetached() { + super.onViewDetached(); + mEnrollHelper.setListener(null); + } + + @NonNull + @Override + public PointF getTouchTranslation() { + if (!mEnrollHelper.isCenterEnrollmentComplete()) { + return new PointF(0, 0); + } else { + return mEnrollHelper.getNextGuidedEnrollmentPoint(); + } + } + + @Override + public int getPaddingX() { + return (int) Math.ceil(UdfpsEnrollDrawable.PROGRESS_BAR_RADIUS); + } + + @Override + public int getPaddingY() { + return (int) Math.ceil(UdfpsEnrollDrawable.PROGRESS_BAR_RADIUS); + } + + private final UdfpsEnrollHelper.Listener mEnrollHelperListener = + new UdfpsEnrollHelper.Listener() { + @Override + public void onEnrollmentProgress(int remaining, int totalSteps) { + final int interpolatedProgress = mProgressBar.getMax() + * Math.max(0, totalSteps + 1 - remaining) / (totalSteps + 1); + + mProgressBar.setProgress(interpolatedProgress, true); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.java index ef7a34000841..09b6fabbdd15 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.java @@ -18,32 +18,19 @@ package com.android.systemui.biometrics; import android.content.Context; import android.graphics.Canvas; -import android.graphics.ColorFilter; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; /** - * UDFPS animations that should be shown when authenticating via FingerprintManager, excluding - * keyguard. + * Draws udfps fingerprint if sensor isn't illuminating. */ -public class UdfpsAnimationFpmOther extends UdfpsAnimation { +public class UdfpsFpDrawable extends UdfpsDrawable { - UdfpsAnimationFpmOther(@NonNull Context context) { + UdfpsFpDrawable(@NonNull Context context) { super(context); } @Override - protected void updateColor() { - - } - - @Override - protected void onDestroy() { - - } - - @Override public void draw(@NonNull Canvas canvas) { if (isIlluminationShowing()) { return; @@ -51,14 +38,4 @@ public class UdfpsAnimationFpmOther extends UdfpsAnimation { mFingerprintDrawable.draw(canvas); } - - @Override - public void setColorFilter(@Nullable ColorFilter colorFilter) { - - } - - @Override - public int getOpacity() { - return 0; - } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherView.java index 3d2f5a0fe5cf..85f16068188e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewFpmOther.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherView.java @@ -18,25 +18,32 @@ package com.android.systemui.biometrics; import android.content.Context; import android.util.AttributeSet; +import android.widget.ImageView; import androidx.annotation.Nullable; +import com.android.systemui.R; + /** - * Class that coordinates non-HBM animations during other usage of FingerprintManager (not - * including Keyguard). + * View corresponding with udfps_fpm_other_view.xml */ -public class UdfpsAnimationViewFpmOther extends UdfpsAnimationView { - - private final UdfpsAnimationFpmOther mAnimation; +public class UdfpsFpmOtherView extends UdfpsAnimationView { + private final UdfpsFpDrawable mFingerprintDrawable; + private ImageView mFingerprintView; - public UdfpsAnimationViewFpmOther(Context context, @Nullable AttributeSet attrs) { + public UdfpsFpmOtherView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); - mAnimation = new UdfpsAnimationFpmOther(context); + mFingerprintDrawable = new UdfpsFpDrawable(context); + } + + @Override + protected void onFinishInflate() { + mFingerprintView = findViewById(R.id.udfps_fpm_other_fp_view); + mFingerprintView.setImageDrawable(mFingerprintDrawable); } - @Nullable @Override - protected UdfpsAnimation getUdfpsAnimation() { - return mAnimation; + UdfpsDrawable getDrawable() { + return mFingerprintDrawable; } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java new file mode 100644 index 000000000000..587501bd1aa5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.StatusBar; + +/** + * Class that coordinates non-HBM animations for non keyguard, enrollment or biometric prompt + * states. + * + * Currently only shows the fp drawable. + */ +class UdfpsFpmOtherViewController extends UdfpsAnimationViewController<UdfpsFpmOtherView> { + protected UdfpsFpmOtherViewController( + UdfpsFpmOtherView view, + StatusBarStateController statusBarStateController, + StatusBar statusBar) { + super(view, statusBarStateController, statusBar); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java index 5f268cfa8fa5..b0c5da09d916 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java @@ -21,28 +21,25 @@ import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorFilter; import android.util.MathUtils; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.doze.DozeReceiver; -import com.android.systemui.plugins.statusbar.StatusBarStateController; /** * UDFPS animations that should be shown when authenticating on keyguard. */ -public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiver, - StatusBarStateController.StateListener { +public class UdfpsKeyguardDrawable extends UdfpsDrawable implements DozeReceiver { private static final String TAG = "UdfpsAnimationKeyguard"; + private final int mLockScreenColor; + private final int mAmbientDisplayColor; @NonNull private final Context mContext; - @NonNull private final StatusBarStateController mStatusBarStateController; private final int mMaxBurnInOffsetX; private final int mMaxBurnInOffsetY; @@ -51,18 +48,19 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv private float mBurnInOffsetX; private float mBurnInOffsetY; - UdfpsAnimationKeyguard(@NonNull Context context, - @NonNull StatusBarStateController statusBarStateController) { + UdfpsKeyguardDrawable(@NonNull Context context) { super(context); mContext = context; - mStatusBarStateController = statusBarStateController; + // TODO: move burn-in to view mMaxBurnInOffsetX = context.getResources() .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); mMaxBurnInOffsetY = context.getResources() .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); - statusBarStateController.addCallback(this); + mLockScreenColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); + mAmbientDisplayColor = Color.WHITE; + updateAodPositionAndColor(); } private void updateAodPositionAndColor() { @@ -74,18 +72,14 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) - mMaxBurnInOffsetY, mInterpolatedDarkAmount); - updateColor(); - postInvalidateView(); - } - @Override - public void dozeTimeTick() { - updateAodPositionAndColor(); + mFingerprintDrawable.setTint(ColorUtils.blendARGB(mLockScreenColor, + mAmbientDisplayColor, mInterpolatedDarkAmount)); + invalidateSelf(); } @Override - public void onDozeAmountChanged(float linear, float eased) { - mInterpolatedDarkAmount = eased; + public void dozeTimeTick() { updateAodPositionAndColor(); } @@ -94,34 +88,11 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv if (isIlluminationShowing()) { return; } - - canvas.save(); - canvas.translate(mBurnInOffsetX, mBurnInOffsetY); mFingerprintDrawable.draw(canvas); - canvas.restore(); } - @Override - public void setColorFilter(@Nullable ColorFilter colorFilter) { - - } - - @Override - public int getOpacity() { - return 0; - } - - @Override - protected void updateColor() { - final int lockScreenIconColor = Utils.getColorAttrDefaultColor(mContext, - R.attr.wallpaperTextColor); - final int ambientDisplayIconColor = Color.WHITE; - mFingerprintDrawable.setTint(ColorUtils.blendARGB(lockScreenIconColor, - ambientDisplayIconColor, mInterpolatedDarkAmount)); - } - - @Override - protected void onDestroy() { - mStatusBarStateController.removeCallback(this); + void onDozeAmountChanged(float linear, float eased) { + mInterpolatedDarkAmount = eased; + updateAodPositionAndColor(); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java new file mode 100644 index 000000000000..6a9356034d22 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +import androidx.annotation.Nullable; + +import com.android.systemui.R; + +/** + * View corresponding with udfps_keyguard_view.xml + */ +public class UdfpsKeyguardView extends UdfpsAnimationView { + private final UdfpsKeyguardDrawable mFingerprintDrawable; + private ImageView mFingerprintView; + + public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mFingerprintDrawable = new UdfpsKeyguardDrawable(mContext); + } + + @Override + protected void onFinishInflate() { + mFingerprintView = findViewById(R.id.udfps_keyguard_animation_fp_view); + mFingerprintView.setImageDrawable(mFingerprintDrawable); + } + + @Override + public UdfpsDrawable getDrawable() { + return mFingerprintDrawable; + } + + @Override + public boolean dozeTimeTick() { + // TODO: burnin + mFingerprintDrawable.dozeTimeTick(); + return true; + } + + void onDozeAmountChanged(float linear, float eased) { + mFingerprintDrawable.onDozeAmountChanged(linear, eased); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java new file mode 100644 index 000000000000..14bb3fee1174 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.StatusBar; + +/** + * Class that coordinates non-HBM animations during keyguard authentication. + */ +public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> { + private boolean mForceShow; + + protected UdfpsKeyguardViewController( + UdfpsKeyguardView view, + StatusBarStateController statusBarStateController, + StatusBar statusBar) { + super(view, statusBarStateController, statusBar); + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + mStatusBarStateController.addCallback(mStateListener); + final float dozeAmount = mStatusBarStateController.getDozeAmount(); + mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount); + } + + @Override + protected void onViewDetached() { + super.onViewDetached(); + mStatusBarStateController.removeCallback(mStateListener); + } + + /** + * Overrides non-force show logic in shouldPauseAuth to still auth. + */ + private void forceShow(boolean forceShow) { + if (mForceShow == forceShow) { + return; + } + + mForceShow = forceShow; + updatePauseAuth(); + // TODO: animate show/hide background protection + } + + /** + * Returns true if the fingerprint manager is running but we want to temporarily pause + * authentication. On the keyguard, we may want to show udfps when the shade + * is expanded, so this can be overridden with the forceShow method. + */ + public boolean shouldPauseAuth() { + if (mForceShow) { + return false; + } + return super.shouldPauseAuth(); + } + + private final StatusBarStateController.StateListener mStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onDozeAmountChanged(float linear, float eased) { + mView.onDozeAmountChanged(linear, eased); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index a52bddc1dcd5..42d0d8438e15 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -16,10 +16,6 @@ package com.android.systemui.biometrics; -import static com.android.systemui.statusbar.StatusBarState.FULLSCREEN_USER_SWITCHER; -import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; -import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; - import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -39,15 +35,12 @@ import android.widget.FrameLayout; import com.android.systemui.R; import com.android.systemui.doze.DozeReceiver; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.StatusBar; /** * A view containing 1) A SurfaceView for HBM, and 2) A normal drawable view for all other * animations. */ -public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIlluminator, - StatusBarStateController.StateListener, StatusBar.ExpansionChangedListener { +public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIlluminator { private static final String TAG = "UdfpsView"; private static final int DEBUG_TEXT_SIZE_PX = 32; @@ -56,7 +49,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @NonNull private final Paint mDebugTextPaint; @NonNull private UdfpsSurfaceView mHbmSurfaceView; - @Nullable private UdfpsAnimationView mAnimationView; + @Nullable private UdfpsAnimationViewController mAnimationViewController; // Used to obtain the sensor location. @NonNull private FingerprintSensorPropertiesInternal mSensorProps; @@ -64,8 +57,6 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin private final float mSensorTouchAreaCoefficient; @Nullable private String mDebugMessage; private boolean mIlluminationRequested; - private int mStatusBarState; - private boolean mNotificationShadeExpanded; public UdfpsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -108,15 +99,6 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin mSensorProps = properties; } - void setAnimationView(@NonNull UdfpsAnimationView animation) { - mAnimationView = animation; - animation.setParent(this); - - // TODO: Consider using a ViewStub placeholder to maintain positioning and inflating it - // after the animation type has been decided. - addView(animation, 0); - } - @Override public void setHbmCallback(@Nullable HbmCallback callback) { mHbmSurfaceView.setHbmCallback(callback); @@ -124,45 +106,38 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void dozeTimeTick() { - if (mAnimationView == null) { - return; - } - mAnimationView.dozeTimeTick(); - } - - @Override - public void onStateChanged(int newState) { - mStatusBarState = newState; - } - - @Override - public void onExpansionChanged(float expansion, boolean expanded) { - mNotificationShadeExpanded = expanded; - - if (mAnimationView != null) { - mAnimationView.onExpansionChanged(expansion, expanded); + if (mAnimationViewController != null) { + mAnimationViewController.dozeTimeTick(); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - mSensorRect.set(0 + mAnimationView.getPaddingX(), - 0 + mAnimationView.getPaddingY(), - 2 * mSensorProps.sensorRadius + mAnimationView.getPaddingX(), - 2 * mSensorProps.sensorRadius + mAnimationView.getPaddingY()); + int paddingX = mAnimationViewController == null ? 0 + : mAnimationViewController.getPaddingX(); + int paddingY = mAnimationViewController == null ? 0 + : mAnimationViewController.getPaddingY(); + mSensorRect.set( + paddingX, + paddingY, + 2 * mSensorProps.sensorRadius + paddingX, + 2 * mSensorProps.sensorRadius + paddingY); mHbmSurfaceView.onSensorRectUpdated(new RectF(mSensorRect)); - mAnimationView.onSensorRectUpdated(new RectF(mSensorRect)); + if (mAnimationViewController != null) { + mAnimationViewController.onSensorRectUpdated(new RectF(mSensorRect)); + } + } + + void setAnimationViewController(UdfpsAnimationViewController animationViewController) { + mAnimationViewController = animationViewController; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); Log.v(TAG, "onAttachedToWindow"); - - // Retrieve the colors each time, since it depends on day/night mode - mAnimationView.updateColor(); } @Override @@ -188,7 +163,9 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin boolean isWithinSensorArea(float x, float y) { // The X and Y coordinates of the sensor's center. - final PointF translation = mAnimationView.getTouchTranslation(); + final PointF translation = mAnimationViewController == null + ? new PointF(0, 0) + : mAnimationViewController.getTouchTranslation(); final float cx = mSensorRect.centerX() + translation.x; final float cy = mSensorRect.centerY() + translation.y; // Radii along the X and Y axes. @@ -199,18 +176,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin && x < (cx + rx * mSensorTouchAreaCoefficient) && y > (cy - ry * mSensorTouchAreaCoefficient) && y < (cy + ry * mSensorTouchAreaCoefficient) - && !shouldPauseAuth(); - } - - /** - * States where UDFPS should temporarily not be authenticating. Instead of completely stopping - * authentication which would cause the UDFPS icons to abruptly disappear, do it here by not - * sending onFingerDown and smoothly animating away. - */ - boolean shouldPauseAuth() { - return (mNotificationShadeExpanded && mStatusBarState != KEYGUARD) - || mStatusBarState == SHADE_LOCKED - || mStatusBarState == FULLSCREEN_USER_SWITCHER; + && !mAnimationViewController.shouldPauseAuth(); } boolean isIlluminationRequested() { @@ -223,7 +189,9 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void startIllumination(@Nullable Runnable onIlluminatedRunnable) { mIlluminationRequested = true; - mAnimationView.onIlluminationStarting(); + if (mAnimationViewController != null) { + mAnimationViewController.onIlluminationStarting(); + } mHbmSurfaceView.setVisibility(View.VISIBLE); mHbmSurfaceView.startIllumination(onIlluminatedRunnable); } @@ -231,7 +199,9 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @Override public void stopIllumination() { mIlluminationRequested = false; - mAnimationView.onIlluminationStopped(); + if (mAnimationViewController != null) { + mAnimationViewController.onIlluminationStopped(); + } mHbmSurfaceView.setVisibility(View.INVISIBLE); mHbmSurfaceView.stopIllumination(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index 6572ca95b5c3..efb799294004 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -18,7 +18,6 @@ package com.android.systemui.classifier; import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_SUCCESS; import static com.android.systemui.classifier.FalsingModule.BRIGHT_LINE_GESTURE_CLASSIFERS; -import static com.android.systemui.classifier.FalsingModule.DOUBLE_TAP_TIMEOUT_MS; import android.net.Uri; import android.os.Build; @@ -29,11 +28,9 @@ import androidx.annotation.NonNull; import com.android.internal.logging.MetricsLogger; import com.android.systemui.classifier.FalsingDataProvider.SessionListener; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.TestHarness; import com.android.systemui.dock.DockManager; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.sensors.ThresholdSensor; import java.io.FileDescriptor; @@ -63,14 +60,13 @@ public class BrightLineFalsingManager implements FalsingManager { private static final int RECENT_INFO_LOG_SIZE = 40; private static final int RECENT_SWIPE_LOG_SIZE = 20; + private static final double TAP_CONFIDENCE_THRESHOLD = 0.7; private final FalsingDataProvider mDataProvider; private final DockManager mDockManager; private final SingleTapClassifier mSingleTapClassifier; private final DoubleTapClassifier mDoubleTapClassifier; private final HistoryTracker mHistoryTracker; - private final DelayableExecutor mDelayableExecutor; - private final long mDoubleTapTimeMs; private final boolean mTestHarness; private final MetricsLogger mMetricsLogger; private int mIsFalseTouchCalls; @@ -98,19 +94,7 @@ public class BrightLineFalsingManager implements FalsingManager { @Override public void onGestureComplete(long completionTimeMs) { if (mPriorResults != null) { - // Single taps that may become double taps don't get added right away. - if (mClassifyAsSingleTap) { - Collection<FalsingClassifier.Result> singleTapResults = mPriorResults; - mSingleTapHistoryCanceller = mDelayableExecutor.executeDelayed( - () -> { - mSingleTapHistoryCanceller = null; - mHistoryTracker.addResults(singleTapResults, completionTimeMs); - }, - mDoubleTapTimeMs); - mClassifyAsSingleTap = false; // Don't treat things as single taps by default. - } else { - mHistoryTracker.addResults(mPriorResults, completionTimeMs); - } + mHistoryTracker.addResults(mPriorResults, completionTimeMs); mPriorResults = null; } else { // Gestures that were not classified get treated as a false. @@ -123,17 +107,13 @@ public class BrightLineFalsingManager implements FalsingManager { }; private Collection<FalsingClassifier.Result> mPriorResults; - private boolean mClassifyAsSingleTap; - private Runnable mSingleTapHistoryCanceller; @Inject public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider, DockManager dockManager, MetricsLogger metricsLogger, @Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers, SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier, - HistoryTracker historyTracker, @Main DelayableExecutor delayableExecutor, - @Named(DOUBLE_TAP_TIMEOUT_MS) long doubleTapTimeMs, - @TestHarness boolean testHarness) { + HistoryTracker historyTracker, @TestHarness boolean testHarness) { mDataProvider = falsingDataProvider; mDockManager = dockManager; mMetricsLogger = metricsLogger; @@ -141,8 +121,6 @@ public class BrightLineFalsingManager implements FalsingManager { mSingleTapClassifier = singleTapClassifier; mDoubleTapClassifier = doubleTapClassifier; mHistoryTracker = historyTracker; - mDelayableExecutor = delayableExecutor; - mDoubleTapTimeMs = doubleTapTimeMs; mTestHarness = testHarness; mDataProvider.addSessionListener(mSessionListener); @@ -158,7 +136,6 @@ public class BrightLineFalsingManager implements FalsingManager { public boolean isFalseTouch(@Classifier.InteractionType int interactionType) { boolean result; - mClassifyAsSingleTap = false; mDataProvider.setInteractionType(interactionType); if (!mTestHarness && !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()) { @@ -166,7 +143,7 @@ public class BrightLineFalsingManager implements FalsingManager { mClassifiers.stream().map(falsingClassifier -> { FalsingClassifier.Result classifierResult = falsingClassifier.classifyGesture( - mHistoryTracker.falsePenalty(), + mHistoryTracker.falseBelief(), mHistoryTracker.falseConfidence()); if (classifierResult.isFalse()) { logInfo(String.format( @@ -217,9 +194,7 @@ public class BrightLineFalsingManager implements FalsingManager { } @Override - public boolean isFalseTap(boolean robustCheck) { - mClassifyAsSingleTap = true; - + public boolean isFalseTap(boolean robustCheck, double falsePenalty) { FalsingClassifier.Result singleTapResult = mSingleTapClassifier.isTap(mDataProvider.getRecentMotionEvents()); mPriorResults = Collections.singleton(singleTapResult); @@ -233,14 +208,24 @@ public class BrightLineFalsingManager implements FalsingManager { return true; } - // TODO(b/172655679): More heuristics to come. For now, allow touches through if face-authed if (robustCheck) { - boolean result = !mDataProvider.isJustUnlockedWithFace(); - mPriorResults = Collections.singleton( - result ? FalsingClassifier.Result.falsed(0.1, "no face detected") - : FalsingClassifier.Result.passed(1)); - - return result; + if (mDataProvider.isJustUnlockedWithFace()) { + // Immediately pass if a face is detected. + mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(1)); + return false; + } else if (!isFalseDoubleTap()) { + // We must check double tapping before other heuristics. This is because + // the double tap will fail if there's only been one tap. We don't want that + // failure to be recorded in mPriorResults. + return false; + } else if (mHistoryTracker.falseBelief() > TAP_CONFIDENCE_THRESHOLD) { + mPriorResults = Collections.singleton( + FalsingClassifier.Result.falsed(0, "bad history")); + return true; + } else { + mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(0.1)); + return false; + } } return false; @@ -248,7 +233,6 @@ public class BrightLineFalsingManager implements FalsingManager { @Override public boolean isFalseDoubleTap() { - mClassifyAsSingleTap = false; FalsingClassifier.Result result = mDoubleTapClassifier.classifyGesture(); mPriorResults = Collections.singleton(result); if (result.isFalse()) { @@ -258,12 +242,6 @@ public class BrightLineFalsingManager implements FalsingManager { if (reason != null) { logInfo(reason); } - } else { - // A valid double tap prevents an invalid single tap from going into history. - if (mSingleTapHistoryCanceller != null) { - mSingleTapHistoryCanceller.run(); - mSingleTapHistoryCanceller = null; - } } return result.isFalse(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java index bbb937176f59..ffcdb93b11b1 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java @@ -62,7 +62,7 @@ class DiagonalClassifier extends FalsingClassifier { VERTICAL_ANGLE_RANGE); } - Result calculateFalsingResult(double historyPenalty, double historyConfidence) { + Result calculateFalsingResult(double historyBelief, double historyConfidence) { float angle = getAngle(); if (angle == Float.MAX_VALUE) { // Unknown angle diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java index 4cb5aa2cce37..0f121c1c9ae7 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java @@ -147,7 +147,7 @@ class DistanceClassifier extends FalsingClassifier { } @Override - Result calculateFalsingResult(double historyPenalty, double historyConfidence) { + Result calculateFalsingResult(double historyBelief, double historyConfidence) { return !getPassedFlingThreshold() ? Result.falsed(0.5, getReason()) : Result.passed(0.5); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java index f7fe14a8e841..baa54a65e4fc 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java @@ -46,14 +46,14 @@ public class DoubleTapClassifier extends FalsingClassifier { } @Override - Result calculateFalsingResult(double historyPenalty, double historyConfidence) { + Result calculateFalsingResult(double historyBelief, double historyConfidence) { List<MotionEvent> secondTapEvents = getRecentMotionEvents(); List<MotionEvent> firstTapEvents = getPriorMotionEvents(); StringBuilder reason = new StringBuilder(); if (firstTapEvents == null) { - return Result.falsed(1, "Only one gesture recorded"); + return Result.falsed(0, "Only one gesture recorded"); } return !isDoubleTap(firstTapEvents, secondTapEvents, reason) diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java index f40045b0f08e..1af5f7c488a5 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java @@ -124,7 +124,7 @@ public abstract class FalsingClassifier { * See also {@link #classifyGesture(double, double)}. */ Result classifyGesture() { - return calculateFalsingResult(0, 0); + return calculateFalsingResult(0.5, 0); } /** @@ -135,16 +135,16 @@ public abstract class FalsingClassifier { * * See also {@link #classifyGesture()}. */ - Result classifyGesture(double historyPenalty, double historyConfidence) { - return calculateFalsingResult(historyPenalty, historyConfidence); + Result classifyGesture(double historyBelief, double historyConfidence) { + return calculateFalsingResult(historyBelief, historyConfidence); } /** * Calculate a result based on available data. * - * When passed a historyConfidence of 0, the history penalty should be wholly ignored. + * When passed a historyConfidence of 0, the history belief should be wholly ignored. */ - abstract Result calculateFalsingResult(double historyPenalty, double historyConfidence); + abstract Result calculateFalsingResult(double historyBelief, double historyConfidence); /** */ public static void logDebug(String msg) { @@ -164,7 +164,7 @@ public abstract class FalsingClassifier { /** * A Falsing result that encapsulates the boolean result along with confidence and a reason. */ - static class Result { + public static class Result { private final boolean mFalsed; private final double mConfidence; private final String mReason; @@ -193,14 +193,14 @@ public abstract class FalsingClassifier { /** * Construct a "falsed" result indicating that a gesture should be treated as accidental. */ - static Result falsed(double confidence, String reason) { + public static Result falsed(double confidence, String reason) { return new Result(true, confidence, reason); } /** * Construct a "passed" result indicating that a gesture should be allowed. */ - static Result passed(double confidence) { + public static Result passed(double confidence) { return new Result(false, confidence, null); } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java index b0bbab366e93..bb037202d985 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java @@ -120,5 +120,8 @@ public interface FalsingCollector { /** */ void cleanup(); + + /** */ + void updateFalseConfidence(FalsingClassifier.Result result); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java index 12a060439106..939b45a2b4a5 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java @@ -153,4 +153,8 @@ public class FalsingCollectorFake implements FalsingCollector { @Override public void cleanup() { } + + @Override + public void updateFalseConfidence(FalsingClassifier.Result result) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index e08b43b3521f..e090006cca4f 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -29,6 +29,9 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.sensors.ThresholdSensor; +import com.android.systemui.util.time.SystemClock; + +import java.util.Collections; import javax.inject.Inject; @@ -42,8 +45,10 @@ class FalsingCollectorImpl implements FalsingCollector { private final FalsingDataProvider mFalsingDataProvider; private final FalsingManager mFalsingManager; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final HistoryTracker mHistoryTracker; private final ProximitySensor mProximitySensor; private final StatusBarStateController mStatusBarStateController; + private final SystemClock mSystemClock; private int mState; private boolean mShowingAod; @@ -80,13 +85,16 @@ class FalsingCollectorImpl implements FalsingCollector { @Inject FalsingCollectorImpl(FalsingDataProvider falsingDataProvider, FalsingManager falsingManager, - KeyguardUpdateMonitor keyguardUpdateMonitor, - ProximitySensor proximitySensor, StatusBarStateController statusBarStateController) { + KeyguardUpdateMonitor keyguardUpdateMonitor, HistoryTracker historyTracker, + ProximitySensor proximitySensor, StatusBarStateController statusBarStateController, + SystemClock systemClock) { mFalsingDataProvider = falsingDataProvider; mFalsingManager = falsingManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mHistoryTracker = historyTracker; mProximitySensor = proximitySensor; mStatusBarStateController = statusBarStateController; + mSystemClock = systemClock; mProximitySensor.setTag(PROXIMITY_SENSOR_TAG); @@ -282,6 +290,11 @@ class FalsingCollectorImpl implements FalsingCollector { mStatusBarStateController.removeCallback(mStatusBarStateListener); } + @Override + public void updateFalseConfidence(FalsingClassifier.Result result) { + mHistoryTracker.addResults(Collections.singleton(result), mSystemClock.uptimeMillis()); + } + private void updateInteractionType(@Classifier.InteractionType int type) { logDebug("InteractionType: " + type); mFalsingDataProvider.setInteractionType(type); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java index d4d8d06b081b..aac27cb43376 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java @@ -76,7 +76,7 @@ public class FalsingManagerFake implements FalsingManager { } @Override - public boolean isFalseTap(boolean robustCheck) { + public boolean isFalseTap(boolean robustCheck, double falsePenalty) { return robustCheck ? mIsFalseRobustTap : mIsFalseTap; } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java index cbec0576e449..e9bb48c7b1a9 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java @@ -131,8 +131,8 @@ public class FalsingManagerProxy implements FalsingManager, Dumpable { } @Override - public boolean isFalseTap(boolean robustCheck) { - return mInternalFalsingManager.isFalseTap(robustCheck); + public boolean isFalseTap(boolean robustCheck, double falsePenalty) { + return mInternalFalsingManager.isFalseTap(robustCheck, falsePenalty); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java b/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java index 8bd94a122707..be48ec415652 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java @@ -36,12 +36,17 @@ import javax.inject.Inject; */ @SysUISingleton public class HistoryTracker { - private static final double HISTORY_DECAY = 0.8f; + private static final long HISTORY_MAX_AGE_MS = 10000; + // A score is decayed discretely every DECAY_INTERVAL_MS. private static final long DECAY_INTERVAL_MS = 100; - // We expire items once their decay factor is below 0.001. - private static final double MINIMUM_SCORE = 0.001; - private static final long TOTAL_DECAY_TIME_MS = - DECAY_INTERVAL_MS * (long) (Math.log(MINIMUM_SCORE) / Math.log(HISTORY_DECAY)); + // We expire items once their decay factor is below 0.1. + private static final double MINIMUM_SCORE = 0.1; + // This magic number is the factor a score is reduced by every DECAY_INTERVAL_MS. + // Once a score is HISTORY_MAX_AGE_MS ms old, it will be reduced by being multiplied by + // MINIMUM_SCORE. The math below ensures that. + private static final double HISTORY_DECAY = + Math.pow(10, Math.log10(MINIMUM_SCORE) / HISTORY_MAX_AGE_MS * DECAY_INTERVAL_MS); + private final SystemClock mSystemClock; DelayQueue<CombinedResult> mResults = new DelayQueue<>(); @@ -54,29 +59,36 @@ public class HistoryTracker { /** * Returns how much the HistoryClassifier thinks the past events indicate pocket dialing. * - * A result of 0 means that all prior gestures succeeded or there is no data to - * calculate a score with. Use {@link #falseConfidence()} to differentiate between the - * two cases. + * A result close to 0.5 means that prior data is inconclusive (inconsistent, lacking + * confidence, or simply lacking in quantity). + * + * A result close to 0 means that prior gestures indicate a success. * - * A result of 1 means that all prior gestures were very obviously false. The current gesture - * might be valid, but it should have a high-bar to be classified as such. + * A result close to 1 means that prior gestures were very obviously false. + * + * The current gesture might be different than what is reported by this method, but there should + * be a high-bar to be classified differently. * * See also {@link #falseConfidence()}. */ - double falsePenalty() { + double falseBelief() { //noinspection StatementWithEmptyBody while (mResults.poll() != null) { // Empty out the expired results. } if (mResults.isEmpty()) { - return 0; + return 0.5; } long nowMs = mSystemClock.uptimeMillis(); + // Get our Bayes on. return mResults.stream() .map(result -> result.getDecayedScore(nowMs)) - .reduce(0.0, Double::sum) / mResults.size(); + .reduce(0.5, + (prior, measurement) -> + (prior * measurement) + / (prior * measurement + (1 - prior) * (1 - measurement))); } /** @@ -91,7 +103,7 @@ public class HistoryTracker { * A result of 1 means that there are ample, fresh data to act upon that is all consistent * with each other. * - * See als {@link #falsePenalty()}. + * See als {@link #falseBelief()}. */ double falseConfidence() { //noinspection StatementWithEmptyBody @@ -126,6 +138,15 @@ public class HistoryTracker { finalScore /= results.size(); + // Never add a 0 or 1, else Bayes breaks down (a 0 and a 1 together results in NaN). In + // other words, you shouldn't need Bayes if you have 100% confidence one way or another. + // Instead, make the number ever so slightly smaller so that our math never breaks. + if (finalScore == 1) { + finalScore = 0.99999; + } else if (finalScore == 0) { + finalScore = 0.00001; + } + //noinspection StatementWithEmptyBody while (mResults.poll() != null) { // Empty out the expired results. @@ -147,15 +168,17 @@ public class HistoryTracker { private final double mScore; CombinedResult(long uptimeMillis, double score) { - mExpiryMs = uptimeMillis + TOTAL_DECAY_TIME_MS; + mExpiryMs = uptimeMillis + HISTORY_MAX_AGE_MS; mScore = score; } double getDecayedScore(long nowMs) { long remainingTimeMs = mExpiryMs - nowMs; - long decayedTimeMs = TOTAL_DECAY_TIME_MS - remainingTimeMs; + long decayedTimeMs = HISTORY_MAX_AGE_MS - remainingTimeMs; double timeIntervals = (double) decayedTimeMs / DECAY_INTERVAL_MS; - return mScore * Math.pow(HISTORY_DECAY, timeIntervals); + + // Score should decay towards 0.5. + return (mScore - 0.5) * Math.pow(HISTORY_DECAY, timeIntervals) + 0.5; } double getScore() { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java index cd399fe15de1..77d2d4267679 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java @@ -56,7 +56,7 @@ class PointerCountClassifier extends FalsingClassifier { } @Override - Result calculateFalsingResult(double historyPenalty, double historyConfidence) { + Result calculateFalsingResult(double historyBelief, double historyConfidence) { int interactionType = getInteractionType(); int allowedPointerCount = (interactionType == QUICK_SETTINGS || interactionType == NOTIFICATION_DRAG_DOWN) diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java index 9ee85986c53c..6e97857f83da 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java @@ -112,7 +112,7 @@ class ProximityClassifier extends FalsingClassifier { } @Override - Result calculateFalsingResult(double historyPenalty, double historyConfidence) { + Result calculateFalsingResult(double historyBelief, double historyConfidence) { if (getInteractionType() == QUICK_SETTINGS) { return Result.passed(0); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java index f2622ec4e6e9..4dd20ccff98e 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java @@ -39,12 +39,15 @@ public class SingleTapClassifier extends FalsingClassifier { } @Override - Result calculateFalsingResult(double historyPenalty, double historyConfidence) { + Result calculateFalsingResult(double historyBelief, double historyConfidence) { return isTap(getRecentMotionEvents()); } /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */ public Result isTap(List<MotionEvent> motionEvents) { + if (motionEvents.isEmpty()) { + return Result.falsed(0, "no motion events"); + } float downX = motionEvents.get(0).getX(); float downY = motionEvents.get(0).getY(); @@ -59,7 +62,7 @@ public class SingleTapClassifier extends FalsingClassifier { } else if (Math.abs(event.getY() - downY) >= mTouchSlop) { reason = "dY too big for a tap: " + Math.abs(event.getY() - downY) - + "vs " + + " vs " + mTouchSlop; return Result.falsed(0.5, reason); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java index d470d6297170..4e032ea487c9 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java @@ -38,7 +38,7 @@ public class TypeClassifier extends FalsingClassifier { } @Override - Result calculateFalsingResult(double historyPenalty, double historyConfidence) { + Result calculateFalsingResult(double historyBelief, double historyConfidence) { boolean vertical = isVertical(); boolean up = isUp(); boolean right = isRight(); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java index 2bfb2186191b..205825790461 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java @@ -84,7 +84,7 @@ class ZigZagClassifier extends FalsingClassifier { } @Override - Result calculateFalsingResult(double historyPenalty, double historyConfidence) { + Result calculateFalsingResult(double historyBelief, double historyConfidence) { List<MotionEvent> motionEvents = getRecentMotionEvents(); // Rotate horizontal gestures to be horizontal between their first and last point. // Rotate vertical gestures to be vertical between their first and last point. diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt index fbdeb30d3911..ed625de9dce8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt @@ -31,6 +31,7 @@ import com.android.systemui.controls.management.ControlsProviderSelectorActivity import com.android.systemui.controls.management.ControlsRequestDialog import com.android.systemui.controls.ui.ControlActionCoordinator import com.android.systemui.controls.ui.ControlActionCoordinatorImpl +import com.android.systemui.controls.ui.ControlsActivity import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.controls.ui.ControlsUiControllerImpl import com.android.systemui.dagger.SysUISingleton @@ -113,4 +114,9 @@ abstract class ControlsModule { abstract fun provideControlsRequestDialog( activity: ControlsRequestDialog ): Activity + + @Binds + @IntoMap + @ClassKey(ControlsActivity::class) + abstract fun provideControlsActivity(activity: ControlsActivity): Activity } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index fc89783018bc..7dd1d28170b2 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -32,7 +32,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.CustomIconCache import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo -import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.controls.ui.ControlsActivity import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.settings.CurrentUserTracker @@ -112,7 +112,11 @@ class ControlsEditingActivity @Inject constructor( if (backToGlobalActions) { globalActionsComponent.handleShowGlobalActionsMenu() } else { - ControlsDialog(applicationContext, broadcastDispatcher).show(uiController) + val i = Intent().apply { + component = ComponentName(applicationContext, ControlsActivity::class.java) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + } + startActivity(i) } animateExitAndFinish() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 2d647a907b17..309901443393 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -40,7 +40,7 @@ import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.TooltipManager import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo -import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.controls.ui.ControlsActivity import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsComponent @@ -352,7 +352,11 @@ class ControlsFavoritingActivity @Inject constructor( if (backToGlobalActions) { globalActionsComponent.handleShowGlobalActionsMenu() } else { - ControlsDialog(applicationContext, broadcastDispatcher).show(uiController) + val i = Intent().apply { + component = ComponentName(applicationContext, ControlsActivity::class.java) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + } + startActivity(i) } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index d5e41d031eac..fa1c41f01e6c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -32,7 +32,7 @@ import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlsController -import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.controls.ui.ControlsActivity import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -116,7 +116,11 @@ class ControlsProviderSelectorActivity @Inject constructor( if (backToGlobalActions) { globalActionsComponent.handleShowGlobalActionsMenu() } else { - ControlsDialog(applicationContext, broadcastDispatcher).show(uiController) + val i = Intent().apply { + component = ComponentName(applicationContext, ControlsActivity::class.java) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + } + startActivity(i) } animateExitAndFinish() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt index d06568a7caf9..0db15e83fc93 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt @@ -16,6 +16,7 @@ package com.android.systemui.controls.ui +import android.content.Context import android.service.controls.Control /** @@ -24,8 +25,8 @@ import android.service.controls.Control */ interface ControlActionCoordinator { - // Handle actions launched from GlobalActionsDialog or ControlDialog - var startedFromGlobalActions: Boolean + // If launched from an Activity, continue within this stack + var activityContext: Context? /** * Close any dialogs which may have been open diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index 6b300f4e07e4..58a5981845c7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -18,6 +18,7 @@ package com.android.systemui.controls.ui import android.annotation.MainThread import android.app.Dialog +import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -60,7 +61,7 @@ class ControlActionCoordinatorImpl @Inject constructor( private var pendingAction: Action? = null private var actionsInProgress = mutableSetOf<String>() - override var startedFromGlobalActions: Boolean = true + override var activityContext: Context? = null companion object { private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L @@ -83,7 +84,7 @@ class ControlActionCoordinatorImpl @Inject constructor( bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) if (cvh.usePanel()) { - showDialog(cvh, control.getAppIntent().getIntent()) + showDetail(cvh, control.getAppIntent().getIntent()) } else { cvh.action(CommandAction(templateId)) } @@ -109,7 +110,7 @@ class ControlActionCoordinatorImpl @Inject constructor( // Long press snould only be called when there is valid control state, otherwise ignore cvh.cws.control?.let { cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - showDialog(cvh, it.getAppIntent().getIntent()) + showDetail(cvh, it.getAppIntent().getIntent()) } }, false /* blockable */)) } @@ -151,10 +152,16 @@ class ControlActionCoordinatorImpl @Inject constructor( activityStarter.dismissKeyguardThenExecute({ Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action") if (closeDialog) { - if (startedFromGlobalActions) { + activityContext?.let { + val i = Intent().apply { + component = ComponentName(context, ControlsActivity::class.java) + addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra(ControlsUiController.BACK_TO_GLOBAL_ACTIONS, false) + } + it.startActivity(i) + } ?: run { globalActionsComponent.handleShowGlobalActionsMenu() - } else { - ControlsDialog(context, broadcastDispatcher).show(lazyUiController.get()) } } else { action.invoke() @@ -170,9 +177,9 @@ class ControlActionCoordinatorImpl @Inject constructor( bgExecutor.execute { vibrator.vibrate(effect) } } - private fun showDialog(cvh: ControlViewHolder, intent: Intent) { + private fun showDetail(cvh: ControlViewHolder, intent: Intent) { bgExecutor.execute { - val activities: List<ResolveInfo> = cvh.context.packageManager.queryIntentActivities( + val activities: List<ResolveInfo> = context.packageManager.queryIntentActivities( intent, PackageManager.MATCH_DEFAULT_ONLY ) @@ -180,8 +187,8 @@ class ControlActionCoordinatorImpl @Inject constructor( uiExecutor.execute { // make sure the intent is valid before attempting to open the dialog if (activities.isNotEmpty() && taskViewFactory.isPresent) { - taskViewFactory.get().create(cvh.context, uiExecutor, { - dialog = DetailDialog(cvh, it, intent).also { + taskViewFactory.get().create(context, uiExecutor, { + dialog = DetailDialog(activityContext, it, intent, cvh).also { it.setOnDismissListener { _ -> dialog = null } it.show() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt new file mode 100644 index 000000000000..a35b792ac7f1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.view.WindowInsets +import android.view.WindowInsets.Type + +import com.android.systemui.R +import com.android.systemui.controls.management.ControlsAnimations +import com.android.systemui.util.LifecycleActivity +import javax.inject.Inject + +/** + * Displays Device Controls inside an activity + */ +class ControlsActivity @Inject constructor( + private val uiController: ControlsUiController +) : LifecycleActivity() { + + private lateinit var parent: ViewGroup + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.controls_fullscreen) + + requireViewById<ViewGroup>(R.id.control_detail_root).apply { + setOnApplyWindowInsetsListener { + v: View, insets: WindowInsets -> + v.apply { + val l = getPaddingLeft() + val t = getPaddingTop() + val r = getPaddingRight() + setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom) + } + + WindowInsets.CONSUMED + } + } + } + + override fun onStart() { + super.onStart() + + parent = requireViewById<ViewGroup>(R.id.global_actions_controls) + parent.alpha = 0f + uiController.show(parent, { animateExitAndFinish() }, this) + } + + override fun onResume() { + super.onResume() + + ControlsAnimations.enterAnimation(parent).start() + } + + override fun onBackPressed() { + animateExitAndFinish() + } + + override fun onStop() { + super.onStop() + + uiController.hide() + } + + private fun animateExitAndFinish() { + ControlsAnimations.exitAnimation(parent, { finish() }).start() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt deleted file mode 100644 index 537334aeb2f7..000000000000 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.controls.ui - -import android.app.Dialog -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager - -import com.android.systemui.Interpolators -import com.android.systemui.R -import com.android.systemui.broadcast.BroadcastDispatcher -import javax.inject.Inject - -/** - * Show the controls space inside a dialog, as from the lock screen. - */ -class ControlsDialog @Inject constructor( - thisContext: Context, - val broadcastDispatcher: BroadcastDispatcher -) : Dialog(thisContext, R.style.Theme_SystemUI_Dialog_Control_LockScreen) { - - private val receiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val action = intent.getAction() - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { - dismiss() - } - } - } - - init { - window.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) - window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) - - setContentView(R.layout.controls_in_dialog) - - requireViewById<ViewGroup>(R.id.control_detail_root).apply { - setOnClickListener { dismiss() } - (getParent() as View).setOnClickListener { dismiss() } - } - } - - fun show( - controller: ControlsUiController - ): ControlsDialog { - super.show() - - val vg = requireViewById<ViewGroup>(com.android.systemui.R.id.global_actions_controls) - vg.alpha = 0f - controller.show(vg, { dismiss() }, false /* startedFromGlobalActions */) - - vg.animate() - .alpha(1f) - .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) - .setDuration(300) - - val filter = IntentFilter() - filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) - broadcastDispatcher.registerReceiver(receiver, filter) - - return this - } - - override fun dismiss() { - broadcastDispatcher.unregisterReceiver(receiver) - - if (!isShowing()) return - - super.dismiss() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index 20bdf609357e..f86948ee8957 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -17,6 +17,7 @@ package com.android.systemui.controls.ui import android.content.ComponentName +import android.content.Context import android.service.controls.Control import android.service.controls.actions.ControlAction import android.view.ViewGroup @@ -30,7 +31,7 @@ interface ControlsUiController { public const val BACK_TO_GLOBAL_ACTIONS = "back_to_global_actions" } - fun show(parent: ViewGroup, onDismiss: Runnable, startedFromGlobalActions: Boolean) + fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context?) fun hide() /** diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index c94d85aa58c4..0f7f48ff951a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -148,7 +148,7 @@ class ControlsUiControllerImpl @Inject constructor ( override fun show( parent: ViewGroup, onDismiss: Runnable, - startedFromGlobalActions: Boolean + activityContext: Context? ) { Log.d(ControlsUiController.TAG, "show()") this.parent = parent @@ -156,7 +156,7 @@ class ControlsUiControllerImpl @Inject constructor ( hidden = false retainCache = false - controlActionCoordinator.startedFromGlobalActions = startedFromGlobalActions + controlActionCoordinator.activityContext = activityContext allStructures = controlsController.get().getFavorites() selectedStructure = loadPreference(allStructures) @@ -193,7 +193,7 @@ class ControlsUiControllerImpl @Inject constructor ( controlViewsById.clear() controlsById.clear() - show(parent, onDismiss, controlActionCoordinator.startedFromGlobalActions) + show(parent, onDismiss, controlActionCoordinator.activityContext) val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f) showAnim.setInterpolator(DecelerateInterpolator(1.0f)) showAnim.setDuration(FADE_IN_MILLIS) @@ -268,7 +268,7 @@ class ControlsUiControllerImpl @Inject constructor ( intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true) intent.putExtra( ControlsUiController.BACK_TO_GLOBAL_ACTIONS, - controlActionCoordinator.startedFromGlobalActions + controlActionCoordinator.activityContext == null ) onDismiss.run() @@ -392,6 +392,17 @@ class ControlsUiControllerImpl @Inject constructor ( val inflater = LayoutInflater.from(context) inflater.inflate(R.layout.controls_with_favorites, parent, true) + if (controlActionCoordinator.activityContext == null) { + parent.requireViewById<View>(R.id.controls_spacer).apply { + visibility = View.VISIBLE + } + } else { + parent.requireViewById<ImageView>(R.id.controls_close).apply { + setOnClickListener { _: View -> onDismiss.run() } + visibility = View.VISIBLE + } + } + val maxColumns = findMaxColumns() val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup @@ -502,6 +513,8 @@ class ControlsUiControllerImpl @Inject constructor ( override fun hide() { hidden = true + controlActionCoordinator.activityContext = null + closeDialogs(true) controlsController.get().unsubscribe() diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index 020694d89fce..9c788df362de 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -22,8 +22,8 @@ import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.Dialog import android.app.PendingIntent import android.content.ComponentName +import android.content.Context import android.content.Intent -import android.provider.Settings import android.view.View import android.view.ViewGroup import android.view.WindowInsets @@ -35,18 +35,20 @@ import com.android.systemui.R import com.android.wm.shell.TaskView /** - * A dialog that provides an {@link ActivityView}, allowing the application to provide + * A dialog that provides an {@link TaskView}, allowing the application to provide * additional information and actions pertaining to a {@link android.service.controls.Control}. * The activity being launched is specified by {@link android.service.controls.Control#getAppIntent}. */ class DetailDialog( - val cvh: ControlViewHolder, - val activityView: TaskView, - val intent: Intent -) : Dialog(cvh.context, R.style.Theme_SystemUI_Dialog_Control_DetailPanel) { - + val activityContext: Context?, + val taskView: TaskView, + val intent: Intent, + val cvh: ControlViewHolder +) : Dialog( + activityContext ?: cvh.context, + R.style.Theme_SystemUI_Dialog_Control_DetailPanel +) { companion object { - private const val PANEL_TOP_OFFSET = "systemui.controls_panel_top_offset" /* * Indicate to the activity that it is being rendered in a bottomsheet, and they * should optimize the layout for a smaller space. @@ -71,10 +73,19 @@ class DetailDialog( launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - activityView.startActivity( - PendingIntent.getActivity(context, 0, launchIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE), - null, ActivityOptions.makeBasic()) + val options = activityContext?.let { + ActivityOptions.makeCustomAnimation( + it, + 0 /* enterResId */, + 0 /* exitResId */ + ) + } ?: ActivityOptions.makeBasic() + taskView.startActivity( + PendingIntent.getActivity(context, 0, launchIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE), + null, + options + ) } override fun onTaskRemovalStarted(taskId: Int) { @@ -92,7 +103,10 @@ class DetailDialog( } init { - window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) + if (activityContext == null) { + window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) + } + // To pass touches to the task inside TaskView. window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL) window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) @@ -100,7 +114,7 @@ class DetailDialog( setContentView(R.layout.controls_detail_dialog) requireViewById<ViewGroup>(R.id.controls_activity_view).apply { - addView(activityView) + addView(taskView) } requireViewById<ImageView>(R.id.control_detail_close).apply { @@ -120,48 +134,34 @@ class DetailDialog( // consume all insets to achieve slide under effect window.getDecorView().setOnApplyWindowInsetsListener { - _: View, insets: WindowInsets -> - activityView.apply { + v: View, insets: WindowInsets -> + taskView.apply { val l = getPaddingLeft() val t = getPaddingTop() val r = getPaddingRight() setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom) } - WindowInsets.CONSUMED - } - - requireViewById<ViewGroup>(R.id.control_detail_root).apply { - // use flag only temporarily for testing - val resolver = cvh.context.contentResolver - val defaultOffsetInPx = cvh.context.resources - .getDimensionPixelSize(R.dimen.controls_activity_view_top_offset) - val offsetInPx = Settings.Secure.getInt(resolver, PANEL_TOP_OFFSET, defaultOffsetInPx) - - val lp = getLayoutParams() as ViewGroup.MarginLayoutParams - lp.topMargin = offsetInPx - setLayoutParams(lp) + val l = v.getPaddingLeft() + val b = v.getPaddingBottom() + val r = v.getPaddingRight() + v.setPadding(l, insets.getInsets(Type.systemBars()).top, r, b) - setOnClickListener { dismiss() } - (getParent() as View).setOnClickListener { dismiss() } + WindowInsets.CONSUMED } if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(context.getResources())) { val cornerRadius = context.resources .getDimensionPixelSize(R.dimen.controls_activity_view_corner_radius) - activityView.setCornerRadius(cornerRadius.toFloat()) + taskView.setCornerRadius(cornerRadius.toFloat()) } - } - - override fun show() { - activityView.setListener(cvh.uiExecutor, stateCallback) - super.show() + taskView.setListener(cvh.uiExecutor, stateCallback) } override fun dismiss() { if (!isShowing()) return - activityView.release() + taskView.release() super.dismiss() } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 8f79de518419..ed3d5ec33b41 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -34,7 +34,7 @@ import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; -import com.android.wm.shell.transition.RemoteTransitions; +import com.android.wm.shell.transition.ShellTransitions; import java.util.Optional; @@ -87,7 +87,7 @@ public interface SysUIComponent { Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump); @BindsInstance - Builder setTransitions(RemoteTransitions t); + Builder setTransitions(ShellTransitions t); @BindsInstance Builder setStartingSurface(Optional<StartingSurface> s); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 1b77d1c16639..bbd95b4d0c90 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -33,7 +33,7 @@ import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; -import com.android.wm.shell.transition.RemoteTransitions; +import com.android.wm.shell.transition.ShellTransitions; import java.util.Optional; @@ -98,7 +98,7 @@ public interface WMComponent { Optional<TaskViewFactory> getTaskViewFactory(); @WMSingleton - RemoteTransitions getTransitions(); + ShellTransitions getTransitions(); @WMSingleton Optional<StartingSurface> getStartingSurface(); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index c26cc4466c6d..d8ade2bdd21f 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -225,8 +225,8 @@ public class DozeLog implements Dumpable { * Appends wake-display event to the logs. * @param wake if we're waking up or sleeping. */ - public void traceWakeDisplay(boolean wake) { - mLogger.logWakeDisplay(wake); + public void traceWakeDisplay(boolean wake, @Reason int reason) { + mLogger.logWakeDisplay(wake, reason); } /** @@ -380,6 +380,7 @@ public class DozeLog implements Dumpable { case REASON_SENSOR_WAKE_UP: return "wakeup"; case REASON_SENSOR_TAP: return "tap"; case REASON_SENSOR_UDFPS_LONG_PRESS: return "udfps"; + case REASON_SENSOR_QUICK_PICKUP: return "quickPickup"; default: throw new IllegalArgumentException("invalid reason: " + pulseReason); } } @@ -389,7 +390,7 @@ public class DozeLog implements Dumpable { PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP, PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP, PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, REASON_SENSOR_TAP, - REASON_SENSOR_UDFPS_LONG_PRESS}) + REASON_SENSOR_UDFPS_LONG_PRESS, REASON_SENSOR_QUICK_PICKUP}) public @interface Reason {} public static final int PULSE_REASON_NONE = -1; public static final int PULSE_REASON_INTENT = 0; @@ -403,6 +404,7 @@ public class DozeLog implements Dumpable { public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8; public static final int REASON_SENSOR_TAP = 9; public static final int REASON_SENSOR_UDFPS_LONG_PRESS = 10; + public static final int REASON_SENSOR_QUICK_PICKUP = 11; - public static final int TOTAL_REASONS = 11; + public static final int TOTAL_REASONS = 12; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index fddefae6f5b6..9bc74be9b9c3 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -16,6 +16,7 @@ package com.android.systemui.doze +import android.view.Display import com.android.systemui.doze.DozeLog.Reason import com.android.systemui.doze.DozeLog.reasonToString import com.android.systemui.log.LogBuffer @@ -161,17 +162,18 @@ class DozeLogger @Inject constructor( fun logDisplayStateChanged(displayState: Int) { buffer.log(TAG, INFO, { - int1 = displayState + str1 = Display.stateToString(displayState) }, { - "Display state changed to $int1" + "Display state changed to $str1" }) } - fun logWakeDisplay(isAwake: Boolean) { + fun logWakeDisplay(isAwake: Boolean, @Reason reason: Int) { buffer.log(TAG, DEBUG, { bool1 = isAwake + int1 = reason }, { - "Display wakefulness changed, isAwake=$bool1" + "Display wakefulness changed, isAwake=$bool1, reason=${reasonToString(int1)}" }) } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index c542e5b07d9b..52c9f164a16e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -113,6 +113,8 @@ public class DozeSensors { mCallback = callback; mProximitySensor = proximitySensor; + boolean udfpsEnrolled = + authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()); boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT); mSensors = new TriggerSensor[] { new TriggerSensor( @@ -159,7 +161,7 @@ public class DozeSensors { findSensorWithType(config.udfpsLongPressSensorType()), "doze_pulse_on_auth", true /* settingDef */, - authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()), + udfpsEnrolled, DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS, true /* reports touch coordinates */, true /* touchscreen */, @@ -181,6 +183,15 @@ public class DozeSensors { false /* touchscreen */, mConfig.getWakeLockScreenDebounce(), dozeLog), + new TriggerSensor( + findSensorWithType(config.quickPickupSensorType()), + Settings.Secure.DOZE_QUICK_PICKUP_GESTURE, + false /* setting default */, + config.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser()) + && udfpsEnrolled, + DozeLog.REASON_SENSOR_QUICK_PICKUP, + false /* touchCoords */, + false /* touchscreen */, dozeLog), }; setProxListening(false); // Don't immediately start listening when we register. diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index c617f3d751d0..04b46705226f 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -42,10 +42,13 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.Assert; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.settings.SecureSettings; @@ -76,6 +79,7 @@ public class DozeTriggers implements DozeMachine.Part { * Assuming that the screen should start on. */ private static boolean sWakeDisplaySensorState = true; + private Runnable mQuickPickupDozeCancellable; private static final int PROXIMITY_TIMEOUT_DELAY_MS = 500; @@ -96,6 +100,8 @@ public class DozeTriggers implements DozeMachine.Part { private final ProximitySensor.ProximityCheck mProxCheck; private final BroadcastDispatcher mBroadcastDispatcher; private final AuthController mAuthController; + private final DelayableExecutor mMainExecutor; + private final DelayableExecutor mBgExecutor; private long mNotificationPulseTime; private boolean mPulsePending; @@ -135,7 +141,10 @@ public class DozeTriggers implements DozeMachine.Part { DOZING_UPDATE_SENSOR_TAP(441), @UiEvent(doc = "Dozing updated because on display auth was triggered from AOD.") - DOZING_UPDATE_AUTH_TRIGGERED(657); + DOZING_UPDATE_AUTH_TRIGGERED(657), + + @UiEvent(doc = "Dozing updated because quick pickup sensor woke up.") + DOZING_UPDATE_QUICK_PICKUP(708); private final int mId; @@ -160,6 +169,7 @@ public class DozeTriggers implements DozeMachine.Part { case 8: return DOZING_UPDATE_SENSOR_WAKE_LOCKSCREEN; case 9: return DOZING_UPDATE_SENSOR_TAP; case 10: return DOZING_UPDATE_AUTH_TRIGGERED; + case 11: return DOZING_UPDATE_QUICK_PICKUP; default: return null; } } @@ -172,7 +182,8 @@ public class DozeTriggers implements DozeMachine.Part { WakeLock wakeLock, DockManager dockManager, ProximitySensor proximitySensor, ProximitySensor.ProximityCheck proxCheck, DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher, - SecureSettings secureSettings, AuthController authController) { + SecureSettings secureSettings, AuthController authController, + @Main DelayableExecutor mainExecutor, @Background DelayableExecutor bgExecutor) { mContext = context; mDozeHost = dozeHost; mConfig = config; @@ -189,6 +200,8 @@ public class DozeTriggers implements DozeMachine.Part { mDozeLog = dozeLog; mBroadcastDispatcher = broadcastDispatcher; mAuthController = authController; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; } @Override @@ -262,18 +275,22 @@ public class DozeTriggers implements DozeMachine.Part { boolean isTap = pulseReason == DozeLog.REASON_SENSOR_TAP; boolean isPickup = pulseReason == DozeLog.REASON_SENSOR_PICKUP; boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS; - boolean isWakeDisplay = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP; - boolean isWakeLockScreen = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN; + boolean isWakeOnPresence = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP; + boolean isWakeOnReach = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN; boolean isUdfpsLongPress = pulseReason == DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS; - boolean wakeEvent = rawValues != null && rawValues.length > 0 && rawValues[0] != 0; - - if (isWakeDisplay) { - onWakeScreen(wakeEvent, mMachine.isExecutingTransition() ? null : mMachine.getState()); + boolean isQuickPickup = pulseReason == DozeLog.REASON_SENSOR_QUICK_PICKUP; + boolean isWakeDisplayEvent = isQuickPickup || ((isWakeOnPresence || isWakeOnReach) + && rawValues != null && rawValues.length > 0 && rawValues[0] != 0); + + if (isWakeOnPresence || isQuickPickup) { + onWakeScreen(isQuickPickup || isWakeDisplayEvent, + mMachine.isExecutingTransition() ? null : mMachine.getState(), + pulseReason); } else if (isLongPress) { requestPulse(pulseReason, true /* alreadyPerformedProxCheck */, null /* onPulseSuppressedListener */); - } else if (isWakeLockScreen) { - if (wakeEvent) { + } else if (isWakeOnReach) { + if (isWakeDisplayEvent) { requestPulse(pulseReason, true /* alreadyPerformedProxCheck */, null /* onPulseSuppressedListener */); } @@ -370,13 +387,17 @@ public class DozeTriggers implements DozeMachine.Part { * @param state The current state, or null if the state could not be determined due to enqueued * transitions. */ - private void onWakeScreen(boolean wake, @Nullable DozeMachine.State state) { - mDozeLog.traceWakeDisplay(wake); - sWakeDisplaySensorState = wake; + private void onWakeScreen(boolean wake, @Nullable DozeMachine.State state, int reason) { + mDozeLog.traceWakeDisplay(wake, reason); + final boolean isWakeOnPresence = reason == DozeLog.REASON_SENSOR_WAKE_UP; + final boolean isQuickPickup = reason == DozeLog.REASON_SENSOR_QUICK_PICKUP; + if (isWakeOnPresence) { + sWakeDisplaySensorState = wake; + } if (wake) { proximityCheckThenCall((result) -> { - if (result != null && result) { + if (result != null && result) { // In pocket, drop event. return; } @@ -385,26 +406,51 @@ public class DozeTriggers implements DozeMachine.Part { // Logs AOD open due to sensor wake up. mMetricsLogger.write(new LogMaker(MetricsEvent.DOZING) .setType(MetricsEvent.TYPE_OPEN) - .setSubtype(DozeLog.REASON_SENSOR_WAKE_UP)); + .setSubtype(reason)); + + if (isQuickPickup) { + // schedule runnable to go back to DOZE + onQuickPickup(); + } + } else if (state == DozeMachine.State.DOZE_AOD && isQuickPickup) { + // elongate time in DOZE_AOD, schedule new runnable to go back to DOZE + onQuickPickup(); } - }, true /* alreadyPerformedProxCheck */, DozeLog.REASON_SENSOR_WAKE_UP); + }, isQuickPickup /* alreadyPerformedProxCheck */, reason); } else { boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED); boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING); + boolean pulse = (state == DozeMachine.State.DOZE_REQUEST_PULSE) + || (state == DozeMachine.State.DOZE_PULSING) + || (state == DozeMachine.State.DOZE_PULSING_BRIGHT); + boolean docked = (state == DozeMachine.State.DOZE_AOD_DOCKED); if (!pausing && !paused) { + if (isQuickPickup && (pulse || docked)) { + return; + } mMachine.requestState(DozeMachine.State.DOZE); // Logs AOD close due to sensor wake up. mMetricsLogger.write(new LogMaker(MetricsEvent.DOZING) .setType(MetricsEvent.TYPE_CLOSE) - .setSubtype(DozeLog.REASON_SENSOR_WAKE_UP)); + .setSubtype(reason)); } } } + private void onQuickPickup() { + cancelQuickPickupDelayableDoze(); + mQuickPickupDozeCancellable = mMainExecutor.executeDelayed(() -> { + onWakeScreen(false, + mMachine.isExecutingTransition() ? null : mMachine.getState(), + DozeLog.REASON_SENSOR_QUICK_PICKUP); + }, mDozeParameters.getQuickPickupAodDuration()); + } + @Override public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { switch (newState) { case INITIALIZED: + sWakeDisplaySensorState = true; mBroadcastReceiver.register(mBroadcastDispatcher); mDozeHost.addCallback(mHostCallback); mDockManager.addListener(mDockEventListener); @@ -417,7 +463,7 @@ public class DozeTriggers implements DozeMachine.Part { mWantSensors = true; mWantTouchScreenSensors = true; if (newState == DozeMachine.State.DOZE_AOD && !sWakeDisplaySensorState) { - onWakeScreen(false, newState); + onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP); } break; case DOZE_AOD_PAUSED: @@ -437,6 +483,7 @@ public class DozeTriggers implements DozeMachine.Part { mDozeSensors.requestTemporaryDisable(); break; case FINISH: + cancelQuickPickupDelayableDoze(); mBroadcastReceiver.unregister(mBroadcastDispatcher); mDozeHost.removeCallback(mHostCallback); mDockManager.removeListener(mDockEventListener); @@ -460,6 +507,17 @@ public class DozeTriggers implements DozeMachine.Part { mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors); } + /** + * Cancels last scheduled Runnable that transitions to STATE_DOZE (blank screen) after + * going into STATE_AOD (AOD screen) from the quick pickup gesture. + */ + private void cancelQuickPickupDelayableDoze() { + if (mQuickPickupDozeCancellable != null) { + mQuickPickupDozeCancellable.run(); + mQuickPickupDozeCancellable = null; + } + } + private void checkTriggersAtInit() { if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR || mDozeHost.isBlockingDoze() diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 1f67276bfbae..461a7303c184 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -2220,7 +2220,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private void showControls(ControlsUiController controller) { mControlsUiController = controller; mControlsUiController.show(mControlsView, this::dismissForControlsActivity, - true /* startedFromGlobalActions */); + null /* activityContext */); } private boolean isWalletViewAvailable() { @@ -2449,7 +2449,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, }); if (mControlsUiController != null) { mControlsUiController.show(mControlsView, this::dismissForControlsActivity, - true /* startedFromGlobalActions */); + null /* activityContext */); } mBackgroundDrawable.setAlpha(0); @@ -2625,7 +2625,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mGlobalActionsLayout.updateList(); if (mControlsUiController != null) { mControlsUiController.show(mControlsView, this::dismissForControlsActivity, - true /* startedFromGlobalActions */); + null /* activityContext */); } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 4491cc12a3cb..0bfd065c4b67 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -212,6 +212,14 @@ public class NavigationBarController implements Callbacks, createNavigationBar(display, null /* savedState */, null /* result */); } + @Override + public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) { + final NavigationBarView navigationBarView = getNavigationBarView(displayId); + if (navigationBarView != null) { + navigationBarView.setNavigationBarLumaSamplingEnabled(enable); + } + } + /** * Recreates the navigation bar for the given display. */ diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 35d5ca949f85..148c6652e851 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -1397,4 +1397,12 @@ public class NavigationBarView extends FrameLayout implements private final Consumer<Rect> mPipListener = bounds -> post(() -> { mEdgeBackGestureHandler.setPipStashExclusionBounds(bounds); }); + + void setNavigationBarLumaSamplingEnabled(boolean enable) { + if (enable) { + mRegionSamplingHelper.start(mSamplingBounds); + } else { + mRegionSamplingHelper.stop(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index a69ec278be91..378e49deb699 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -35,6 +35,7 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.people.widget.PeopleSpaceWidgetManager; +import com.android.systemui.people.widget.PeopleTileKey; import com.android.systemui.statusbar.notification.NotificationEntryManager; import java.util.List; @@ -117,7 +118,7 @@ public class PeopleSpaceActivity extends Activity { String pkg = tile.getPackageName(); String status = PeopleSpaceUtils.getLastInteractionString(mContext, - tile.getLastInteractionTimestamp(), true); + tile.getLastInteractionTimestamp()); tileView.setStatus(status); tileView.setName(tile.getUserName().toString()); @@ -138,7 +139,9 @@ public class PeopleSpaceActivity extends Activity { + mAppWidgetId); } } - mPeopleSpaceWidgetManager.addNewWidget(tile, mAppWidgetId); + PeopleTileKey key = new PeopleTileKey( + tile.getId(), tile.getUserHandle().getIdentifier(), tile.getPackageName()); + mPeopleSpaceWidgetManager.addNewWidget(mAppWidgetId, key); finishActivity(); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 502c95c47d03..aa45178b6439 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -52,9 +52,7 @@ import android.icu.text.MeasureFormat; import android.icu.util.Measure; import android.icu.util.MeasureUnit; import android.net.Uri; -import android.os.Bundle; import android.os.Parcelable; -import android.os.ServiceManager; import android.provider.ContactsContract; import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; @@ -71,10 +69,11 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ArrayUtils; import com.android.settingslib.utils.ThreadUtils; -import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.people.widget.AppWidgetOptionsHelper; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; +import com.android.systemui.people.widget.PeopleTileKey; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -106,7 +105,6 @@ public class PeopleSpaceUtils { private static final int DAYS_IN_A_WEEK = 7; private static final int MIN_HOUR = 1; private static final int ONE_DAY = 1; - public static final String OPTIONS_PEOPLE_SPACE_TILE = "options_people_space_tile"; public static final String PACKAGE_NAME = "package_name"; public static final String USER_ID = "user_id"; public static final String SHORTCUT_ID = "shortcut_id"; @@ -115,6 +113,9 @@ public class PeopleSpaceUtils { public static final int INVALID_WIDGET_ID = -1; public static final int INVALID_USER_ID = -1; + public static final PeopleTileKey EMPTY_KEY = + new PeopleTileKey(EMPTY_STRING, INVALID_USER_ID, EMPTY_STRING); + private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+"); private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+"); private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+"); @@ -200,67 +201,76 @@ public class PeopleSpaceUtils { AppWidgetManager appWidgetManager, IPeopleManager peopleManager) { Map<Integer, PeopleSpaceTile> widgetIdToTile = new HashMap<>(); for (int appWidgetId : appWidgetIds) { - PeopleSpaceTile tile = getPeopleSpaceTile(peopleManager, appWidgetManager, context, - appWidgetId); + PeopleSpaceTile tile = getPeopleSpaceTile( + context, appWidgetId, appWidgetManager, peopleManager); if (tile == null) { if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID"); //TODO: Delete app widget id when crash is fixed (b/172932636) continue; } - - if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName()); - RemoteViews views = createRemoteViews(context, tile, appWidgetId); - - // Tell the AppWidgetManager to perform an update on the current app widget. - appWidgetManager.updateAppWidget(appWidgetId, views); - + updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, tile); widgetIdToTile.put(appWidgetId, tile); } getBirthdaysOnBackgroundThread(context, appWidgetManager, widgetIdToTile, appWidgetIds); } + /** + * Returns a {@link PeopleSpaceTile} based on the {@code appWidgetId}. If the PeopleSpaceTile + * isn't cached, store it in AppWidgetOptions. + */ @Nullable - public static PeopleSpaceTile getPeopleSpaceTile(IPeopleManager peopleManager, - AppWidgetManager appWidgetManager, - Context context, int appWidgetId) { - try { - // Migrate storage for existing users. - SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId), - Context.MODE_PRIVATE); - String pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING); - int userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); - String shortcutId = widgetSp.getString(SHORTCUT_ID, EMPTY_STRING); - if (!validKey(shortcutId, pkg, userId)) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - shortcutId = sp.getString(String.valueOf(appWidgetId), null); - if (shortcutId == null) { - Log.e(TAG, "Cannot restore widget"); - return null; - } - migrateExistingUsersToNewStorage(context, shortcutId, appWidgetId); - pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING); - userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); - } + public static PeopleSpaceTile getPeopleSpaceTile(Context context, int appWidgetId, + AppWidgetManager appWidgetManager, IPeopleManager peopleManager) { + // First, check if tile is cached in AppWidgetOptions. + PeopleSpaceTile tile = AppWidgetOptionsHelper.getPeopleTile(appWidgetManager, appWidgetId); + if (tile != null) { + if (DEBUG) Log.d(TAG, "People Tile is cached for widget: " + appWidgetId); + return tile; + } - // Check if tile is cached. - Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); - PeopleSpaceTile tile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); - if (tile != null) { - return tile; - } + // If not, we get the PeopleTileKey from SharedPreferences, retrieve the Conversation from + // persisted storage, and cache it in AppWidgetOptions. + SharedPreferences widgetSp = context.getSharedPreferences( + String.valueOf(appWidgetId), + Context.MODE_PRIVATE); + PeopleTileKey sharedPreferencesKey = new PeopleTileKey( + widgetSp.getString(SHORTCUT_ID, EMPTY_STRING), + widgetSp.getInt(USER_ID, INVALID_USER_ID), + widgetSp.getString(PACKAGE_NAME, EMPTY_STRING)); - // If tile is null, we need to retrieve from persisted storage. - if (DEBUG) { - Log.d(TAG, - "Retrieving from storage after reboots: " + shortcutId + " user: " + userId - + " pkg: " + pkg); - } + if (!sharedPreferencesKey.isValid()) { + Log.e(TAG, "Cannot find shortcut info for widgetId: " + appWidgetId); + return null; + } + + if (DEBUG) Log.d(TAG, "PeopleTile key is present in sharedPreferences: " + appWidgetId); + // If tile is null, we need to retrieve from persisted storage. + return getPeopleTileFromPersistentStorage(context, sharedPreferencesKey, peopleManager); + } + + /** + * Returns a {@link PeopleSpaceTile} based on {@link ConversationChannel} returned by + * {@link IPeopleManager}. + */ + public static PeopleSpaceTile getPeopleTileFromPersistentStorage(Context context, + PeopleTileKey peopleTileKey, IPeopleManager peopleManager) { + try { + if (DEBUG) Log.d(TAG, "Retrieving Tile from storage: " + peopleTileKey.toString()); LauncherApps launcherApps = context.getSystemService(LauncherApps.class); - ConversationChannel channel = peopleManager.getConversation(pkg, userId, shortcutId); + if (launcherApps == null) { + Log.d(TAG, "LauncherApps is null"); + return null; + } + + ConversationChannel channel = peopleManager.getConversation( + peopleTileKey.getPackageName(), + peopleTileKey.getUserId(), + peopleTileKey.getShortcutId()); if (channel == null) { Log.d(TAG, "Could not retrieve conversation from storage"); return null; } + return new PeopleSpaceTile.Builder(channel, launcherApps).build(); } catch (Exception e) { Log.e(TAG, "Failed to retrieve conversation for tile: " + e); @@ -269,79 +279,46 @@ public class PeopleSpaceUtils { } /** Returns stored widgets for the conversation specified. */ - public static Set<String> getStoredWidgetIds(SharedPreferences sp, String shortcutId, - String packageName, int userId) { - if (shortcutId == null || packageName == null) { + public static Set<String> getStoredWidgetIds(SharedPreferences sp, PeopleTileKey key) { + if (!key.isValid()) { return new HashSet<>(); } - String key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); - return new HashSet<>(sp.getStringSet(key, new HashSet<>())); - } - - - /** Best-effort attempts to migrate existing users to the new storage format. */ - // TODO: Remove after sufficient time. Temporary migration storage for existing users. - private static void migrateExistingUsersToNewStorage(Context context, String shortcutId, - int appWidgetId) { - try { - List<PeopleSpaceTile> tiles = - PeopleSpaceUtils.getTiles(context, INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)), - IPeopleManager.Stub.asInterface( - ServiceManager.getService(Context.PEOPLE_SERVICE)), - context.getSystemService(LauncherApps.class), - Dependency.get(NotificationEntryManager.class)); - Optional<PeopleSpaceTile> entry = tiles.stream().filter( - e -> e.getId().equals(shortcutId)).findFirst(); - if (entry.isPresent()) { - if (DEBUG) Log.d(TAG, "Migrate storage for " + entry.get().getUserName()); - setStorageForTile(context, entry.get(), appWidgetId); - } else { - Log.e(TAG, "Could not migrate user. Delete old storage"); - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - SharedPreferences.Editor editor = sp.edit(); - editor.remove(String.valueOf(appWidgetId)); - editor.apply(); - } - } catch (Exception e) { - Log.e(TAG, "Could not query conversations"); - } + return new HashSet<>(sp.getStringSet(key.toString(), new HashSet<>())); } /** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */ - public static void setStorageForTile(Context context, PeopleSpaceTile tile, int appWidgetId) { + public static void setSharedPreferencesStorageForTile(Context context, PeopleTileKey key, + int appWidgetId) { // Write relevant persisted storage. SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId), Context.MODE_PRIVATE); SharedPreferences.Editor widgetEditor = widgetSp.edit(); - widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, tile.getPackageName()); - widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, tile.getId()); - int userId = getUserId(tile); - widgetEditor.putInt(PeopleSpaceUtils.USER_ID, userId); + widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, key.getPackageName()); + widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, key.getShortcutId()); + widgetEditor.putInt(PeopleSpaceUtils.USER_ID, key.getUserId()); widgetEditor.apply(); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sp.edit(); - editor.putString(String.valueOf(appWidgetId), tile.getId()); - String key = PeopleSpaceUtils.getKey(tile.getId(), tile.getPackageName(), userId); + editor.putString(String.valueOf(appWidgetId), key.getShortcutId()); + // Don't overwrite existing widgets with the same key. - Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); + Set<String> storedWidgetIds = new HashSet<>( + sp.getStringSet(key.toString(), new HashSet<>())); storedWidgetIds.add(String.valueOf(appWidgetId)); - editor.putStringSet(key, storedWidgetIds); + editor.putStringSet(key.toString(), storedWidgetIds); editor.apply(); - - // Write cached storage. - updateAppWidgetOptionsAndView(AppWidgetManager.getInstance(context), context, appWidgetId, - tile); } /** Removes stored data when tile is deleted. */ - public static void removeStorageForTile(Context context, String key, int widgetId) { + public static void removeSharedPreferencesStorageForTile(Context context, PeopleTileKey key, + int widgetId) { // Delete widgetId mapping to key. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sp.edit(); - Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); + Set<String> storedWidgetIds = new HashSet<>( + sp.getStringSet(key.toString(), new HashSet<>())); storedWidgetIds.remove(String.valueOf(widgetId)); - editor.putStringSet(key, storedWidgetIds); + editor.putStringSet(key.toString(), storedWidgetIds); editor.remove(String.valueOf(widgetId)); editor.apply(); @@ -361,12 +338,12 @@ public class PeopleSpaceUtils { Log.w(TAG, "NotificationEntryManager is null"); return tiles; } - Map<String, NotificationEntry> visibleNotifications = notificationEntryManager + Map<PeopleTileKey, NotificationEntry> visibleNotifications = notificationEntryManager .getVisibleNotifications() .stream() .filter(entry -> entry.getRanking() != null && entry.getRanking().getConversationShortcutInfo() != null) - .collect(Collectors.toMap(PeopleSpaceUtils::getKey, e -> e)); + .collect(Collectors.toMap(PeopleTileKey::new, e -> e)); if (DEBUG) { Log.d(TAG, "Number of visible notifications:" + visibleNotifications.size()); } @@ -378,16 +355,15 @@ public class PeopleSpaceUtils { } static PeopleSpaceTile augmentTileFromVisibleNotifications(Context context, - PeopleSpaceTile tile, Map<String, NotificationEntry> visibleNotifications) { - String shortcutId = tile.getId(); - String packageName = tile.getPackageName(); - int userId = getUserId(tile); - String key = getKey(shortcutId, packageName, userId); + PeopleSpaceTile tile, Map<PeopleTileKey, NotificationEntry> visibleNotifications) { + PeopleTileKey key = new PeopleTileKey( + tile.getId(), getUserId(tile), tile.getPackageName()); + if (!visibleNotifications.containsKey(key)) { - if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key); + if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key.toString()); return tile; } - if (DEBUG) Log.d(TAG, "Augmenting tile from visible notifications, key:" + key); + if (DEBUG) Log.d(TAG, "Augmenting tile from visible notifications, key:" + key.toString()); return augmentTileFromNotification(context, tile, visibleNotifications.get(key).getSbn()); } @@ -423,17 +399,6 @@ public class PeopleSpaceUtils { .build(); } - private static void updateAppWidgetOptions(AppWidgetManager appWidgetManager, int appWidgetId, - PeopleSpaceTile tile) { - if (tile == null) { - if (DEBUG) Log.d(TAG, "Requested to store null tile"); - return; - } - Bundle newOptions = new Bundle(); - newOptions.putParcelable(OPTIONS_PEOPLE_SPACE_TILE, tile); - appWidgetManager.updateAppWidgetOptions(appWidgetId, newOptions); - } - /** Creates a {@link RemoteViews} for {@code tile}. */ public static RemoteViews createRemoteViews(Context context, PeopleSpaceTile tile, int appWidgetId) { @@ -653,7 +618,7 @@ public class PeopleSpaceUtils { } // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile. views.setTextViewText(R.id.subtext, PeopleSpaceUtils.getLastInteractionString( - context, tile.getLastInteractionTimestamp(), false)); + context, tile.getLastInteractionTimestamp())); return views; } @@ -662,7 +627,7 @@ public class PeopleSpaceUtils { RemoteViews views = new RemoteViews( context.getPackageName(), R.layout.people_space_large_avatar_tile); String status = PeopleSpaceUtils.getLastInteractionString( - context, tile.getLastInteractionTimestamp(), true); + context, tile.getLastInteractionTimestamp()); views.setTextViewText(R.id.last_interaction, status); return views; } @@ -808,8 +773,7 @@ public class PeopleSpaceUtils { } /** Returns a readable status describing the {@code lastInteraction}. */ - public static String getLastInteractionString(Context context, long lastInteraction, - boolean includeLastChatted) { + public static String getLastInteractionString(Context context, long lastInteraction) { if (lastInteraction == 0L) { Log.e(TAG, "Could not get valid last interaction"); return context.getString(R.string.basic_status); @@ -818,41 +782,20 @@ public class PeopleSpaceUtils { Duration durationSinceLastInteraction = Duration.ofMillis(now - lastInteraction); MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE); - MeasureFormat shortFormatter = MeasureFormat.getInstance(Locale.getDefault(), - MeasureFormat.FormatWidth.SHORT); if (durationSinceLastInteraction.toHours() < MIN_HOUR) { - if (includeLastChatted) { - return context.getString(R.string.last_interaction_status_less_than, - formatter.formatMeasures(new Measure(MIN_HOUR, MeasureUnit.HOUR))); - } - return context.getString(R.string.timestamp, shortFormatter.formatMeasures( + return context.getString(R.string.timestamp, formatter.formatMeasures( new Measure(durationSinceLastInteraction.toMinutes(), MeasureUnit.MINUTE))); } else if (durationSinceLastInteraction.toDays() < ONE_DAY) { - if (includeLastChatted) { - return context.getString(R.string.last_interaction_status, - formatter.formatMeasures( - new Measure(durationSinceLastInteraction.toHours(), - MeasureUnit.HOUR))); - } - return context.getString(R.string.timestamp, shortFormatter.formatMeasures( + return context.getString(R.string.timestamp, formatter.formatMeasures( new Measure(durationSinceLastInteraction.toHours(), MeasureUnit.HOUR))); } else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) { - if (includeLastChatted) { - return context.getString(R.string.last_interaction_status, - formatter.formatMeasures( - new Measure(durationSinceLastInteraction.toDays(), - MeasureUnit.DAY))); - } - return context.getString(R.string.timestamp, shortFormatter.formatMeasures( + return context.getString(R.string.timestamp, formatter.formatMeasures( new Measure(durationSinceLastInteraction.toHours(), MeasureUnit.DAY))); } else { return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK - ? (includeLastChatted ? R.string.last_interaction_status : - R.string.timestamp) : - (includeLastChatted ? R.string.last_interaction_status_over - : R.string.over_timestamp), + ? R.string.timestamp : R.string.over_timestamp, formatter.formatMeasures( new Measure(durationSinceLastInteraction.toDays() / DAYS_IN_A_WEEK, MeasureUnit.WEEK))); @@ -957,11 +900,15 @@ public class PeopleSpaceUtils { removeBirthdayStatusIfPresent(appWidgetManager, context, storedTile, appWidgetId); } - /** Update app widget options and the current view. */ + /** Updates tile in app widget options and the current view. */ public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager, Context context, int appWidgetId, PeopleSpaceTile tile) { - updateAppWidgetOptions(appWidgetManager, appWidgetId, tile); + AppWidgetOptionsHelper.setPeopleTile(appWidgetManager, appWidgetId, tile); + + if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName()); RemoteViews views = createRemoteViews(context, tile, appWidgetId); + + // Tell the AppWidgetManager to perform an update on the current app widget. appWidgetManager.updateAppWidget(appWidgetId, views); } @@ -1006,44 +953,6 @@ public class PeopleSpaceUtils { return lookupKeysWithBirthdaysToday; } - static String getKey(NotificationEntry entry) { - if (entry.getRanking() == null || entry.getRanking().getConversationShortcutInfo() == null - || entry.getSbn() == null || entry.getSbn().getUser() == null) { - return null; - } - return getKey(entry.getRanking().getConversationShortcutInfo().getId(), - entry.getSbn().getPackageName(), - entry.getSbn().getUser().getIdentifier()); - } - - /** - * Returns the uniquely identifying key for the conversation. - * - * <p>{@code userId} will always be a number, so we put user ID as the - * delimiter between the app-provided strings of shortcut ID and package name. - * - * <p>There aren't restrictions on shortcut ID characters, but there are restrictions requiring - * a {@code packageName} to always start with a letter. This restriction means we are - * guaranteed to avoid cases like "a/b/0/0/package.name" having two potential keys, as the first - * case is impossible given the package name restrictions: - * <ul> - * <li>"a/b" + "/" + 0 + "/" + "0/packageName"</li> - * <li>"a/b/0" + "/" + 0 + "/" + "packageName"</li> - * </ul> - */ - @Nullable - public static String getKey(String shortcutId, String packageName, int userId) { - if (!validKey(shortcutId, packageName, userId)) { - return null; - } - return shortcutId + "/" + userId + "/" + packageName; - } - - /** Returns whether the key is valid. */ - public static boolean validKey(String shortcutId, String packageName, int userId) { - return !TextUtils.isEmpty(shortcutId) && !TextUtils.isEmpty(packageName) && userId >= 0; - } - /** Returns the userId associated with a {@link PeopleSpaceTile} */ public static int getUserId(PeopleSpaceTile tile) { return tile.getUserHandle().getIdentifier(); diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java b/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java new file mode 100644 index 000000000000..df08ee4a42bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.people.widget; + +import static com.android.systemui.people.PeopleSpaceUtils.DEBUG; +import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_KEY; +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 android.app.people.PeopleSpaceTile; +import android.appwidget.AppWidgetManager; +import android.os.Bundle; +import android.util.Log; + +/** Helper class encapsulating AppWidgetOptions for People Tile. */ +public class AppWidgetOptionsHelper { + private static final String TAG = "AppWidgetOptionsHelper"; + + /** Key to store {@link PeopleSpaceTile} in AppWidgetOptions Bundle. */ + public static final String OPTIONS_PEOPLE_TILE = "options_people_tile"; + + /** Sets {@link PeopleSpaceTile} in AppWidgetOptions. */ + public static void setPeopleTile(AppWidgetManager appWidgetManager, int appWidgetId, + PeopleSpaceTile tile) { + if (tile == null) { + if (DEBUG) Log.d(TAG, "Requested to store null tile"); + return; + } + Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); + options.putParcelable(OPTIONS_PEOPLE_TILE, tile); + appWidgetManager.updateAppWidgetOptions(appWidgetId, options); + } + + /** Gets {@link PeopleSpaceTile} from AppWidgetOptions. */ + public static PeopleSpaceTile getPeopleTile(AppWidgetManager appWidgetManager, + int appWidgetId) { + Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); + return options != null ? options.getParcelable(OPTIONS_PEOPLE_TILE) : null; + } + + /** Sets {@link PeopleTileKey} in AppWidgetOptions. */ + public static void setPeopleTileKey(AppWidgetManager appWidgetManager, int appWidgetId, + PeopleTileKey key) { + Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); + options.putString(SHORTCUT_ID, key.getShortcutId()); + options.putInt(USER_ID, key.getUserId()); + options.putString(PACKAGE_NAME, key.getPackageName()); + appWidgetManager.updateAppWidgetOptions(appWidgetId, options); + } + + /** Gets {@link PeopleTileKey} from AppWidgetOptions. */ + public static PeopleTileKey getPeopleTileKey(AppWidgetManager appWidgetManager, + int appWidgetId) { + Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); + if (options == null) { + return EMPTY_KEY; + } + return getPeopleTileKeyFromBundle(options); + } + + /** Gets {@link PeopleTileKey} from Bundle {@code options}. */ + public static PeopleTileKey getPeopleTileKeyFromBundle(Bundle options) { + String pkg = options.getString(PACKAGE_NAME, EMPTY_STRING); + int userId = options.getInt(USER_ID, INVALID_USER_ID); + String shortcutId = options.getString(SHORTCUT_ID, EMPTY_STRING); + return new PeopleTileKey(shortcutId, userId, pkg); + } + + /** Removes {@link PeopleTileKey} from AppWidgetOptions. */ + public static void removePeopleTileKey(AppWidgetManager appWidgetManager, + int appWidgetId) { + setPeopleTileKey(appWidgetManager, appWidgetId, EMPTY_KEY); + } +} 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 22ee9e89d0a0..7da9a80ca287 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java @@ -16,6 +16,7 @@ package com.android.systemui.people.widget; +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; @@ -38,6 +39,7 @@ import android.content.SharedPreferences; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.net.Uri; +import android.os.Bundle; import android.os.ServiceManager; import android.os.UserHandle; import android.preference.PreferenceManager; @@ -78,8 +80,8 @@ public class PeopleSpaceWidgetManager { private PeopleManager mPeopleManager; public UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); @GuardedBy("mLock") - public static Map<String, PeopleSpaceWidgetProvider.TileConversationListener> mListeners = - new HashMap<>(); + public static Map<PeopleTileKey, PeopleSpaceWidgetProvider.TileConversationListener> + mListeners = new HashMap<>(); @Inject public PeopleSpaceWidgetManager(Context context) { @@ -122,8 +124,8 @@ public class PeopleSpaceWidgetManager { Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; if (showSingleConversation) { synchronized (mLock) { - PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, - mAppWidgetManager, mIPeopleManager); + PeopleSpaceUtils.updateSingleConversationWidgets( + mContext, widgetIds, mAppWidgetManager, mIPeopleManager); } } } catch (Exception e) { @@ -157,9 +159,11 @@ public class PeopleSpaceWidgetManager { return; } synchronized (mLock) { - Set<String> storedWidgetIds = getStoredWidgetIds(mSharedPrefs, sbnShortcutId, - sbn.getPackageName(), - UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier()); + 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()); @@ -177,9 +181,9 @@ public class PeopleSpaceWidgetManager { public void updateWidgetsWithConversationChanged(ConversationChannel conversation) { ShortcutInfo info = conversation.getShortcutInfo(); synchronized (mLock) { - Set<String> storedWidgetIds = getStoredWidgetIds(mSharedPrefs, info.getId(), - info.getPackage(), - info.getUserId()); + PeopleTileKey key = new PeopleTileKey( + info.getId(), info.getUserId(), info.getPackage()); + Set<String> storedWidgetIds = getStoredWidgetIds(mSharedPrefs, key); for (String widgetIdString : storedWidgetIds) { if (DEBUG) { Log.d(TAG, @@ -197,9 +201,8 @@ public class PeopleSpaceWidgetManager { */ private void updateStorageAndViewWithConversationData(ConversationChannel conversation, int appWidgetId) { - PeopleSpaceTile storedTile = getPeopleSpaceTile(mIPeopleManager, mAppWidgetManager, - mContext, - appWidgetId); + PeopleSpaceTile storedTile = getPeopleSpaceTile( + mContext, appWidgetId, mAppWidgetManager, mIPeopleManager); if (storedTile == null) { if (DEBUG) Log.d(TAG, "Could not find stored tile to add conversation to"); return; @@ -232,9 +235,8 @@ public class PeopleSpaceWidgetManager { StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction notificationAction, int appWidgetId) { - PeopleSpaceTile storedTile = getPeopleSpaceTile(mIPeopleManager, mAppWidgetManager, - mContext, - appWidgetId); + PeopleSpaceTile storedTile = getPeopleSpaceTile( + mContext, appWidgetId, mAppWidgetManager, mIPeopleManager); if (storedTile == null) { if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to"); return; @@ -312,18 +314,39 @@ public class PeopleSpaceWidgetManager { } }; - /** Adds {@code tile} mapped to {@code appWidgetId}. */ - public void addNewWidget(PeopleSpaceTile tile, int appWidgetId) { + /** + * Checks if this widget has been added externally, and this the first time we are learning + * about the widget. If so, the widget adder should have populated options with PeopleTileKey + * arguments. + */ + public void onAppWidgetOptionsChanged(int appWidgetId, Bundle newOptions) { + // Check if this widget has been added externally, and this the first time we are + // learning about the widget. If so, the widget adder should have populated options with + // PeopleTileKey arguments. + if (DEBUG) Log.d(TAG, "onAppWidgetOptionsChanged called for widget: " + appWidgetId); + PeopleTileKey optionsKey = AppWidgetOptionsHelper.getPeopleTileKeyFromBundle(newOptions); + if (optionsKey.isValid()) { + if (DEBUG) { + Log.d(TAG, "PeopleTileKey was present in Options, shortcutId: " + + optionsKey.getShortcutId()); + } + addNewWidget(appWidgetId, optionsKey); + AppWidgetOptionsHelper.removePeopleTileKey(mAppWidgetManager, appWidgetId); + } + } + + /** Adds{@code tile} mapped to {@code appWidgetId}. */ + public void addNewWidget(int appWidgetId, PeopleTileKey key) { mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED); synchronized (mLock) { - if (DEBUG) Log.d(TAG, "Add storage for : " + tile.getUserName()); - PeopleSpaceUtils.setStorageForTile(mContext, tile, appWidgetId); + if (DEBUG) Log.d(TAG, "Add storage for : " + key.getShortcutId()); + PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId); } try { - if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId()); - mLauncherApps.cacheShortcuts(tile.getPackageName(), - Collections.singletonList(tile.getId()), - tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); + if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + key.getShortcutId()); + mLauncherApps.cacheShortcuts(key.getPackageName(), + Collections.singletonList(key.getShortcutId()), + UserHandle.of(key.getUserId()), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); } catch (Exception e) { Log.w(TAG, "Exception caching shortcut:" + e); } @@ -335,19 +358,16 @@ public class PeopleSpaceWidgetManager { public void registerConversationListenerIfNeeded(int widgetId, PeopleSpaceWidgetProvider.TileConversationListener newListener) { // Retrieve storage needed for registration. - String packageName; - String shortcutId; - int userId; - String key; + PeopleTileKey key; synchronized (mLock) { SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId), Context.MODE_PRIVATE); - packageName = widgetSp.getString(PACKAGE_NAME, null); - shortcutId = widgetSp.getString(SHORTCUT_ID, null); - userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); - key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); - if (key == null) { - if (DEBUG) Log.e(TAG, "Could not register " + widgetId); + key = new PeopleTileKey( + widgetSp.getString(SHORTCUT_ID, EMPTY_STRING), + widgetSp.getInt(USER_ID, INVALID_USER_ID), + widgetSp.getString(PACKAGE_NAME, EMPTY_STRING)); + if (!key.isValid()) { + if (DEBUG) Log.w(TAG, "Could not register listener for widget: " + widgetId); return; } } @@ -359,9 +379,9 @@ public class PeopleSpaceWidgetManager { if (DEBUG) Log.d(TAG, "Register listener for " + widgetId + " with " + key); mListeners.put(key, newListener); } - mPeopleManager.registerConversationListener(packageName, - userId, - shortcutId, newListener, + mPeopleManager.registerConversationListener(key.getPackageName(), + key.getUserId(), + key.getShortcutId(), newListener, mContext.getMainExecutor()); } @@ -371,27 +391,24 @@ public class PeopleSpaceWidgetManager { if (DEBUG) Log.d(TAG, "Widget removed: " + widgetId); mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED); // Retrieve storage needed for widget deletion. - String packageName; - String shortcutId; - int userId; - String key; + PeopleTileKey key; Set<String> storedWidgetIdsForKey; synchronized (mLock) { SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId), Context.MODE_PRIVATE); - packageName = widgetSp.getString(PACKAGE_NAME, null); - shortcutId = widgetSp.getString(SHORTCUT_ID, null); - userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); - key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); - if (key == null) { + key = new PeopleTileKey( + widgetSp.getString(SHORTCUT_ID, null), + widgetSp.getInt(USER_ID, INVALID_USER_ID), + widgetSp.getString(PACKAGE_NAME, null)); + if (!key.isValid()) { if (DEBUG) Log.e(TAG, "Could not delete " + widgetId); return; } storedWidgetIdsForKey = new HashSet<>( - mSharedPrefs.getStringSet(key, new HashSet<>())); + mSharedPrefs.getStringSet(key.toString(), new HashSet<>())); } synchronized (mLock) { - PeopleSpaceUtils.removeStorageForTile(mContext, key, widgetId); + PeopleSpaceUtils.removeSharedPreferencesStorageForTile(mContext, key, widgetId); } // 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()); @@ -399,13 +416,13 @@ public class PeopleSpaceWidgetManager { && storedWidgetIdsForKey.size() == 1) { if (DEBUG) Log.d(TAG, "Remove caching and listener"); unregisterConversationListener(key, widgetId); - uncacheConversationShortcut(shortcutId, packageName, userId); + uncacheConversationShortcut(key); } } } /** Unregisters the conversation listener for {@code appWidgetId}. */ - private void unregisterConversationListener(String key, int appWidgetId) { + private void unregisterConversationListener(PeopleTileKey key, int appWidgetId) { PeopleSpaceWidgetProvider.TileConversationListener registeredListener; synchronized (mListeners) { registeredListener = mListeners.get(key); @@ -420,12 +437,12 @@ public class PeopleSpaceWidgetManager { } /** Uncaches the conversation shortcut. */ - private void uncacheConversationShortcut(String shortcutId, String packageName, int userId) { + private void uncacheConversationShortcut(PeopleTileKey key) { try { - if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + shortcutId); - mLauncherApps.uncacheShortcuts(packageName, - Collections.singletonList(shortcutId), - UserHandle.of(userId), + if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + key.getShortcutId()); + mLauncherApps.uncacheShortcuts(key.getPackageName(), + Collections.singletonList(key.getShortcutId()), + UserHandle.of(key.getUserId()), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); } catch (Exception e) { Log.d(TAG, "Exception uncaching shortcut:" + e); diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java index cccf7aa13028..3bc5b29bd05d 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java @@ -22,6 +22,7 @@ import android.app.people.PeopleManager; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; +import android.os.Bundle; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -67,18 +68,21 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { ensurePeopleSpaceWidgetManagerInitialized(context); peopleSpaceWidgetManager.updateWidgets(appWidgetIds); for (int appWidgetId : appWidgetIds) { + if (DEBUG) Log.d(TAG, "Ensure listener is registered for widget: " + appWidgetId); PeopleSpaceWidgetProvider.TileConversationListener newListener = new PeopleSpaceWidgetProvider.TileConversationListener(); peopleSpaceWidgetManager.registerConversationListenerIfNeeded(appWidgetId, newListener); } - return; } - private void ensurePeopleSpaceWidgetManagerInitialized(Context context) { - if (peopleSpaceWidgetManager == null) { - peopleSpaceWidgetManager = new PeopleSpaceWidgetManager(context); - } + /** Called when widget updates. */ + @Override + public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, + int appWidgetId, Bundle newOptions) { + super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); + ensurePeopleSpaceWidgetManagerInitialized(context); + peopleSpaceWidgetManager.onAppWidgetOptionsChanged(appWidgetId, newOptions); } @Override @@ -88,6 +92,12 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { peopleSpaceWidgetManager.deleteWidgets(appWidgetIds); } + private void ensurePeopleSpaceWidgetManagerInitialized(Context context) { + if (peopleSpaceWidgetManager == null) { + peopleSpaceWidgetManager = new PeopleSpaceWidgetManager(context); + } + } + @VisibleForTesting public void setPeopleSpaceWidgetManager(PeopleSpaceWidgetManager manager) { peopleSpaceWidgetManager = manager; diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java index 050352292b38..87b2a15d1c55 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java @@ -106,7 +106,7 @@ public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.R PeopleSpaceTile tile = mTiles.get(i); String status = PeopleSpaceUtils.getLastInteractionString(mContext, - tile.getLastInteractionTimestamp(), true); + tile.getLastInteractionTimestamp()); personView.setTextViewText(R.id.status, status); personView.setTextViewText(R.id.name, tile.getUserName().toString()); diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java new file mode 100644 index 000000000000..ac42cb08090a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.people.widget; + +import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING; +import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID; + +import android.text.TextUtils; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import java.util.Objects; + +/** Class that encapsulates fields identifying a Conversation. */ +public class PeopleTileKey { + private String mShortcutId; + private int mUserId; + private String mPackageName; + + public PeopleTileKey(String shortcutId, int userId, String packageName) { + mShortcutId = shortcutId; + mUserId = userId; + mPackageName = packageName; + } + + public PeopleTileKey(NotificationEntry entry) { + mShortcutId = entry.getRanking() != null + && entry.getRanking().getConversationShortcutInfo() != null + ? entry.getRanking().getConversationShortcutInfo().getId() + : EMPTY_STRING; + mUserId = entry.getSbn().getUser() != null + ? entry.getSbn().getUser().getIdentifier() : INVALID_USER_ID; + mPackageName = entry.getSbn().getPackageName(); + } + + public String getShortcutId() { + return mShortcutId; + } + + public int getUserId() { + return mUserId; + } + + public String getPackageName() { + return mPackageName; + } + + /** Returns whether PeopleTileKey is valid/well-formed. */ + public boolean isValid() { + return !TextUtils.isEmpty(mShortcutId) && !TextUtils.isEmpty(mPackageName) && mUserId >= 0; + } + + /** + * Returns the uniquely identifying key for the conversation. + * + * <p>{@code userId} will always be a number, so we put user ID as the + * delimiter between the app-provided strings of shortcut ID and package name. + * + * <p>There aren't restrictions on shortcut ID characters, but there are restrictions requiring + * a {@code packageName} to always start with a letter. This restriction means we are + * guaranteed to avoid cases like "a/b/0/0/package.name" having two potential keys, as the first + * case is impossible given the package name restrictions: + * <ul> + * <li>"a/b" + "/" + 0 + "/" + "0/packageName"</li> + * <li>"a/b/0" + "/" + 0 + "/" + "packageName"</li> + * </ul> + */ + @Override + public String toString() { + if (!isValid()) return null; + return mShortcutId + "/" + mUserId + "/" + mPackageName; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof PeopleTileKey)) { + return false; + } + final PeopleTileKey o = (PeopleTileKey) other; + return Objects.equals(o.toString(), this.toString()); + } + + @Override + public int hashCode() { + return mPackageName.hashCode() + Integer.valueOf(mUserId).hashCode() + + mShortcutId.hashCode(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt index 03c184336364..f87ea7c61ca8 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt @@ -214,9 +214,7 @@ class PrivacyDialogController( private fun filterType(type: PrivacyType?): PrivacyType? { return type?.let { - if (privacyItemController.allIndicatorsAvailable) { - it - } else if ((it == PrivacyType.TYPE_CAMERA || it == PrivacyType.TYPE_MICROPHONE) && + if ((it == PrivacyType.TYPE_CAMERA || it == PrivacyType.TYPE_MICROPHONE) && privacyItemController.micCameraAvailable) { it } else if (it == PrivacyType.TYPE_LOCATION && privacyItemController.locationAvailable) { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index 1e0451601e50..03d6154706eb 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -68,8 +68,6 @@ class PrivacyItemController @Inject constructor( addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) } const val TAG = "PrivacyItemController" - private const val ALL_INDICATORS = - SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED private const val LOCATION = SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED private const val DEFAULT_ALL_INDICATORS = false @@ -83,11 +81,6 @@ class PrivacyItemController @Inject constructor( @Synchronized get() = field.toList() // Returns a shallow copy of the list @Synchronized set - private fun isAllIndicatorsEnabled(): Boolean { - return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - ALL_INDICATORS, DEFAULT_ALL_INDICATORS) - } - private fun isMicCameraEnabled(): Boolean { return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, MIC_CAMERA, DEFAULT_MIC_CAMERA) @@ -120,34 +113,29 @@ class PrivacyItemController @Inject constructor( uiExecutor.execute(notifyChanges) } - var allIndicatorsAvailable = isAllIndicatorsEnabled() - private set var micCameraAvailable = isMicCameraEnabled() private set var locationAvailable = isLocationEnabled() + var allIndicatorsAvailable = micCameraAvailable && locationAvailable + private val devicePropertiesChangedListener = object : DeviceConfig.OnPropertiesChangedListener { override fun onPropertiesChanged(properties: DeviceConfig.Properties) { if (DeviceConfig.NAMESPACE_PRIVACY.equals(properties.getNamespace()) && - (properties.keyset.contains(ALL_INDICATORS) || - properties.keyset.contains(MIC_CAMERA) || + (properties.keyset.contains(MIC_CAMERA) || properties.keyset.contains(LOCATION))) { // Running on the ui executor so can iterate on callbacks - if (properties.keyset.contains(ALL_INDICATORS)) { - allIndicatorsAvailable = properties.getBoolean(ALL_INDICATORS, - DEFAULT_ALL_INDICATORS) - callbacks.forEach { it.get()?.onFlagAllChanged(allIndicatorsAvailable) } - } - if (properties.keyset.contains(MIC_CAMERA)) { micCameraAvailable = properties.getBoolean(MIC_CAMERA, DEFAULT_MIC_CAMERA) + allIndicatorsAvailable = micCameraAvailable && locationAvailable callbacks.forEach { it.get()?.onFlagMicCameraChanged(micCameraAvailable) } } if (properties.keyset.contains(LOCATION)) { locationAvailable = properties.getBoolean(LOCATION, DEFAULT_LOCATION) + allIndicatorsAvailable = micCameraAvailable && locationAvailable callbacks.forEach { it.get()?.onFlagLocationChanged(locationAvailable) } } internalUiExecutor.updateListeningState() @@ -163,8 +151,7 @@ class PrivacyItemController @Inject constructor( active: Boolean ) { // Check if we care about this code right now - if (!allIndicatorsAvailable && - (code in OPS_LOCATION && !locationAvailable)) { + if (code in OPS_LOCATION && !locationAvailable) { return } val userId = UserHandle.getUserId(uid) @@ -231,7 +218,7 @@ class PrivacyItemController @Inject constructor( */ private fun setListeningState() { val listen = !callbacks.isEmpty() and - (allIndicatorsAvailable || micCameraAvailable || locationAvailable) + (micCameraAvailable || locationAvailable) if (listening == listen) return listening = listen if (listening) { @@ -338,7 +325,7 @@ class PrivacyItemController @Inject constructor( AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE else -> return null } - if (type == PrivacyType.TYPE_LOCATION && (!allIndicatorsAvailable && !locationAvailable)) { + if (type == PrivacyType.TYPE_LOCATION && !locationAvailable) { return null } val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid) diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java index 0fa7b59d0e54..5ab7bd88e49b 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java +++ b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java @@ -88,7 +88,6 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl private boolean mViewAndWindowAdded; private ObjectAnimator mAnimator; - private boolean mAllIndicatorsFlagEnabled; private boolean mMicCameraIndicatorFlagEnabled; private boolean mLocationIndicatorEnabled; private List<PrivacyItem> mPrivacyItems; @@ -111,12 +110,10 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl mIconMarginStart = Math.round(res.getDimension(R.dimen.privacy_chip_icon_margin)); mIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_icon_size); - mAllIndicatorsFlagEnabled = privacyItemController.getAllIndicatorsAvailable(); mMicCameraIndicatorFlagEnabled = privacyItemController.getMicCameraAvailable(); mLocationIndicatorEnabled = privacyItemController.getLocationAvailable(); if (DEBUG) { - Log.d(TAG, "allIndicators: " + mAllIndicatorsFlagEnabled); Log.d(TAG, "micCameraIndicators: " + mMicCameraIndicatorFlagEnabled); Log.d(TAG, "locationIndicators: " + mLocationIndicatorEnabled); } @@ -135,12 +132,6 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl } @Override - public void onFlagAllChanged(boolean flag) { - if (DEBUG) Log.d(TAG, "all indicators enabled: " + flag); - mAllIndicatorsFlagEnabled = flag; - } - - @Override public void onFlagMicCameraChanged(boolean flag) { if (DEBUG) Log.d(TAG, "mic/camera indicators enabled: " + flag); mMicCameraIndicatorFlagEnabled = flag; @@ -155,8 +146,8 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl private void updateUI() { if (DEBUG) Log.d(TAG, mPrivacyItems.size() + " privacy items"); - if ((mMicCameraIndicatorFlagEnabled || mAllIndicatorsFlagEnabled - || mLocationIndicatorEnabled) && !mPrivacyItems.isEmpty()) { + if ((mMicCameraIndicatorFlagEnabled || mLocationIndicatorEnabled) + && !mPrivacyItems.isEmpty()) { if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) { showIndicator(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index fe76668ab68b..b8823e148e68 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -14,6 +14,8 @@ package com.android.systemui.qs; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; import android.util.Log; import android.view.View; import android.view.View.OnAttachStateChangeListener; @@ -35,6 +37,7 @@ import com.android.systemui.tuner.TunerService.Tunable; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -77,6 +80,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // This animates fading of SecurityFooter and media divider private TouchAnimator mAllPagesDelayedAnimator; private TouchAnimator mBrightnessAnimator; + private HeightExpansionAnimator mQQSTileHeightAnimator; + private HeightExpansionAnimator mOtherTilesExpandAnimator; + private boolean mNeedsAnimatorUpdate = false; private boolean mOnKeyguard; @@ -161,7 +167,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha @Override public void onViewAttachedToWindow(View v) { mTunerService.addTunable(this, ALLOW_FANCY_ANIMATION, - MOVE_FULL_ROWS, QuickQSPanel.NUM_QUICK_TILES); + MOVE_FULL_ROWS); } @Override @@ -179,9 +185,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } } else if (MOVE_FULL_ROWS.equals(key)) { mFullRows = TunerService.parseIntegerSwitch(newValue, true); - } else if (QuickQSPanel.NUM_QUICK_TILES.equals(key)) { - mNumQuickTiles = QuickQSPanel.parseNumTiles(newValue); - clearAnimationState(); } updateAnimators(); } @@ -209,6 +212,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha clearAnimationState(); mAllViews.clear(); mQuickQsViews.clear(); + mQQSTileHeightAnimator = null; + mOtherTilesExpandAnimator = null; + + mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(); QSTileLayout tileLayout = mQsPanelController.getTileLayout(); mAllViews.add((View) tileLayout); @@ -218,6 +225,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha + mQs.getHeader().getPaddingBottom(); firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0); + boolean qsSideLabelsEnabled = mFeatureFlags.isQSLabelsEnabled(); + int qqsTileHeight = 0; + for (QSTile tile : tiles) { QSTileView tileView = mQsPanelController.getTileView(tile); if (tileView == null) { @@ -237,22 +247,47 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha getRelativePosition(loc1, quickTileView.getIcon().getIconView(), view); getRelativePosition(loc2, tileIcon, view); final int xDiff = loc2[0] - loc1[0]; - final int yDiff = loc2[1] - loc1[1]; - + int yDiff = loc2[1] - loc1[1]; if (count < tileLayout.getNumVisibleTiles()) { + getRelativePosition(loc1, quickTileView, view); + getRelativePosition(loc2, tileView, view); + int yOffset = qsSideLabelsEnabled ? loc2[1] - loc1[1] : 0; // Move the quick tile right from its location to the new one. - translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff); - translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff); - - // Counteract the parent translation on the tile. So we have a static base to - // animate the label position off from. - //firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0); + View v = qsSideLabelsEnabled ? quickTileView.getIcon() : quickTileView; + translationXBuilder.addFloat(v, "translationX", 0, xDiff); + translationYBuilder.addFloat(v, "translationY", 0, yDiff - yOffset); + mAllViews.add(v); // Move the real tile from the quick tile position to its final // location. - translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); - translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); + v = qsSideLabelsEnabled ? tileIcon : tileView; + translationXBuilder.addFloat(v, "translationX", -xDiff, 0); + translationYBuilder.addFloat(v, "translationY", -yDiff + yOffset, 0); + + if (qsSideLabelsEnabled) { + translationYBuilder.addFloat(quickTileView, "translationY", 0, yOffset); + translationYBuilder.addFloat(tileView, "translationY", -yOffset, 0); + + if (mQQSTileHeightAnimator == null) { + mQQSTileHeightAnimator = new HeightExpansionAnimator(this, + quickTileView.getHeight(), tileView.getHeight()); + qqsTileHeight = quickTileView.getHeight(); + } + + mQQSTileHeightAnimator.addView(quickTileView); + View qqsLabelContainer = quickTileView.getLabelContainer(); + View qsLabelContainer = tileView.getLabelContainer(); + + getRelativePosition(loc1, qqsLabelContainer, view); + getRelativePosition(loc2, qsLabelContainer, view); + yDiff = loc2[1] - loc1[1] - yOffset; + + translationYBuilder.addFloat(qqsLabelContainer, "translationY", 0, yDiff); + translationYBuilder.addFloat(qsLabelContainer, "translationY", -yDiff, 0); + mAllViews.add(qqsLabelContainer); + mAllViews.add(qsLabelContainer); + } } else { // These tiles disappear when expanding firstPageBuilder.addFloat(quickTileView, "alpha", 1, 0); @@ -265,7 +300,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationX); } - if (mFeatureFlags.isQSLabelsEnabled()) { + if (qsSideLabelsEnabled) { mQuickQsViews.add(tileView); } else { mQuickQsViews.add(tileView.getIconWithBackground()); @@ -278,9 +313,29 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mAllViews.add(tileIcon); } else { - firstPageBuilder.addFloat(tileView, "alpha", 0, 1); - firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0); + if (!qsSideLabelsEnabled) { + firstPageBuilder.addFloat(tileView, "alpha", 0, 1); + firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0); + } else { + // Pretend there's a corresponding QQS tile (for the position) that we are + // expanding from. + SideLabelTileLayout qqsLayout = + (SideLabelTileLayout) mQuickQsPanel.getTileLayout(); + getRelativePosition(loc1, qqsLayout, view); + getRelativePosition(loc2, tileView, view); + int diff = loc2[1] - (loc1[1] + qqsLayout.getPhantomTopPosition(count)); + translationYBuilder.addFloat(tileView, "translationY", -diff, 0); + if (mOtherTilesExpandAnimator == null) { + mOtherTilesExpandAnimator = + new HeightExpansionAnimator( + this, qqsTileHeight, tileView.getHeight()); + } + mOtherTilesExpandAnimator.addView(tileView); + tileView.setClipChildren(true); + tileView.setClipToPadding(true); + } } + mAllViews.add(tileView); count++; } @@ -303,7 +358,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha .build(); // Fade in the tiles/labels as we reach the final position. Builder builder = new Builder() - .setStartDelay(EXPANDED_TILE_DELAY) + .setStartDelay(qsSideLabelsEnabled ? 0 : EXPANDED_TILE_DELAY) .addFloat(tileLayout, "alpha", 0, 1); mFirstPageDelayedAnimator = builder.build(); @@ -331,6 +386,12 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationYBuilder.setInterpolator(interpolatorBuilder.getYInterpolator()); mTranslationXAnimator = translationXBuilder.build(); mTranslationYAnimator = translationYBuilder.build(); + if (mQQSTileHeightAnimator != null) { + mQQSTileHeightAnimator.setInterpolator(interpolatorBuilder.getYInterpolator()); + } + if (mOtherTilesExpandAnimator != null) { + mOtherTilesExpandAnimator.setInterpolator(interpolatorBuilder.getYInterpolator()); + } } mNonfirstPageAnimator = new TouchAnimator.Builder() .addFloat(mQuickQsPanel, "alpha", 1, 0) @@ -404,6 +465,12 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if (mBrightnessAnimator != null) { mBrightnessAnimator.setPosition(position); } + if (mQQSTileHeightAnimator != null) { + mQQSTileHeightAnimator.setPosition(position); + } + if (mOtherTilesExpandAnimator != null) { + mOtherTilesExpandAnimator.setPosition(position); + } } else { mNonfirstPageAnimator.setPosition(position); mNonfirstPageDelayedAnimator.setPosition(position); @@ -446,6 +513,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha v.setAlpha(1); v.setTranslationX(0); v.setTranslationY(0); + if (v instanceof SideLabelTileLayout) { + ((SideLabelTileLayout) v).setClipChildren(false); + ((SideLabelTileLayout) v).setClipToPadding(false); + } + } + if (mQQSTileHeightAnimator != null) { + mQQSTileHeightAnimator.resetViewsHeights(); + } + if (mOtherTilesExpandAnimator != null) { + mOtherTilesExpandAnimator.resetViewsHeights(); } final int N2 = mQuickQsViews.size(); for (int i = 0; i < N2; i++) { @@ -483,4 +560,61 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha updateAnimators(); setCurrentPosition(); }; + + static class HeightExpansionAnimator { + private final List<View> mViews = new ArrayList<>(); + private final ValueAnimator mAnimator; + private final TouchAnimator.Listener mListener; + + private final ValueAnimator.AnimatorUpdateListener mUpdateListener = + new ValueAnimator.AnimatorUpdateListener() { + float mLastT = -1; + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + float t = valueAnimator.getAnimatedFraction(); + if (t == 0f) { + mListener.onAnimationAtStart(); + } else if (t == 1f) { + mListener.onAnimationAtEnd(); + } else if (mLastT <= 0 || mLastT == 1) { + mListener.onAnimationStarted(); + } + mLastT = t; + final int viewCount = mViews.size(); + int height = (Integer) valueAnimator.getAnimatedValue(); + for (int i = 0; i < viewCount; i++) { + View v = mViews.get(i); + v.setBottom(v.getTop() + height); + } + } + }; + + HeightExpansionAnimator(TouchAnimator.Listener listener, int startHeight, int endHeight) { + mListener = listener; + mAnimator = ValueAnimator.ofInt(startHeight, endHeight); + mAnimator.setRepeatCount(ValueAnimator.INFINITE); + mAnimator.setRepeatMode(ValueAnimator.REVERSE); + mAnimator.addUpdateListener(mUpdateListener); + } + + void addView(View v) { + mViews.add(v); + } + + void setInterpolator(TimeInterpolator interpolator) { + mAnimator.setInterpolator(interpolator); + } + + void setPosition(float position) { + mAnimator.setCurrentFraction(position); + } + + void resetViewsHeights() { + final int viewsCount = mViews.size(); + for (int i = 0; i < viewsCount; i++) { + View v = mViews.get(i); + v.setBottom(v.getTop() + v.getMeasuredHeight()); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index ed308ae1ac6c..7c9f0b082d8f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -430,6 +430,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); mQSPanelController.setRevealExpansion(expansion); mQSPanelController.getTileLayout().setExpansion(expansion); + mQuickQSPanelController.getTileLayout().setExpansion(expansion); mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff); if (fullyCollapsed) { mQSPanelScrollView.setScrollY(0); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index ff9b9120c6e1..c794a2121cc2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -315,7 +315,7 @@ public class QSPanel extends LinearLayout implements Tunable { int tileBg = getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size); mFooterMarginStartHorizontal = getResources().getDimensionPixelSize( R.dimen.qs_footer_horizontal_margin); - mVisualTilePadding = (int) ((tileSize - tileBg) / 2.0f); + mVisualTilePadding = mSideLabels ? 0 : (int) ((tileSize - tileBg) / 2.0f); updatePadding(); updatePageIndicator(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index f51d7ef381ee..e7828c366b64 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -348,19 +348,20 @@ public class QuickQSPanel extends QSPanel { } static class QQSSideLabelTileLayout extends SideLabelTileLayout { + QQSSideLabelTileLayout(Context context) { super(context, null); setClipChildren(false); setClipToPadding(false); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - lp.gravity = Gravity.CENTER_HORIZONTAL; setLayoutParams(lp); setMaxColumns(4); } @Override public boolean updateResources() { + mCellHeightResId = R.dimen.qs_quick_tile_size; boolean b = super.updateResources(); mMaxAllowedRows = 2; return b; @@ -379,5 +380,38 @@ public class QuickQSPanel extends QSPanel { updateMaxRows(10000, mRecords.size()); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + + @Override + public void setListening(boolean listening, UiEventLogger uiEventLogger) { + boolean startedListening = !mListening && listening; + super.setListening(listening, uiEventLogger); + if (startedListening) { + // getNumVisibleTiles() <= mRecords.size() + for (int i = 0; i < getNumVisibleTiles(); i++) { + QSTile tile = mRecords.get(i).tile; + uiEventLogger.logWithInstanceId(QSEvent.QQS_TILE_VISIBLE, 0, + tile.getMetricsSpec(), tile.getInstanceId()); + } + } + } + + @Override + public void setExpansion(float expansion) { + if (expansion > 0f && expansion < 1f) { + return; + } + boolean selected = expansion == 0f; + // Expansion == 0f is when QQS is fully showing (as opposed to 1f, which is QS). At this + // point we want them to be selected so the tiles will marquee (but not at other points + // of expansion. + // We set it as not important while we change this, so setting each tile as selected + // will not cause them to announce themselves until the user has actually selected the + // item. + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + for (int i = 0; i < getChildCount(); i++) { + getChildAt(i).setSelected(selected); + } + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index 671f8f7dd2d1..fee56b984ecc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -99,7 +99,7 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> break; } } - super.setTiles(tiles, !mQSLabelFlag); + super.setTiles(tiles, /* collapsedView */ true); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index e3c39aa77402..eedcdab68b9f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -97,7 +97,6 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader private boolean mListening; private AlarmClockInfo mNextAlarm; - private boolean mAllIndicatorsEnabled; private boolean mMicCameraIndicatorsEnabled; private boolean mLocationIndicatorsEnabled; private boolean mPrivacyChipLogged; @@ -151,14 +150,6 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader } @Override - public void onFlagAllChanged(boolean flag) { - if (mAllIndicatorsEnabled != flag) { - mAllIndicatorsEnabled = flag; - update(); - } - } - - @Override public void onFlagMicCameraChanged(boolean flag) { if (mMicCameraIndicatorsEnabled != flag) { mMicCameraIndicatorsEnabled = flag; @@ -270,7 +261,6 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mRingerContainer.setOnClickListener(mOnClickListener); mPrivacyChip.setOnClickListener(mOnClickListener); - mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable(); mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable(); mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable(); @@ -321,7 +311,6 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mNextAlarmController.addCallback(mNextAlarmChangeCallback); mLifecycle.setCurrentState(Lifecycle.State.RESUMED); // Get the most up to date info - mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable(); mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable(); mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable(); mPrivacyItemController.addCallback(mPICCallback); @@ -353,13 +342,13 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader private List<String> getIgnoredIconSlots() { ArrayList<String> ignored = new ArrayList<>(); if (getChipEnabled()) { - if (mAllIndicatorsEnabled || mMicCameraIndicatorsEnabled) { + if (mMicCameraIndicatorsEnabled) { ignored.add(mView.getResources().getString( com.android.internal.R.string.status_bar_camera)); ignored.add(mView.getResources().getString( com.android.internal.R.string.status_bar_microphone)); } - if (mAllIndicatorsEnabled || mLocationIndicatorsEnabled) { + if (mLocationIndicatorsEnabled) { ignored.add(mView.getResources().getString( com.android.internal.R.string.status_bar_location)); } @@ -368,7 +357,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader } private boolean getChipEnabled() { - return mMicCameraIndicatorsEnabled || mLocationIndicatorsEnabled || mAllIndicatorsEnabled; + return mMicCameraIndicatorsEnabled || mLocationIndicatorsEnabled; } private boolean isZenOverridingRinger() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt index 52f111e7ab48..0ebadfd2fa11 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt @@ -33,4 +33,15 @@ open class SideLabelTileLayout( override fun isFull(): Boolean { return mRecords.size >= maxTiles() } + + /** + * Return the position from the top of the layout of the tile with this index. + * + * This will return a position even for indices that go beyond [maxTiles], continuing the rows + * beyond that. + */ + fun getPhantomTopPosition(index: Int): Int { + val row = index / mColumns + return getRowTop(row) + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index c1ce4a577dda..9e0aa5ad78b1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -25,6 +25,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { protected int mColumns; protected int mCellWidth; + protected int mCellHeightResId = R.dimen.qs_tile_height; protected int mCellHeight; protected int mMaxCellHeight; protected int mCellMarginHorizontal; @@ -118,7 +119,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { final Resources res = mContext.getResources(); mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); updateColumns(); - mMaxCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height); + mMaxCellHeight = mContext.getResources().getDimensionPixelSize(mCellHeightResId); mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top); @@ -235,7 +236,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { layoutTileRecords(mRecords.size()); } - private int getRowTop(int row) { + protected int getRowTop(int row) { return row * (mCellHeight + mCellMarginVertical) + mCellMarginTop; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt index f8c0dd4239d9..7977b4904a7d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileViewHorizontal.kt @@ -14,7 +14,7 @@ import com.android.systemui.qs.tileimpl.QSTileViewHorizontal class CustomizeTileViewHorizontal( context: Context, icon: QSIconView -) : QSTileViewHorizontal(context, icon), +) : QSTileViewHorizontal(context, icon, collapsed = false), TileAdapter.CustomizeView { private var showAppLabel = false @@ -27,6 +27,7 @@ class CustomizeTileViewHorizontal( override fun handleStateChanged(state: QSTile.State) { super.handleStateChanged(state) + mShowRippleEffect = false mSecondLine.visibility = if (showAppLabel) View.VISIBLE else View.GONE } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 56d06eb1b474..1699a34e6c08 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -51,6 +51,7 @@ import com.android.systemui.Dependency; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -95,6 +96,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener QSHost host, Looper backgroundLooper, Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -102,8 +104,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener String action, Context userContext ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mWindowManager = WindowManagerGlobal.getWindowManagerService(); mComponent = ComponentName.unflattenFromString(action); mTile = new Tile(); @@ -450,6 +452,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener final Lazy<QSHost> mQSHostLazy; final Looper mBackgroundLooper; final Handler mMainHandler; + private final FalsingManager mFalsingManager; final MetricsLogger mMetricsLogger; final StatusBarStateController mStatusBarStateController; final ActivityStarter mActivityStarter; @@ -463,6 +466,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener Lazy<QSHost> hostLazy, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -471,6 +475,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mQSHostLazy = hostLazy; mBackgroundLooper = backgroundLooper; mMainHandler = mainHandler; + mFalsingManager = falsingManager; mMetricsLogger = metricsLogger; mStatusBarStateController = statusBarStateController; mActivityStarter = activityStarter; @@ -496,6 +501,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mQSHostLazy.get(), mBackgroundLooper, mMainHandler, + mFalsingManager, mMetricsLogger, mStatusBarStateController, mActivityStarter, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 9e5fe732cd0c..29b9e64d1659 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -248,10 +248,10 @@ public class QSFactoryImpl implements QSFactory { public QSTileView createTileView(QSTile tile, boolean collapsedView) { Context context = new ContextThemeWrapper(mQsHostLazy.get().getContext(), R.style.qs_theme); QSIconView icon = tile.createTileView(context); - if (collapsedView) { + if (mSideLabels) { + return new QSTileViewHorizontal(context, icon, collapsedView); + } else if (collapsedView) { return new QSTileBaseView(context, icon, collapsedView); - } else if (mSideLabels) { - return new QSTileViewHorizontal(context, icon); } else { return new com.android.systemui.qs.tileimpl.QSTileView(context, icon); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 33ca7d6bafd8..a45b131902c8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -58,13 +58,13 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private static final int ICON_MASK_ID = com.android.internal.R.string.config_icon_mask; protected final Handler mHandler = new H(); private final int[] mLocInScreen = new int[2]; - private final FrameLayout mIconFrame; + protected final FrameLayout mIconFrame; protected QSIconView mIcon; protected RippleDrawable mRipple; protected Drawable mTileBackground; private String mAccessibilityClass; private boolean mTileState; - private boolean mCollapsedView; + protected boolean mCollapsedView; protected boolean mShowRippleEffect = true; private float mStrokeWidthActive; private float mStrokeWidthInactive; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 5a81676567a7..f9d1def3b716 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -58,6 +58,7 @@ import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.Prefs; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; @@ -103,6 +104,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private final StatusBarStateController mStatusBarStateController; protected final ActivityStarter mActivityStarter; private final UiEventLogger mUiEventLogger; + private final FalsingManager mFalsingManager; private final QSLogger mQSLogger; private volatile int mReadyState; @@ -159,6 +161,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy QSHost host, Looper backgroundLooper, Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -171,6 +174,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mUiHandler = mainHandler; mHandler = new H(backgroundLooper); + mFalsingManager = falsingManager; mQSLogger = qsLogger; mMetricsLogger = metricsLogger; mStatusBarStateController = statusBarStateController; @@ -608,7 +612,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mContext, mEnforcedAdmin); mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); } else { - handleClick(); + if (!mFalsingManager.isFalseTap(true, 0.1)) { + handleClick(); + } } } else if (msg.what == SECONDARY_CLICK) { name = "handleSecondaryClick"; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index b59326ae56d5..c7ed89ba49b1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -26,6 +26,8 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.settingslib.Utils; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; @@ -116,7 +118,8 @@ public class QSTileView extends QSTileBaseView { } } - private boolean shouldLabelBeSingleLine() { + protected boolean shouldLabelBeSingleLine() { + if (mCollapsedView) return true; if (mLabel.getLineCount() > mMaxLabelLines) { return true; } else if (!TextUtils.isEmpty(mSecondLine.getText()) @@ -138,14 +141,14 @@ public class QSTileView extends QSTileBaseView { } else { labelColor = mColorLabelUnavailable; } - mLabel.setTextColor(labelColor); + changeLabelColor(labelColor); mState = state.state; mLabel.setText(state.label); } if (!Objects.equals(mSecondLine.getText(), state.secondaryLabel)) { mSecondLine.setText(state.secondaryLabel); - mSecondLine.setVisibility(TextUtils.isEmpty(state.secondaryLabel) ? View.GONE - : View.VISIBLE); + mSecondLine.setVisibility(TextUtils.isEmpty(state.secondaryLabel) || mCollapsedView + ? View.GONE : View.VISIBLE); } boolean dualTarget = mDualTargetAllowed && state.dualTarget; handleExpand(dualTarget); @@ -160,6 +163,10 @@ public class QSTileView extends QSTileBaseView { mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE); } + protected void changeLabelColor(ColorStateList color) { + mLabel.setTextColor(color); + } + protected void handleExpand(boolean dualTarget) { mExpandIndicator.setVisibility(dualTarget ? View.VISIBLE : View.GONE); mExpandSpace.setVisibility(dualTarget ? View.VISIBLE : View.GONE); @@ -178,4 +185,10 @@ public class QSTileView extends QSTileBaseView { public TextView getAppLabel() { return mSecondLine; } + + @Nullable + @Override + public View getLabelContainer() { + return mLabelContainer; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt index 328c2c353a29..32285cf797e4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt @@ -21,8 +21,7 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.Drawable -import android.graphics.drawable.ShapeDrawable -import android.graphics.drawable.shapes.RoundRectShape +import android.graphics.drawable.RippleDrawable import android.service.quicksettings.Tile.STATE_ACTIVE import android.view.Gravity import android.widget.LinearLayout @@ -32,25 +31,31 @@ import com.android.systemui.plugins.qs.QSIconView import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState -// Placeholder -private const val CORNER_RADIUS = 40f -private val RADII = (1..8).map { CORNER_RADIUS }.toFloatArray() - open class QSTileViewHorizontal( context: Context, - icon: QSIconView -) : QSTileView(context, icon, false) { + icon: QSIconView, + collapsed: Boolean +) : QSTileView(context, icon, collapsed) { - protected var backgroundDrawable: ShapeDrawable? = null + protected var colorBackgroundDrawable: Drawable? = null private var paintColor = Color.WHITE private var paintAnimator: ValueAnimator? = null + private var labelAnimator: ValueAnimator? = null init { orientation = HORIZONTAL + gravity = Gravity.CENTER_VERTICAL or Gravity.START mDualTargetAllowed = false + val padding = context.resources.getDimensionPixelSize(R.dimen.qs_tile_side_label_padding) + setPadding(padding, paddingTop, padding, paddingBottom) + mBg.setImageDrawable(null) + mIconFrame.removeAllViews() + removeView(mIconFrame) + val iconSize = context.resources.getDimensionPixelSize(R.dimen.qs_icon_size) + addView(mIcon, 0, LayoutParams(iconSize, iconSize)) + mColorLabelActive = ColorStateList.valueOf(getColorForState(getContext(), STATE_ACTIVE)) - mMaxLabelLines = 3 } override fun createLabel() { @@ -61,65 +66,112 @@ open class QSTileViewHorizontal( removeRule(RelativeLayout.ALIGN_PARENT_TOP) } } + mLabelContainer.setPadding(0, 0, 0, 0) + (mLabelContainer.layoutParams as MarginLayoutParams).apply { + marginStart = context.resources.getDimensionPixelSize(R.dimen.qs_label_container_margin) + } mLabel.gravity = Gravity.START mLabel.textDirection = TEXT_DIRECTION_LOCALE mSecondLine.gravity = Gravity.START mSecondLine.textDirection = TEXT_DIRECTION_LOCALE - val padding = context.resources.getDimensionPixelSize(R.dimen.qs_tile_side_label_padding) - mLabelContainer.setPaddingRelative(0, padding, padding, padding) + (mLabelContainer.layoutParams as LayoutParams).gravity = Gravity.CENTER_VERTICAL or Gravity.START + if (mCollapsedView) { + mSecondLine.visibility = GONE + } + } + + override fun shouldLabelBeSingleLine(): Boolean { + return true } override fun updateRippleSize() { } override fun newTileBackground(): Drawable? { - backgroundDrawable = ShapeDrawable(RoundRectShape(RADII, null, null)) - return backgroundDrawable + val ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable + colorBackgroundDrawable = ripple.findDrawableByLayerId(R.id.background) + return ripple } override fun setClickable(clickable: Boolean) { super.setClickable(clickable) - background = mTileBackground + background = if (clickable && mShowRippleEffect) { + mTileBackground + } else { + colorBackgroundDrawable + } } override fun handleStateChanged(state: QSTile.State) { super.handleStateChanged(state) - mSecondLine.setTextColor(mLabel.textColors) mLabelContainer.background = null val allowAnimations = animationsEnabled() && paintColor != Color.WHITE val newColor = getCircleColor(state.state) if (allowAnimations) { - animateToNewState(newColor) + animateBackground(newColor) } else { if (newColor != paintColor) { - clearAnimator() - backgroundDrawable?.setTintList(ColorStateList.valueOf(newColor)) + clearBackgroundAnimator() + colorBackgroundDrawable?.setTintList(ColorStateList.valueOf(newColor))?.also { + paintColor = newColor + } paintColor = newColor } } } - private fun animateToNewState(newColor: Int) { - if (newColor != paintColor) { - clearAnimator() - paintAnimator = ValueAnimator.ofArgb(paintColor, newColor) + private fun animateBackground(newBackgroundColor: Int) { + if (newBackgroundColor != paintColor) { + clearBackgroundAnimator() + paintAnimator = ValueAnimator.ofArgb(paintColor, newBackgroundColor) .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { addUpdateListener { animation: ValueAnimator -> val c = animation.animatedValue as Int - backgroundDrawable?.setTintList(ColorStateList.valueOf(c)) - paintColor = c + colorBackgroundDrawable?.setTintList(ColorStateList.valueOf(c))?.also { + paintColor = c + } } start() } } } - private fun clearAnimator() { + override fun changeLabelColor(color: ColorStateList) { + val allowAnimations = animationsEnabled() + val currentColor = mLabel.textColors.defaultColor + if (currentColor != color.defaultColor) { + clearLabelAnimator() + if (allowAnimations) { + labelAnimator = ValueAnimator.ofArgb(currentColor, color.defaultColor) + .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { + addUpdateListener { + setLabelsColor(ColorStateList.valueOf(it.animatedValue as Int)) + } + start() + } + } else { + setLabelsColor(color) + } + } + } + + private fun setLabelsColor(color: ColorStateList) { + mLabel.setTextColor(color) + if (!mCollapsedView) { + mSecondLine.setTextColor(color) + } + } + + private fun clearBackgroundAnimator() { paintAnimator?.cancel()?.also { paintAnimator = null } } + private fun clearLabelAnimator() { + labelAnimator?.cancel()?.also { labelAnimator = null } + } + override fun handleExpand(dualTarget: Boolean) {} }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index b8485907c519..07abb90f631c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -38,6 +38,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.GlobalSetting; @@ -63,6 +64,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -70,8 +72,8 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { BroadcastDispatcher broadcastDispatcher, Lazy<ConnectivityManager> lazyConnectivityManager ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mBroadcastDispatcher = broadcastDispatcher; mLazyConnectivityManager = lazyConnectivityManager; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt index dede627cd4dc..2945c6b4e22a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -15,6 +15,7 @@ import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost @@ -30,6 +31,7 @@ class AlarmTile @Inject constructor( host: QSHost, @Background backgroundLooper: Looper, @Main mainHandler: Handler, + falsingManager: FalsingManager, metricsLogger: MetricsLogger, statusBarStateController: StatusBarStateController, activityStarter: ActivityStarter, @@ -41,6 +43,7 @@ class AlarmTile @Inject constructor( host, backgroundLooper, mainHandler, + falsingManager, metricsLogger, statusBarStateController, activityStarter, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java index bf3e4be9b9db..49d3ff9439d6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java @@ -29,6 +29,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -59,6 +60,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -66,8 +68,8 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements BatteryController batteryController, SecureSettings secureSettings ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mBatteryController = batteryController; mBatteryController.observe(getLifecycle(), this); int currentUser = host.getUserContext().getUserId(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 1424244b6729..56df4d8de6c6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -42,6 +42,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -70,14 +71,15 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, BluetoothController bluetoothController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mController = bluetoothController; mDetailAdapter = (BluetoothDetailAdapter) createDetailAdapter(); mController.observe(getLifecycle(), mCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java index 70287cd37d01..0d73a5a97706 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -21,7 +21,6 @@ import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static com.android.systemui.DejankUtils.whitelistIpcs; -import android.hardware.SensorPrivacyManager; import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; @@ -35,6 +34,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; @@ -50,13 +50,15 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { @Background Looper backgroundLooper, @Main Handler mainHandler, MetricsLogger metricsLogger, + FalsingManager falsingManager, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, IndividualSensorPrivacyController sensorPrivacyController, KeyguardStateController keyguardStateController) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger, sensorPrivacyController, keyguardStateController); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger, sensorPrivacyController, + keyguardStateController); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index f03ce2c0b267..fa99eed150e3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -40,6 +40,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -81,6 +82,7 @@ public class CastTile extends QSTileImpl<BooleanState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -90,8 +92,8 @@ public class CastTile extends QSTileImpl<BooleanState> { NetworkController networkController, HotspotController hotspotController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mController = castController; mDetailAdapter = new CastDetailAdapter(); mKeyguard = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index 6a574d1d314b..8cc6ff254083 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -45,6 +45,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile.SignalState; @@ -76,14 +77,15 @@ public class CellularTile extends QSTileImpl<SignalState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, NetworkController networkController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mController = networkController; mDataController = mController.getMobileDataController(); mDetailAdapter = new CellularDetailAdapter(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index a6d96042d5af..ca7cf8319d32 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -32,6 +32,7 @@ import com.android.systemui.R.drawable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -60,6 +61,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -67,8 +69,8 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { UserTracker userTracker, SecureSettings secureSettings ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mSetting = new SecureSetting(secureSettings, mHandler, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index 85f12458c33a..61376f0610a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -29,6 +29,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -49,14 +50,15 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, DataSaverController dataSaverController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mDataSaverController = dataSaverController; mDataSaverController.observe(getLifecycle(), this); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 3b9f5dc8ea03..a74a50e7d6c2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles +import android.content.ComponentName import android.content.Intent import android.os.Handler import android.os.Looper @@ -27,10 +28,12 @@ import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.dagger.ControlsComponent import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE import com.android.systemui.controls.management.ControlsListingController -import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.controls.ui.ControlsActivity +import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost @@ -40,24 +43,24 @@ import com.android.systemui.statusbar.FeatureFlags import com.android.systemui.util.settings.GlobalSettings import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject -import javax.inject.Provider class DeviceControlsTile @Inject constructor( host: QSHost, @Background backgroundLooper: Looper, @Main mainHandler: Handler, + falsingManager: FalsingManager, metricsLogger: MetricsLogger, statusBarStateController: StatusBarStateController, activityStarter: ActivityStarter, qsLogger: QSLogger, private val controlsComponent: ControlsComponent, private val featureFlags: FeatureFlags, - private val dialogProvider: Provider<ControlsDialog>, globalSettings: GlobalSettings ) : QSTileImpl<QSTile.State>( host, backgroundLooper, mainHandler, + falsingManager, metricsLogger, statusBarStateController, activityStarter, @@ -72,7 +75,6 @@ class DeviceControlsTile @Inject constructor( private var hasControlsApps = AtomicBoolean(false) private val intent = Intent(Settings.ACTION_DEVICE_CONTROLS_SETTINGS) - private var controlsDialog: ControlsDialog? = null private val icon = ResourceIcon.get(R.drawable.ic_device_light) private val listingCallback = object : ControlsListingController.ControlsListingCallback { @@ -102,27 +104,19 @@ class DeviceControlsTile @Inject constructor( } override fun handleDestroy() { - dismissDialog() super.handleDestroy() } - private fun createDialog() { - if (controlsDialog?.isShowing != true) { - controlsDialog = dialogProvider.get() - } - } - - private fun dismissDialog() { - controlsDialog?.dismiss()?.also { - controlsDialog = null - } - } - override fun handleClick() { if (state.state == Tile.STATE_ACTIVE) { mUiHandler.post { - createDialog() - controlsDialog?.show(controlsComponent.getControlsUiController().get()) + mHost.collapsePanels() + val i = Intent().apply { + component = ComponentName(mContext, ControlsActivity::class.java) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra(ControlsUiController.BACK_TO_GLOBAL_ACTIONS, false) + } + mContext.startActivity(i) } } } @@ -133,9 +127,6 @@ class DeviceControlsTile @Inject constructor( state.contentDescription = state.label state.icon = icon if (controlsComponent.isEnabled() && hasControlsApps.get()) { - if (controlsDialog == null) { - mUiHandler.post(this::createDialog) - } if (controlsComponent.getVisibility() == AVAILABLE) { state.state = Tile.STATE_ACTIVE state.secondaryLabel = "" @@ -146,7 +137,6 @@ class DeviceControlsTile @Inject constructor( state.stateDescription = state.secondaryLabel } else { state.state = Tile.STATE_UNAVAILABLE - dismissDialog() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 7ec2691f3a04..4b96cf3dbf16 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -54,6 +54,7 @@ import com.android.systemui.SysUIToast; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -87,6 +88,7 @@ public class DndTile extends QSTileImpl<BooleanState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -94,8 +96,8 @@ public class DndTile extends QSTileImpl<BooleanState> { ZenModeController zenModeController, @Main SharedPreferences sharedPreferences ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mController = zenModeController; mSharedPreferences = sharedPreferences; mDetailAdapter = new DndDetailAdapter(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index cd45082458f2..31a98db033f9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -30,6 +30,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -51,14 +52,15 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, FlashlightController flashlightController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mFlashlightController = flashlightController; mFlashlightController.observe(getLifecycle(), this); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index a45d94ace425..4e0f634ce22c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -32,6 +32,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -57,6 +58,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -64,8 +66,8 @@ public class HotspotTile extends QSTileImpl<BooleanState> { HotspotController hotspotController, DataSaverController dataSaverController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mHotspotController = hotspotController; mDataSaverController = dataSaverController; mHotspotController.observe(this, mCallbacks); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index e1a1fd2679c5..14a3fc0bcd73 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -40,9 +40,9 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.plugins.qs.QSTile.Icon; import com.android.systemui.plugins.qs.QSTile.SignalState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.AlphaControlledSignalTileView; @@ -80,14 +80,15 @@ public class InternetTile extends QSTileImpl<SignalState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, NetworkController networkController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mController = networkController; mDataController = mController.getMobileDataController(); mController.observe(getLifecycle(), mSignalCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index d502d06efceb..830a1fd57d7f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -30,6 +30,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -55,6 +56,7 @@ public class LocationTile extends QSTileImpl<BooleanState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -62,8 +64,8 @@ public class LocationTile extends QSTileImpl<BooleanState> { LocationController locationController, KeyguardStateController keyguardStateController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mController = locationController; mKeyguard = keyguardStateController; mController.observe(this, mCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java index e9b712df2154..b8d879226f55 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -21,7 +21,6 @@ import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static com.android.systemui.DejankUtils.whitelistIpcs; -import android.hardware.SensorPrivacyManager; import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; @@ -35,6 +34,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; @@ -50,13 +50,15 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { @Background Looper backgroundLooper, @Main Handler mainHandler, MetricsLogger metricsLogger, + FalsingManager falsingManager, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, IndividualSensorPrivacyController sensorPrivacyController, KeyguardStateController keyguardStateController) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger, sensorPrivacyController, keyguardStateController); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger, sensorPrivacyController, + keyguardStateController); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java index 63e27796f441..6ac2f9ae202a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java @@ -36,6 +36,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -60,14 +61,15 @@ public class NfcTile extends QSTileImpl<BooleanState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, BroadcastDispatcher broadcastDispatcher ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mBroadcastDispatcher = broadcastDispatcher; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index d8548decc5f4..536908639def 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -40,6 +40,7 @@ import com.android.systemui.dagger.NightDisplayListenerModule; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -78,6 +79,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -86,8 +88,8 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements ColorDisplayManager colorDisplayManager, NightDisplayListenerModule.Builder nightDisplayListenerBuilder ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mLocationController = locationController; mManager = colorDisplayManager; mNightDisplayListenerBuilder = nightDisplayListenerBuilder; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java index aec7b9a4b6b1..479be659ba10 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java @@ -30,6 +30,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -57,13 +58,14 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mReduceBrightColorsController = reduceBrightColorsController; mReduceBrightColorsController.observe(getLifecycle(), this); mIsAvailable = isAvailable; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index c46cc4f9aa0a..173cc0597b0b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -31,6 +31,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -52,14 +53,15 @@ public class RotationLockTile extends QSTileImpl<BooleanState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, RotationLockController rotationLockController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mController = rotationLockController; mController.observe(this, mCallback); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 0f0a9a2766f1..6845dc593aa3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -29,6 +29,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -55,6 +56,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -62,8 +64,8 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> RecordingController controller, KeyguardDismissUtil keyguardDismissUtil ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mController = controller; mController.observe(this, mCallback); mKeyguardDismissUtil = keyguardDismissUtil; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index 0c582bdbe12f..a492330c1796 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -29,6 +29,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -59,14 +60,15 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS protected SensorPrivacyToggleTile(QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, IndividualSensorPrivacyController sensorPrivacyController, KeyguardStateController keyguardStateController) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mSensorPrivacyController = sensorPrivacyController; mKeyguard = keyguardStateController; mSensorPrivacyController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index 78975a4798ce..0ef5bc8f926b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -32,6 +32,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -66,6 +67,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -74,8 +76,8 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements BatteryController batteryController, LocationController locationController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mBatteryController = batteryController; mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class); mLocationController = locationController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java index a6cddd3367d3..2590f374a662 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java @@ -28,6 +28,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.State; @@ -51,6 +52,7 @@ public class UserTile extends QSTileImpl<State> implements UserInfoController.On QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -58,8 +60,8 @@ public class UserTile extends QSTileImpl<State> implements UserInfoController.On UserSwitcherController userSwitcherController, UserInfoController userInfoController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mUserSwitcherController = userSwitcherController; mUserInfoController = userInfoController; mUserInfoController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index 341e67c9393f..dab68ed30c2b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -37,6 +37,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; @@ -77,6 +78,7 @@ public class WifiTile extends QSTileImpl<SignalState> { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, @@ -84,8 +86,8 @@ public class WifiTile extends QSTileImpl<SignalState> { NetworkController networkController, AccessPointController accessPointController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mController = networkController; mWifiController = accessPointController; mDetailAdapter = (WifiDetailAdapter) createDetailAdapter(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index 5235b6d5a0bb..c88a002ddf92 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -29,6 +29,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -50,14 +51,15 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, ManagedProfileController managedProfileController ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); mProfileController = managedProfileController; mProfileController.observe(getLifecycle(), this); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index a87bfd83916a..5a714f2ed384 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -25,6 +25,11 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS; @@ -35,15 +40,12 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_T import android.annotation.FloatRange; import android.app.ActivityTaskManager; -import android.app.PendingIntent; -import android.app.PictureInPictureParams; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; -import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; @@ -57,14 +59,12 @@ import android.os.Looper; import android.os.PatternMatcher; import android.os.RemoteException; import android.os.UserHandle; -import android.util.ArraySet; import android.util.Log; import android.view.InputMonitor; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import android.view.accessibility.AccessibilityManager; -import android.window.IRemoteTransition; import androidx.annotation.NonNull; @@ -83,15 +83,11 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.shared.recents.IOverviewProxy; -import com.android.systemui.shared.recents.IPinnedStackAnimationListener; -import com.android.systemui.shared.recents.ISplitScreenListener; -import com.android.systemui.shared.recents.IStartingWindowListener; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.shared.system.RemoteTransitionCompat; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; @@ -103,7 +99,7 @@ import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; -import com.android.wm.shell.transition.RemoteTransitions; +import com.android.wm.shell.transition.ShellTransitions; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -111,7 +107,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.BiConsumer; -import java.util.function.Consumer; import javax.inject.Inject; @@ -151,12 +146,11 @@ public class OverviewProxyService extends CurrentUserTracker implements private final ScreenshotHelper mScreenshotHelper; private final Optional<OneHanded> mOneHandedOptional; private final CommandQueue mCommandQueue; - private final RemoteTransitions mShellTransitions; + private final ShellTransitions mShellTransitions; private final Optional<StartingSurface> mStartingSurface; private Region mActiveNavBarRegion; - private IPinnedStackAnimationListener mIPinnedStackAnimationListener; private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; private boolean mBound; @@ -169,8 +163,6 @@ public class OverviewProxyService extends CurrentUserTracker implements private float mWindowCornerRadius; private boolean mSupportsRoundedCornersOnWindows; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; - private final ArraySet<IRemoteTransition> mRemoteTransitions = new ArraySet<>(); - private IStartingWindowListener mIStartingWindowListener; @VisibleForTesting public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -388,20 +380,6 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override - public void setShelfHeight(boolean visible, int shelfHeight) { - if (!verifyCaller("setShelfHeight")) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - mPipOptional.ifPresent( - pip -> pip.setShelfHeight(visible, shelfHeight)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen, Insets visibleInsets, int taskId) { // Deprecated @@ -429,36 +407,6 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override - public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) { - if (!verifyCaller("setPinnedStackAnimationListener")) { - return; - } - mIPinnedStackAnimationListener = listener; - final long token = Binder.clearCallingIdentity(); - try { - mPipOptional.ifPresent( - pip -> pip.setPinnedStackAnimationListener(mPinnedStackAnimationCallback)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void setStartingWindowListener(IStartingWindowListener listener) { - if (!verifyCaller("setStartingWindowListener")) { - return; - } - mIStartingWindowListener = listener; - final long token = Binder.clearCallingIdentity(); - try { - mStartingSurface.ifPresent(s -> - s.setStartingWindowListener(mStartingWindowListener)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) { if (!verifyCaller("onQuickSwitchToNewTask")) { return; @@ -472,32 +420,6 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override - public void startOneHandedMode() { - if (!verifyCaller("startOneHandedMode")) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - mOneHandedOptional.ifPresent(oneHanded -> oneHanded.startOneHanded()); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void stopOneHandedMode() { - if (!verifyCaller("stopOneHandedMode")) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - mOneHandedOptional.ifPresent(oneHanded -> oneHanded.stopOneHanded()); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen, Insets visibleInsets, Task.TaskKey task) { mScreenshotHelper.provideScreenshot( @@ -525,190 +447,6 @@ public class OverviewProxyService extends CurrentUserTracker implements } } - @Override - public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, - PictureInPictureParams pictureInPictureParams, - int launcherRotation, int shelfHeight) { - if (!verifyCaller("startSwipePipToHome")) { - return null; - } - final long binderToken = Binder.clearCallingIdentity(); - try { - return mPipOptional.map(pip -> - pip.startSwipePipToHome(componentName, activityInfo, - pictureInPictureParams, launcherRotation, shelfHeight)) - .orElse(null); - } finally { - Binder.restoreCallingIdentity(binderToken); - } - } - - @Override - public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { - if (!verifyCaller("stopSwipePipToHome")) { - return; - } - final long binderToken = Binder.clearCallingIdentity(); - try { - mPipOptional.ifPresent(pip -> pip.stopSwipePipToHome( - componentName, destinationBounds)); - } finally { - Binder.restoreCallingIdentity(binderToken); - } - } - - @Override - public void registerRemoteTransition(RemoteTransitionCompat remoteTransition) { - if (!verifyCaller("registerRemoteTransition")) return; - final long binderToken = Binder.clearCallingIdentity(); - try { - mRemoteTransitions.add(remoteTransition.getTransition()); - mShellTransitions.registerRemote( - remoteTransition.getFilter(), remoteTransition.getTransition()); - } finally { - Binder.restoreCallingIdentity(binderToken); - } - } - - @Override - public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) { - if (!verifyCaller("registerRemoteTransition")) return; - final long binderToken = Binder.clearCallingIdentity(); - try { - mRemoteTransitions.remove(remoteTransition.getTransition()); - mShellTransitions.unregisterRemote(remoteTransition.getTransition()); - } finally { - Binder.restoreCallingIdentity(binderToken); - } - } - - @Override - public void registerSplitScreenListener(ISplitScreenListener listener) { - if (!verifyCaller("registerSplitScreenListener")) { - return; - } - mISplitScreenListener = listener; - final long token = Binder.clearCallingIdentity(); - try { - mSplitScreenOptional.ifPresent( - s -> s.registerSplitScreenListener(mSplitScreenListener)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void unregisterSplitScreenListener(ISplitScreenListener listener) { - if (!verifyCaller("unregisterSplitScreenListener")) { - return; - } - mISplitScreenListener = null; - final long token = Binder.clearCallingIdentity(); - try { - mSplitScreenOptional.ifPresent( - s -> s.unregisterSplitScreenListener(mSplitScreenListener)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void setSideStageVisibility(boolean visible) { - if (!verifyCaller("setSideStageVisibility")) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - mSplitScreenOptional.ifPresent(s -> s.setSideStageVisibility(visible)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void exitSplitScreen() { - if (!verifyCaller("exitSplitScreen")) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - mSplitScreenOptional.ifPresent(s -> s.exitSplitScreen()); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { - if (!verifyCaller("exitSplitScreenOnHide")) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - mSplitScreenOptional.ifPresent(s -> s.exitSplitScreenOnHide(exitSplitScreenOnHide)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void startTask(int taskId, int stage, int position, Bundle options) { - if (!verifyCaller("startTask")) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - mSplitScreenOptional.ifPresent( - s -> s.startTask(taskId, stage, position, options)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void startShortcut(String packageName, String shortcutId, int stage, int position, - Bundle options, UserHandle user) { - if (!verifyCaller("startShortcut")) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - mSplitScreenOptional.ifPresent(s -> - s.startShortcut(packageName, shortcutId, stage, position, options, user)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void startIntent(PendingIntent intent, Intent fillInIntent, - int stage, int position, Bundle options) { - if (!verifyCaller("startIntent")) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - mSplitScreenOptional.ifPresent(s -> - s.startIntent(intent, mContext, fillInIntent, stage, position, options)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override - public void removeFromSideStage(int taskId) { - if (!verifyCaller("removeFromSideStage")) { - return; - } - final long token = Binder.clearCallingIdentity(); - try { - mSplitScreenOptional.ifPresent( - s -> s.removeFromSideStage(taskId)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -762,6 +500,22 @@ public class OverviewProxyService extends CurrentUserTracker implements params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder()); params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius); params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows); + + mPipOptional.ifPresent((pip) -> params.putBinder( + KEY_EXTRA_SHELL_PIP, + pip.createExternalInterface().asBinder())); + mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder( + KEY_EXTRA_SHELL_SPLIT_SCREEN, + splitscreen.createExternalInterface().asBinder())); + mOneHandedOptional.ifPresent((onehanded) -> params.putBinder( + KEY_EXTRA_SHELL_ONE_HANDED, + onehanded.createExternalInterface().asBinder())); + params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS, + mShellTransitions.createExternalInterface().asBinder()); + mStartingSurface.ifPresent((startingwindow) -> params.putBinder( + KEY_EXTRA_SHELL_STARTING_WINDOW, + startingwindow.createExternalInterface().asBinder())); + try { mOverviewProxy.onInitialize(params); } catch (RemoteException e) { @@ -801,42 +555,11 @@ public class OverviewProxyService extends CurrentUserTracker implements private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged; private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener = this::notifySplitScreenBoundsChanged; - private final Consumer<Boolean> mPinnedStackAnimationCallback = - this::notifyPinnedStackAnimationStarted; - - private final BiConsumer<Integer, Integer> mStartingWindowListener = - this::notifyTaskLaunching; // This is the death handler for the binder from the launcher service private final IBinder.DeathRecipient mOverviewServiceDeathRcpt = this::cleanupAfterDeath; - private ISplitScreenListener mISplitScreenListener; - private final SplitScreen.SplitScreenListener mSplitScreenListener = - new SplitScreen.SplitScreenListener() { - @Override - public void onStagePositionChanged(int stage, int position) { - try { - if (mISplitScreenListener != null) { - mISplitScreenListener.onStagePositionChanged(stage, position); - } - } catch (RemoteException e) { - Log.e(TAG_OPS, "onStagePositionChanged", e); - } - } - - @Override - public void onTaskStageChanged(int taskId, int stage, boolean visible) { - try { - if (mISplitScreenListener != null) { - mISplitScreenListener.onTaskStageChanged(taskId, stage, visible); - } - } catch (RemoteException e) { - Log.e(TAG_OPS, "onTaskStageChanged", e); - } - } - }; - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject public OverviewProxyService(Context context, CommandQueue commandQueue, @@ -849,7 +572,7 @@ public class OverviewProxyService extends CurrentUserTracker implements Optional<Lazy<StatusBar>> statusBarOptionalLazy, Optional<OneHanded> oneHandedOptional, BroadcastDispatcher broadcastDispatcher, - RemoteTransitions shellTransitions, + ShellTransitions shellTransitions, Optional<StartingSurface> startingSurface) { super(broadcastDispatcher); mContext = context; @@ -966,29 +689,6 @@ public class OverviewProxyService extends CurrentUserTracker implements } } - private void notifyPinnedStackAnimationStarted(Boolean isAnimationStarted) { - if (mIPinnedStackAnimationListener == null) { - return; - } - try { - mIPinnedStackAnimationListener.onPinnedStackAnimationStarted(); - } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call onPinnedStackAnimationStarted()", e); - } - } - - private void notifyTaskLaunching(int taskId, int supportedType) { - if (mIStartingWindowListener == null) { - return; - } - - try { - mIStartingWindowListener.onTaskLaunching(taskId, supportedType); - } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call notifyTaskLaunching()", e); - } - } - private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing) { mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING, @@ -1032,12 +732,6 @@ public class OverviewProxyService extends CurrentUserTracker implements // Clean up the minimized state if launcher dies mLegacySplitScreenOptional.ifPresent( splitScreen -> splitScreen.setMinimized(false)); - - // Clean up any registered remote transitions - for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) { - mShellTransitions.unregisterRemote(mRemoteTransitions.valueAt(i)); - } - mRemoteTransitions.clear(); } public void startConnectionToCurrentUser() { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS b/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS new file mode 100644 index 000000000000..9b3e386bc0d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS @@ -0,0 +1,12 @@ +# Scroll Capture (Long Screenshots) +# Bug component: 801322 +# +# Referenced by: +# +# core/java/src/android/view/OWNERS +# core/java/src/com/android/internal/view/OWNERS +# core/tests/coretests/src/android/view/OWNERS +# core/tests/coretests/src/com/android/internal/view/OWNERS + +mrcasey@google.com +mrenouf@google.com diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index c4fa6df56775..059903961eae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -141,6 +141,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private static final int MSG_HANDLE_WINDOW_MANAGER_LOGGING_COMMAND = 57 << MSG_SHIFT; //TODO(b/169175022) Update name and when feature name is locked. private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE = 58 << MSG_SHIFT; + private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -369,6 +370,11 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< * Handles a window manager shell logging command. */ default void handleWindowManagerLoggingCommand(String[] args, ParcelFileDescriptor outFd) {} + + /** + * @see IStatusBar#setNavigationBarLumaSamplingEnabled(int, boolean) + */ + default void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {} } public CommandQueue(Context context) { @@ -1019,6 +1025,14 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } @Override + public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED, displayId, + enable ? 1 : 0).sendToTarget(); + } + } + + @Override public void passThroughShellCommand(String[] args, ParcelFileDescriptor pfd) { final FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor()); final PrintWriter pw = new PrintWriter(fos); @@ -1400,6 +1414,12 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } args.recycle(); break; + case MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).setNavigationBarLumaSamplingEnabled(msg.arg1, + msg.arg2 != 0); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index d08f9736adf6..85a1aed68559 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -146,17 +146,19 @@ public class ActivityLaunchAnimator { private final ExpandableNotificationRow mSourceNotification; private final ExpandAnimationParameters mParams; private final Rect mWindowCrop = new Rect(); - private final float mNotificationCornerRadius; - private float mCornerRadius; private boolean mIsFullScreenLaunch = true; private final SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier; - public AnimationRunner(ExpandableNotificationRow sourceNofitication) { - mSourceNotification = sourceNofitication; + private final float mNotificationStartTopCornerRadius; + private final float mNotificationStartBottomCornerRadius; + + AnimationRunner(ExpandableNotificationRow sourceNotification) { + mSourceNotification = sourceNotification; mParams = new ExpandAnimationParameters(); mSyncRtTransactionApplier = new SyncRtSurfaceTransactionApplier(mSourceNotification); - mNotificationCornerRadius = Math.max(mSourceNotification.getCurrentTopRoundness(), - mSourceNotification.getCurrentBottomRoundness()); + mNotificationStartTopCornerRadius = mSourceNotification.getCurrentBackgroundRadiusTop(); + mNotificationStartBottomCornerRadius = + mSourceNotification.getCurrentBackgroundRadiusBottom(); } @Override @@ -224,7 +226,10 @@ public class ActivityLaunchAnimator { + notificationHeight, primary.position.y + primary.sourceContainerBounds.bottom, progress); - mCornerRadius = MathUtils.lerp(mNotificationCornerRadius, + mParams.topCornerRadius = MathUtils.lerp(mNotificationStartTopCornerRadius, + mWindowCornerRadius, progress); + mParams.bottomCornerRadius = MathUtils.lerp( + mNotificationStartBottomCornerRadius, mWindowCornerRadius, progress); applyParamsToWindow(primary); applyParamsToNotification(mParams); @@ -309,12 +314,13 @@ public class ActivityLaunchAnimator { Matrix m = new Matrix(); m.postTranslate(0, (float) (mParams.top - app.position.y)); mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight()); + float cornerRadius = Math.min(mParams.topCornerRadius, mParams.bottomCornerRadius); SurfaceParams params = new SurfaceParams.Builder(app.leash) .withAlpha(1f) .withMatrix(m) .withWindowCrop(mWindowCrop) .withLayer(app.prefixOrderIndex) - .withCornerRadius(mCornerRadius) + .withCornerRadius(cornerRadius) .withVisibility(true) .build(); mSyncRtTransactionApplier.scheduleApply(params); @@ -339,6 +345,8 @@ public class ActivityLaunchAnimator { int bottom; int startClipTopAmount; int parentStartClipTopAmount; + float topCornerRadius; + float bottomCornerRadius; public ExpandAnimationParameters() { } @@ -389,6 +397,14 @@ public class ActivityLaunchAnimator { public float getStartTranslationZ() { return startTranslationZ; } + + public float getTopCornerRadius() { + return topCornerRadius; + } + + public float getBottomCornerRadius() { + return bottomCornerRadius; + } } public interface Callback { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 31d052d75998..18e5ead7fbb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -883,8 +883,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } private void applyBackgroundRoundness(float topRadius, float bottomRadius) { - mBackgroundDimmed.setRoundness(topRadius, bottomRadius); - mBackgroundNormal.setRoundness(topRadius, bottomRadius); + mBackgroundDimmed.setRadius(topRadius, bottomRadius); + mBackgroundNormal.setRadius(topRadius, bottomRadius); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index cde9f38d0074..046fbd5b616b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -346,6 +346,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private SystemNotificationAsyncTask mSystemNotificationAsyncTask = new SystemNotificationAsyncTask(); + private float mTopRoundnessDuringExpandAnimation; + private float mBottomRoundnessDuringExpandAnimation; + /** * Returns whether the given {@code statusBarNotification} is a system notification. * <b>Note</b>, this should be run in the background thread if possible as it makes multiple IPC @@ -2009,6 +2012,24 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return false; } + @Override + public float getCurrentTopRoundness() { + if (mExpandAnimationRunning) { + return mTopRoundnessDuringExpandAnimation; + } + + return super.getCurrentTopRoundness(); + } + + @Override + public float getCurrentBottomRoundness() { + if (mExpandAnimationRunning) { + return mBottomRoundnessDuringExpandAnimation; + } + + return super.getCurrentBottomRoundness(); + } + public void applyExpandAnimationParams(ExpandAnimationParameters params) { if (params == null) { return; @@ -2024,17 +2045,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView int top = params.getTop(); float interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(params.getProgress()); int startClipTopAmount = params.getStartClipTopAmount(); + int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, interpolation); if (mNotificationParent != null) { float parentY = mNotificationParent.getTranslationY(); top -= parentY; mNotificationParent.setTranslationZ(translationZ); + + // When the expanding notification is below its parent, the parent must be clipped + // exactly how it was clipped before the animation. When the expanding notification is + // on or above its parent (top <= 0), then the parent must be clipped exactly 'top' + // pixels to show the expanding notification, while still taking the decreasing + // notification clipTopAmount into consideration, so 'top + clipTopAmount'. int parentStartClipTopAmount = params.getParentStartClipTopAmount(); - if (startClipTopAmount != 0) { - int clipTopAmount = (int) MathUtils.lerp(parentStartClipTopAmount, - parentStartClipTopAmount - startClipTopAmount, - interpolation); - mNotificationParent.setClipTopAmount(clipTopAmount); - } + int parentClipTopAmount = Math.min(parentStartClipTopAmount, + top + clipTopAmount); + mNotificationParent.setClipTopAmount(parentClipTopAmount); + mNotificationParent.setExtraWidthForClipping(extraWidthForClipping); float clipBottom = Math.max(params.getBottom(), parentY + mNotificationParent.getActualHeight() @@ -2043,12 +2069,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView int minimumHeightForClipping = (int) (clipBottom - clipTop); mNotificationParent.setMinimumHeightForClipping(minimumHeightForClipping); } else if (startClipTopAmount != 0) { - int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, interpolation); setClipTopAmount(clipTopAmount); } setTranslationY(top); setActualHeight(params.getHeight()); + mTopRoundnessDuringExpandAnimation = params.getTopCornerRadius() / mOutlineRadius; + mBottomRoundnessDuringExpandAnimation = params.getBottomCornerRadius() / mOutlineRadius; + invalidateOutline(); + mBackgroundNormal.setExpandAnimationParams(params); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 85f556fa733c..3728388f63b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -27,7 +27,6 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewOutlineProvider; -import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -83,8 +82,8 @@ public abstract class ExpandableOutlineView extends ExpandableView { private final ViewOutlineProvider mProvider = new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - if (!mCustomOutline && mCurrentTopRoundness == 0.0f - && mCurrentBottomRoundness == 0.0f && !mAlwaysRoundBothCorners + if (!mCustomOutline && getCurrentTopRoundness() == 0.0f + && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners && !mTopAmountRounded) { int translation = mShouldTranslateContents ? (int) getTranslation() : 0; int left = Math.max(translation, 0); @@ -135,10 +134,12 @@ public abstract class ExpandableOutlineView extends ExpandableView { ? mOutlineRadius : getCurrentBackgroundRadiusBottom(); if (topRoundness + bottomRoundness > height) { float overShoot = topRoundness + bottomRoundness - height; - topRoundness -= overShoot * mCurrentTopRoundness - / (mCurrentTopRoundness + mCurrentBottomRoundness); - bottomRoundness -= overShoot * mCurrentBottomRoundness - / (mCurrentTopRoundness + mCurrentBottomRoundness); + float currentTopRoundness = getCurrentTopRoundness(); + float currentBottomRoundness = getCurrentBottomRoundness(); + topRoundness -= overShoot * currentTopRoundness + / (currentTopRoundness + currentBottomRoundness); + bottomRoundness -= overShoot * currentBottomRoundness + / (currentTopRoundness + currentBottomRoundness); } getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness, mTmpPath); return mTmpPath; @@ -267,7 +268,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { if (mTopAmountRounded) { return mOutlineRadius; } - return mCurrentTopRoundness * mOutlineRadius; + return getCurrentTopRoundness() * mOutlineRadius; } public float getCurrentTopRoundness() { @@ -278,8 +279,8 @@ public abstract class ExpandableOutlineView extends ExpandableView { return mCurrentBottomRoundness; } - protected float getCurrentBackgroundRadiusBottom() { - return mCurrentBottomRoundness * mOutlineRadius; + public float getCurrentBackgroundRadiusBottom() { + return getCurrentBottomRoundness() * mOutlineRadius; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index 62d596b60a89..95885633a3e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -224,10 +224,9 @@ public class NotificationBackgroundView extends View { } /** - * Sets the current top and bottom roundness amounts for this background, between 0.0 (not - * rounded) and 1.0 (maximally rounded). + * Sets the current top and bottom radius for this background. */ - public void setRoundness(float topRoundness, float bottomRoundness) { + public void setRadius(float topRoundness, float bottomRoundness) { if (topRoundness == mCornerRadii[0] && bottomRoundness == mCornerRadii[4]) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index d8ee102064e1..f65ae0c39331 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -34,7 +34,6 @@ import android.widget.RemoteViews; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.widget.CachingIconView; import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; @@ -338,6 +337,7 @@ public class NotificationChildrenContainer extends ViewGroup { } else { header.reapply(getContext(), mNotificationHeader); } + mNotificationHeaderWrapper.setExpanded(mChildrenExpanded); mNotificationHeaderWrapper.onContentUpdated(mContainingNotification); if (mNotificationHeaderWrapper instanceof NotificationHeaderViewWrapper) { NotificationHeaderViewWrapper headerWrapper = @@ -638,10 +638,6 @@ public class NotificationChildrenContainer extends ViewGroup { childState.location = parentState.location; childState.inShelf = parentState.inShelf; yPosition += intrinsicHeight; - if (child.isExpandAnimationRunning()) { - launchTransitionCompensation = -ambientState.getExpandAnimationTopChange(); - } - } if (mOverflowNumber != null) { ExpandableNotificationRow overflowView = mAttachedChildren.get(Math.min( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 6c8cdf67d974..b06f7d25db16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -318,9 +318,6 @@ class NotificationSectionsManager @Inject internal constructor( (child == null || row != null && nextBucket != row.entry.bucket) if (isSectionBoundary && showHeaders) { when (nextBucket) { - BUCKET_HEADS_UP -> incomingState?.targetPosition = i + 1 - BUCKET_PEOPLE -> peopleState?.targetPosition = i + 1 - BUCKET_ALERTING -> alertingState?.targetPosition = i + 1 BUCKET_SILENT -> gentleState?.targetPosition = i + 1 } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 5d2203b57991..d7a98bdf2715 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -28,14 +28,12 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; -import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.FooterView; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; /** @@ -156,7 +154,7 @@ public class StackScrollAlgorithm { private void updateClipping(StackScrollAlgorithmState algorithmState, AmbientState ambientState) { float drawStart = !ambientState.isOnKeyguard() ? ambientState.getTopPadding() - + ambientState.getStackTranslation() + ambientState.getExpandAnimationTopChange() + + ambientState.getStackTranslation() : 0; float clipStart = 0; int childCount = algorithmState.visibleChildren.size(); @@ -329,9 +327,6 @@ public class StackScrollAlgorithm { childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; float inset = ambientState.getTopPadding() + ambientState.getStackTranslation() + ambientState.getSectionPadding(); - if (i <= algorithmState.getIndexOfExpandingNotification()) { - inset += ambientState.getExpandAnimationTopChange(); - } if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) { // Even if we're not scrolled away we're in view and we're also not in the // shelf. We can relax the constraints and let us scroll off the top! diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 85d8df8e6057..c23f1ad6f9c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -148,6 +148,11 @@ public class DozeParameters implements TunerService.Tunable, return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold); } + public int getQuickPickupAodDuration() { + return getInt("doze.gesture.quickpickup.duration", + R.integer.doze_quick_pickup_aod_duration); + } + /** * For how long a wallpaper can be visible in AoD before it fades aways. * @return duration in millis. @@ -175,6 +180,10 @@ public class DozeParameters implements TunerService.Tunable, return mDozeAlwaysOn && !mBatteryController.isAodPowerSave(); } + public boolean isQuickPickupEnabled() { + return mAmbientDisplayConfiguration.quickPickupSensorEnabled(UserHandle.USER_CURRENT); + } + /** * Some screens need to be completely black before changing the display power mode, * unexpected behavior might happen if this parameter isn't respected. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 89b97aef6283..6b144c652c56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -28,6 +28,7 @@ import android.graphics.Rect; import android.graphics.drawable.Icon; import android.util.AttributeSet; import android.util.Property; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.animation.Interpolator; @@ -35,6 +36,7 @@ import androidx.collection.ArrayMap; import com.android.internal.statusbar.StatusBarIcon; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.settingslib.Utils; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.AlphaOptimizedFrameLayout; @@ -168,6 +170,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private Rect mIsolatedIconLocation; private int[] mAbsolutePosition = new int[2]; private View mIsolatedIconForAnimation; + private int mThemedTextColorPrimary; public NotificationIconContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -179,6 +182,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding); mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); mStaticDotDiameter = 2 * mStaticDotRadius; + final Context themedContext = new ContextThemeWrapper(getContext(), + com.android.internal.R.style.Theme_DeviceDefault_DayNight); + mThemedTextColorPrimary = Utils.getColorAttr(themedContext, + com.android.internal.R.attr.textColorPrimary).getDefaultColor(); } @Override @@ -806,7 +813,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } } icon.setVisibleState(visibleState, animationsAllowed); - icon.setIconColor(iconColor, needsCannedAnimation && animationsAllowed); + icon.setIconColor(mThemedTextColorPrimary, + needsCannedAnimation && animationsAllowed); if (animate) { animateTo(icon, animationProperties); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 19b98953325f..ca6e53d2ec04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -439,7 +439,6 @@ public class NotificationPanelViewController extends PanelViewController { private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); private boolean mUserSetupComplete; private int mQsNotificationTopPadding; - private float mExpandOffset; private boolean mHideIconsDuringNotificationLaunch = true; private int mStackScrollerMeasuringPass; private ArrayList<Consumer<ExpandableNotificationRow>> @@ -576,8 +575,8 @@ public class NotificationPanelViewController extends PanelViewController { FeatureFlags featureFlags) { super(view, falsingManager, dozeLog, keyguardStateController, (SysuiStatusBarStateController) statusBarStateController, vibratorHelper, - latencyTracker, flingAnimationUtilsBuilder.get(), statusBarTouchableRegionManager, - ambientState); + statusBarKeyguardViewManager, latencyTracker, flingAnimationUtilsBuilder.get(), + statusBarTouchableRegionManager, ambientState); mView = view; mMetricsLogger = metricsLogger; mActivityManager = activityManager; @@ -1349,7 +1348,6 @@ public class NotificationPanelViewController extends PanelViewController { super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); } - private boolean onQsIntercept(MotionEvent event) { int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { @@ -2444,8 +2442,7 @@ public class NotificationPanelViewController extends PanelViewController { } startHeight = -mQs.getQsMinExpansionHeight(); } - float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount)) - + mExpandOffset; + float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount)); return Math.min(0, translation); } @@ -3185,16 +3182,16 @@ public class NotificationPanelViewController extends PanelViewController { } public void applyExpandAnimationParams(ExpandAnimationParameters params) { - mExpandOffset = params != null ? params.getTopChange() : 0; - updateQsExpansion(); - if (params != null) { - boolean hideIcons = params.getProgress( - ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; - if (hideIcons != mHideIconsDuringNotificationLaunch) { - mHideIconsDuringNotificationLaunch = hideIcons; - if (!hideIcons) { - mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */); - } + if (params == null) { + return; + } + + boolean hideIcons = params.getProgress( + ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; + if (hideIcons != mHideIconsDuringNotificationLaunch) { + mHideIconsDuringNotificationLaunch = hideIcons; + if (!hideIcons) { + mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationTapHelper.java index 50c8e2e0d710..66df936dd556 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationTapHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationTapHelper.java @@ -64,7 +64,7 @@ public class NotificationTapHelper { mTrackTouch = event.getY() <= maxTouchableHeight; break; case MotionEvent.ACTION_MOVE: - if (mTrackTouch && mFalsingManager.isFalseTap(false)) { + if (mTrackTouch && mFalsingManager.isFalseTap(false, 0)) { makeInactive(); mTrackTouch = false; } @@ -78,10 +78,10 @@ public class NotificationTapHelper { // 1) See if we have confidence that we can activate after a single tap. // 2) Else, see if it looks like a tap at all and check for a double-tap. - if (!mFalsingManager.isFalseTap(true)) { + if (!mFalsingManager.isFalseTap(true, 0)) { makeInactive(); return mDoubleTapListener.onDoubleTap(); - } else if (!mFalsingManager.isFalseTap(false)) { + } else if (!mFalsingManager.isFalseTap(false, 0)) { if (mSlideBackListener != null && mSlideBackListener.onSlideBack()) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index 0c9ed661925c..1cb0be0efc90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -89,30 +89,22 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout @Override protected void dispatchDraw(Canvas canvas) { - // Invert the order of the scroll view and user switcher such that the notifications receive - // touches first but the panel gets drawn above. mDrawingOrderedChildren.clear(); mLayoutDrawingOrder.clear(); if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) { mDrawingOrderedChildren.add(mKeyguardStatusBar); mLayoutDrawingOrder.add(mKeyguardStatusBar); } - if (mStackScroller.getVisibility() == View.VISIBLE) { - mDrawingOrderedChildren.add(mStackScroller); - mLayoutDrawingOrder.add(mStackScroller); - } if (mQsFrame.getVisibility() == View.VISIBLE) { mDrawingOrderedChildren.add(mQsFrame); mLayoutDrawingOrder.add(mQsFrame); } - - if (mHasViewsAboveShelf) { - // StackScroller needs to be on top - mDrawingOrderedChildren.remove(mStackScroller); + if (mStackScroller.getVisibility() == View.VISIBLE) { mDrawingOrderedChildren.add(mStackScroller); + mLayoutDrawingOrder.add(mStackScroller); } - // Let's now find the order that the view has when drawing regulary by sorting + // Let's now find the order that the view has when drawing regularly by sorting mLayoutDrawingOrder.sort(mIndexComparator); super.dispatchDraw(canvas); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index b6ed3e50ed7e..3ac69378d7d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -167,6 +167,7 @@ public abstract class PanelViewController { private boolean mIgnoreXTouchSlop; private boolean mExpandLatencyTracking; private final PanelView mView; + private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; protected final Resources mResources; protected final KeyguardStateController mKeyguardStateController; protected final SysuiStatusBarStateController mStatusBarStateController; @@ -235,12 +236,14 @@ public abstract class PanelViewController { FalsingManager falsingManager, DozeLog dozeLog, KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper, + StatusBarKeyguardViewManager statusBarKeyguardViewManager, LatencyTracker latencyTracker, FlingAnimationUtils.Builder flingAnimationUtilsBuilder, StatusBarTouchableRegionManager statusBarTouchableRegionManager, AmbientState ambientState) { mAmbientState = ambientState; mView = view; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { @@ -1391,8 +1394,13 @@ public abstract class PanelViewController { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - addMovement(event); - endMotionEvent(event, x, y, false /* forceCancel */); + if (mStatusBarKeyguardViewManager.isBouncerShowing() + && mFalsingManager.isFalseTap(true, 0.5)) { + endMotionEvent(event, x, y, true /* forceCancel */); + } else { + addMovement(event); + endMotionEvent(event, x, y, false /* forceCancel */); + } InteractionJankMonitor monitor = InteractionJankMonitor.getInstance(); if (event.getActionMasked() == MotionEvent.ACTION_UP) { monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index d53724159244..f1405dea1294 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -675,8 +675,7 @@ public class PhoneStatusBarPolicy mIconController.setIconVisibility(mSlotCamera, showCamera); mIconController.setIconVisibility(mSlotMicrophone, showMicrophone); - if (mPrivacyItemController.getAllIndicatorsAvailable() - || mPrivacyItemController.getLocationAvailable()) { + if (mPrivacyItemController.getLocationAvailable()) { mIconController.setIconVisibility(mSlotLocation, showLocation); } mPrivacyLogger.logStatusBarIconsVisible(showCamera, showMicrophone, showLocation); @@ -684,8 +683,7 @@ public class PhoneStatusBarPolicy @Override public void onLocationActiveChanged(boolean active) { - if (!mPrivacyItemController.getAllIndicatorsAvailable() - && !mPrivacyItemController.getLocationAvailable()) { + if (!mPrivacyItemController.getLocationAvailable()) { updateLocationFromController(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 48c97a2fd79c..9b8b7160c95c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -140,11 +140,13 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn(); + final boolean quickPickupEnabled = mDozeParameters.isQuickPickupEnabled(); final boolean isDocked = mDockManager.isDocked(); mBlankScreen = mDisplayRequiresBlanking; mFrontTint = Color.BLACK; - mFrontAlpha = (alwaysOnEnabled || isDocked) ? mAodFrontScrimAlpha : 1f; + mFrontAlpha = (alwaysOnEnabled || isDocked || quickPickupEnabled) + ? mAodFrontScrimAlpha : 1f; mBehindTint = Color.BLACK; mBehindAlpha = ScrimController.TRANSPARENT; 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 0807f8aa5607..117921dd860c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -95,6 +95,7 @@ import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; +import android.text.TextUtils; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; @@ -4071,7 +4072,9 @@ public class StatusBar extends SystemUI implements DemoMode, private @Nullable Intent getEmergencyActionIntent() { Intent emergencyIntent = new Intent(EmergencyGesture.ACTION_LAUNCH_EMERGENCY); PackageManager pm = mContext.getPackageManager(); - ResolveInfo resolveInfo = pm.resolveActivity(emergencyIntent, /*flags=*/0); + List<ResolveInfo> emergencyActivities = pm.queryIntentActivities(emergencyIntent, + PackageManager.MATCH_SYSTEM_ONLY); + ResolveInfo resolveInfo = getTopEmergencySosInfo(emergencyActivities); if (resolveInfo == null) { Log.wtf(TAG, "Couldn't find an app to process the emergency intent."); return null; @@ -4082,6 +4085,34 @@ public class StatusBar extends SystemUI implements DemoMode, return emergencyIntent; } + /** + * Select and return the "best" ResolveInfo for Emergency SOS Activity. + */ + private @Nullable ResolveInfo getTopEmergencySosInfo(List<ResolveInfo> emergencyActivities) { + // No matched activity. + if (emergencyActivities == null || emergencyActivities.isEmpty()) { + return null; + } + + // Of multiple matched Activities, give preference to the pre-set package name. + String preferredAppPackageName = + mContext.getString(R.string.config_preferredEmergencySosPackage); + + // If there is no preferred app, then return first match. + if (TextUtils.isEmpty(preferredAppPackageName)) { + return emergencyActivities.get(0); + } + + for (ResolveInfo emergencyInfo: emergencyActivities) { + // If activity is from the preferred app, use it. + if (TextUtils.equals(emergencyInfo.activityInfo.packageName, preferredAppPackageName)) { + return emergencyInfo; + } + } + // No matching activity: return first match + return emergencyActivities.get(0); + } + boolean isCameraAllowedByAdmin() { if (mDevicePolicyManager.getCameraDisabled(null, mLockscreenUserManager.getCurrentUserId())) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 5083e330f9a0..01ada0f4c86c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -76,7 +76,7 @@ import javax.inject.Inject; * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done, * which is in turn, reported to this class by the current - * {@link com.android.keyguard.KeyguardViewBase}. + * {@link com.android.keyguard.KeyguardViewController}. */ @SysUISingleton public class StatusBarKeyguardViewManager implements RemoteInputController.Callback, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 34673f2503ce..801ac964777b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -356,9 +356,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit if (isActivityIntent || canBubble) { mAssistManagerLazy.get().hideAssist(); } - if (shouldCollapse()) { - collapseOnMainThread(); - } NotificationVisibility.NotificationLocation location = NotificationLogger.getNotificationLocation(entry); @@ -408,6 +405,12 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mMainThreadHandler.post( () -> mBubblesManagerOptional.get().expandStackAndSelectBubble(entry)); } + + // expandStackAndSelectBubble won't affect shouldCollapse, so we can collapse directly even + // if we are not on the main thread. + if (shouldCollapse()) { + collapseOnMainThread(); + } } private void startNotificationIntent( @@ -438,6 +441,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit null, null, options); mMainThreadHandler.post(() -> { mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent); + if (shouldCollapse()) { + collapseOnMainThread(); + } }); } catch (RemoteException | PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. @@ -465,11 +471,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mActivityLaunchAnimator.setLaunchResult(launchResult, true /* isActivityIntent */); removeHUN(row); + if (shouldCollapse()) { + mCommandQueue.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, + true /* force */); + } }); - if (shouldCollapse()) { - mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */)); - } }); return true; }, null, false /* afterKeyguardGone */); diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java index ba58ed282786..5cd3e5787a49 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java @@ -52,6 +52,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -408,14 +409,15 @@ public class GarbageMonitor implements Dumpable { QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, GarbageMonitor monitor ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); gm = monitor; } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index a12326961b08..1d18750f1fdf 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -81,7 +81,7 @@ import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.startingsurface.StartingWindowController; -import com.android.wm.shell.transition.RemoteTransitions; +import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; import java.util.Optional; @@ -399,8 +399,8 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static RemoteTransitions provideRemoteTransitions(Transitions transitions) { - return Transitions.asRemoteTransitions(transitions); + static ShellTransitions provideRemoteTransitions(Transitions transitions) { + return transitions.asRemoteTransitions(); } @WMSingleton @@ -480,8 +480,8 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static StartingWindowController provideStartingWindowController(Context context, - @ShellSplashscreenThread ShellExecutor executor) { - return new StartingWindowController(context, executor); + @ShellAnimationThread ShellExecutor executor, TransactionPool pool) { + return new StartingWindowController(context, executor, pool); } // @@ -509,27 +509,33 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static ShellInit provideShellInit(DisplayImeController displayImeController, + static ShellInit provideShellInit(ShellInitImpl impl) { + return impl.asShellInit(); + } + + @WMSingleton + @Provides + static ShellInitImpl provideShellInitImpl(DisplayImeController displayImeController, DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, - Optional<StartingSurface> startingSurface, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, + StartingWindowController startingWindow, @ShellMainThread ShellExecutor mainExecutor) { - return ShellInitImpl.create(displayImeController, + return new ShellInitImpl(displayImeController, dragAndDropController, shellTaskOrganizer, legacySplitScreenOptional, splitScreenOptional, appPairsOptional, - startingSurface, pipTouchHandlerOptional, fullscreenTaskListener, transitions, + startingWindow, mainExecutor); } @@ -539,7 +545,13 @@ public abstract class WMShellBaseModule { */ @WMSingleton @Provides - static Optional<ShellCommandHandler> provideShellCommandHandler( + static Optional<ShellCommandHandler> provideShellCommandHandler(ShellCommandHandlerImpl impl) { + return Optional.of(impl.asShellCommandHandler()); + } + + @WMSingleton + @Provides + static ShellCommandHandlerImpl provideShellCommandHandlerImpl( ShellTaskOrganizer shellTaskOrganizer, Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, @@ -548,8 +560,8 @@ public abstract class WMShellBaseModule { Optional<HideDisplayCutoutController> hideDisplayCutout, Optional<AppPairsController> appPairsOptional, @ShellMainThread ShellExecutor mainExecutor) { - return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer, + return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional, - hideDisplayCutout, appPairsOptional, mainExecutor)); + hideDisplayCutout, appPairsOptional, mainExecutor); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index c2ade81a9877..0cf343c2d3c4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -36,6 +36,8 @@ import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.classifier.FalsingCollectorFake; import org.junit.Before; import org.junit.Test; @@ -69,6 +71,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { private KeyguardMessageAreaController mKeyguardMessageAreaController; @Mock private LatencyTracker mLatencyTracker; + private final FalsingCollector mFalsingCollector = new FalsingCollectorFake(); private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController; @@ -84,7 +87,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { .thenReturn(mKeyguardMessageArea); mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, - mKeyguardMessageAreaControllerFactory, mLatencyTracker) { + mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector) { @Override void resetState() { } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index c69ec1a254c3..fc93dedc4e8e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -24,6 +24,8 @@ import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.classifier.FalsingCollectorFake import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -48,6 +50,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback @Mock private lateinit var mLatencyTracker: LatencyTracker + private var mFalsingCollector: FalsingCollector = FalsingCollectorFake() @Mock private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory @@ -72,7 +75,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { .thenReturn(mKeyguardMessageAreaController) mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, - mLatencyTracker, mKeyguardMessageAreaControllerFactory) + mLatencyTracker, mFalsingCollector, mKeyguardMessageAreaControllerFactory) } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index 31cc7bb7c958..33a0dcd048ae 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -33,6 +33,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollectorFake; +import com.android.systemui.classifier.SingleTapClassifier; import org.junit.Before; import org.junit.Test; @@ -69,9 +70,12 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { private LiftToActivateListener mLiftToactivateListener; private FalsingCollector mFalsingCollector = new FalsingCollectorFake(); @Mock + private SingleTapClassifier mSingleTapClassifier; + @Mock private View mDeleteButton; @Mock private View mOkButton; + private NumPadKey[] mButtons = new NumPadKey[]{}; private KeyguardPinBasedInputViewController mKeyguardPinViewController; @@ -83,6 +87,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { when(mPinBasedInputView.getPasswordTextViewId()).thenReturn(1); when(mPinBasedInputView.findViewById(1)).thenReturn(mPasswordEntry); when(mPinBasedInputView.isAttachedToWindow()).thenReturn(true); + when(mPinBasedInputView.getButtons()).thenReturn(mButtons); when(mPinBasedInputView.findViewById(R.id.keyguard_message_area)) .thenReturn(mKeyguardMessageArea); when(mPinBasedInputView.findViewById(R.id.delete_button)) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index b03dc94fde33..49ba646420a3 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -34,6 +34,8 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -83,6 +85,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController; @Mock private ConfigurationController mConfigurationController; + @Mock + private KeyguardViewController mKeyguardViewController; + private FalsingManager mFalsingManager = new FalsingManagerFake(); private KeyguardSecurityContainerController mKeyguardSecurityContainerController; @@ -96,7 +101,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, mKeyguardStateController, mKeyguardSecurityViewFlipperController, - mConfigurationController).create(mSecurityCallback); + mConfigurationController, mKeyguardViewController, mFalsingManager) + .create(mSecurityCallback); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 07686181649d..d6f4958942dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -246,20 +246,4 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN the illumination is hidden verify(mUdfpsView).stopIllumination(); } - - @Test - public void registersAndUnregistersViewForCallbacks() throws RemoteException { - mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - verify(mStatusBarStateController).addCallback(mUdfpsController.mStatusBarStateListener); - verify(mStatusBar).addExpansionChangedListener( - mUdfpsController.mStatusBarExpansionListener); - - mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID); - mFgExecutor.runAllReady(); - verify(mStatusBarStateController).removeCallback(mUdfpsController.mStatusBarStateListener); - verify(mStatusBar).removeExpansionChangedListener( - mUdfpsController.mStatusBarExpansionListener); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java new file mode 100644 index 000000000000..480b33556b27 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -0,0 +1,138 @@ +/* + * 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.biometrics; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.phone.StatusBar; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { + // Dependencies + @Mock + private UdfpsKeyguardView mView; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private StatusBar mStatusBar; + + private UdfpsKeyguardViewController mController; + + // Capture listeners so that they can be used to send events + @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor; + private StatusBarStateController.StateListener mParentListener; + private StatusBarStateController.StateListener mDozeListener; + + @Captor private ArgumentCaptor<StatusBar.ExpansionChangedListener> mExpansionListenerCaptor; + private StatusBar.ExpansionChangedListener mExpansionListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mController = new UdfpsKeyguardViewController( + mView, + mStatusBarStateController, + mStatusBar); + } + + @Test + public void testRegistersExpansionChangedListenerOnAttached() { + mController.onViewAttached(); + captureExpansionListener(); + } + + @Test + public void testRegistersStatusBarStateListenersOnAttached() { + mController.onViewAttached(); + captureStatusBarStateListeners(); + } + + @Test + public void testViewControllerQueriesSBStateOnAttached() { + mController.onViewAttached(); + verify(mStatusBarStateController).getState(); + verify(mStatusBarStateController).getDozeAmount(); + + final float dozeAmount = .88f; + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); + when(mStatusBarStateController.getDozeAmount()).thenReturn(dozeAmount); + captureStatusBarStateListeners(); + + mController.onViewAttached(); + verify(mView).setPauseAuth(true); + verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount); + } + + @Test + public void testListenersUnregisteredOnDetached() { + mController.onViewAttached(); + captureStatusBarStateListeners(); + captureExpansionListener(); + mController.onViewDetached(); + + verify(mStatusBarStateController).removeCallback(mParentListener); + verify(mStatusBarStateController).removeCallback(mDozeListener); + verify(mStatusBar).removeExpansionChangedListener(mExpansionListener); + } + + @Test + public void testDozeEventsSentToView() { + mController.onViewAttached(); + captureStatusBarStateListeners(); + + final float linear = .55f; + final float eased = .65f; + mDozeListener.onDozeAmountChanged(linear, eased); + + verify(mView).onDozeAmountChanged(linear, eased); + } + + private void captureStatusBarStateListeners() { + verify(mStatusBarStateController, times(2)).addCallback(mStateListenerCaptor.capture()); + List<StatusBarStateController.StateListener> stateListeners = + mStateListenerCaptor.getAllValues(); + mParentListener = stateListeners.get(0); + mDozeListener = stateListeners.get(1); + } + + private void captureExpansionListener() { + verify(mStatusBar).addExpansionChangedListener(mExpansionListenerCaptor.capture()); + mExpansionListener = mExpansionListenerCaptor.getValue(); + } +} 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 5709ce3035a2..b2328504272a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -21,10 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.ArgumentMatchers.anyDouble; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -93,7 +90,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mDockManager, mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier, - mHistoryTracker, mFakeExecutor, DOUBLE_TAP_TIMEOUT_MS, false); + mHistoryTracker, false); ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor = @@ -161,25 +158,27 @@ public class BrightLineClassifierTest extends SysuiTestCase { public void testIsFalseTap_BasicCheck() { when(mSingleTapClassfier.isTap(mMotionEventList)).thenReturn(mFalsedResult); - assertThat(mBrightLineFalsingManager.isFalseTap(false)).isTrue(); + assertThat(mBrightLineFalsingManager.isFalseTap(false, 0)).isTrue(); when(mSingleTapClassfier.isTap(mMotionEventList)).thenReturn(mPassedResult); - assertThat(mBrightLineFalsingManager.isFalseTap(false)).isFalse(); + assertThat(mBrightLineFalsingManager.isFalseTap(false, 0)).isFalse(); } @Test public void testIsFalseTap_RobustCheck_NoFaceAuth() { when(mSingleTapClassfier.isTap(mMotionEventList)).thenReturn(mPassedResult); + when(mDoubleTapClassifier.classifyGesture()).thenReturn(mFalsedResult); + when(mHistoryTracker.falseBelief()).thenReturn(1.0); mFalsingDataProvider.setJustUnlockedWithFace(false); - assertThat(mBrightLineFalsingManager.isFalseTap(true)).isTrue(); + assertThat(mBrightLineFalsingManager.isFalseTap(true, 0)).isTrue(); } @Test public void testIsFalseTap_RobustCheck_FaceAuth() { when(mSingleTapClassfier.isTap(mMotionEventList)).thenReturn(mPassedResult); when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true); - assertThat(mBrightLineFalsingManager.isFalseTap(true)).isFalse(); + assertThat(mBrightLineFalsingManager.isFalseTap(true, 0)).isFalse(); } @Test @@ -203,43 +202,29 @@ public class BrightLineClassifierTest extends SysuiTestCase { @Test public void testHistory_singleTap() { // When trying to classify single taps, we don't immediately add results to history. - mBrightLineFalsingManager.isFalseTap(false); + mBrightLineFalsingManager.isFalseTap(false, 0); mGestureCompleteListener.onGestureComplete(1000); - - verify(mHistoryTracker, never()).addResults(any(), anyLong()); - - mFakeExecutor.advanceClockToNext(); - mFakeExecutor.runAllReady(); - verify(mHistoryTracker).addResults(anyCollection(), eq(1000L)); } @Test public void testHistory_multipleSingleTaps() { // When trying to classify single taps, we don't immediately add results to history. - mBrightLineFalsingManager.isFalseTap(false); + mBrightLineFalsingManager.isFalseTap(false, 0); mGestureCompleteListener.onGestureComplete(1000); - mBrightLineFalsingManager.isFalseTap(false); + mBrightLineFalsingManager.isFalseTap(false, 0); mGestureCompleteListener.onGestureComplete(2000); - - verify(mHistoryTracker, never()).addResults(any(), anyLong()); - - mFakeExecutor.advanceClockToNext(); - mFakeExecutor.runNextReady(); verify(mHistoryTracker).addResults(anyCollection(), eq(1000L)); - reset(mHistoryTracker); - mFakeExecutor.advanceClockToNext(); - mFakeExecutor.runNextReady(); verify(mHistoryTracker).addResults(anyCollection(), eq(2000L)); } @Test public void testHistory_doubleTap() { // When trying to classify single taps, we don't immediately add results to history. - mBrightLineFalsingManager.isFalseTap(false); + mBrightLineFalsingManager.isFalseTap(false, 0); mGestureCompleteListener.onGestureComplete(1000); // Before checking for double tap, we may check for single-tap on the second gesture. - mBrightLineFalsingManager.isFalseTap(false); + mBrightLineFalsingManager.isFalseTap(false, 0); mBrightLineFalsingManager.isFalseDoubleTap(); mGestureCompleteListener.onGestureComplete(2000); diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java index 23ef865750b2..dc79b8881891 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java @@ -36,6 +36,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.sensors.ThresholdSensor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -56,6 +57,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock + private HistoryTracker mHistoryTracker; + @Mock private ProximitySensor mProximitySensor; @Mock private SysuiStatusBarStateController mStatusBarStateController; @@ -67,7 +70,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager, - mKeyguardUpdateMonitor, mProximitySensor, mStatusBarStateController); + mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor, + mStatusBarStateController, new FakeSystemClock()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java index 01cce3579b0c..bb7545f93b4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java @@ -48,14 +48,14 @@ public class HistoryTrackerTest extends SysuiTestCase { @Test public void testNoDataNoPenalty() { - assertThat(mHistoryTracker.falsePenalty()).isEqualTo(0); + assertThat(mHistoryTracker.falseBelief()).isEqualTo(0.5); assertThat(mHistoryTracker.falseConfidence()).isEqualTo(0); } @Test public void testOneResultFullConfidence() { addResult(true, 1); - assertThat(mHistoryTracker.falsePenalty()).isEqualTo(1); + assertThat(mHistoryTracker.falseBelief()).isWithin(0.001).of(1); assertThat(mHistoryTracker.falseConfidence()).isEqualTo(1); } @@ -64,8 +64,8 @@ public class HistoryTrackerTest extends SysuiTestCase { addResult(true, 1); addResult(false, 1); - assertThat(mHistoryTracker.falsePenalty()).isEqualTo(0.5); - assertThat(mHistoryTracker.falseConfidence()).isEqualTo(0.5); + assertThat(mHistoryTracker.falseBelief()).isWithin(0.001).of(0.5); + assertThat(mHistoryTracker.falseConfidence()).isWithin(0.001).of(0.5); } @Test @@ -73,20 +73,20 @@ public class HistoryTrackerTest extends SysuiTestCase { addResult(true, 1); addResult(true, 0); - assertThat(mHistoryTracker.falsePenalty()).isEqualTo(0.75); - assertThat(mHistoryTracker.falseConfidence()).isEqualTo(.75); + assertThat(mHistoryTracker.falseBelief()).isWithin(0.001).of(1); + assertThat(mHistoryTracker.falseConfidence()).isWithin(0.001).of(.75); } @Test public void testDecay() { addResult(true, 1); - assertThat(mHistoryTracker.falsePenalty()).isEqualTo(1); + assertThat(mHistoryTracker.falseBelief()).isWithin(0.001).of(1); assertThat(mHistoryTracker.falseConfidence()).isEqualTo(1); - mSystemClock.advanceTime(1000); + mSystemClock.advanceTime(9999); - assertThat(mHistoryTracker.falsePenalty()).isWithin(0.01).of(0.1); + assertThat(mHistoryTracker.falseBelief()).isWithin(0.005).of(0.55); assertThat(mHistoryTracker.falseConfidence()).isEqualTo(1); } @@ -96,25 +96,25 @@ public class HistoryTrackerTest extends SysuiTestCase { mSystemClock.advanceTime(1000); addResult(false, .5); - assertThat(mHistoryTracker.falsePenalty()).isWithin(0.01).of(0.17); - assertThat(mHistoryTracker.falseConfidence()).isEqualTo(0.625); + assertThat(mHistoryTracker.falseBelief()).isWithin(0.01).of(0.74); + assertThat(mHistoryTracker.falseConfidence()).isWithin(0.001).of(0.625); } @Test public void testCompleteDecay() { addResult(true, 1); - assertThat(mHistoryTracker.falsePenalty()).isEqualTo(1); + assertThat(mHistoryTracker.falseBelief()).isWithin(0.001).of(1); assertThat(mHistoryTracker.falseConfidence()).isEqualTo(1); - mSystemClock.advanceTime(2999); + mSystemClock.advanceTime(9999); - assertThat(mHistoryTracker.falsePenalty()).isGreaterThan(0); + assertThat(mHistoryTracker.falseBelief()).isGreaterThan(0); assertThat(mHistoryTracker.falseConfidence()).isEqualTo(1); mSystemClock.advanceTime(1); - assertThat(mHistoryTracker.falsePenalty()).isEqualTo(0); + assertThat(mHistoryTracker.falseBelief()).isEqualTo(0.5); assertThat(mHistoryTracker.falseConfidence()).isEqualTo(0); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java index 9fd9b470a83b..1aeb0d8a5361 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -60,6 +60,7 @@ public class DozeConfigurationUtil { when(config.tapSensorType()).thenReturn(null); when(config.longPressSensorType()).thenReturn(null); when(config.udfpsLongPressSensorType()).thenReturn(null); + when(config.quickPickupSensorType()).thenReturn(null); when(config.tapGestureEnabled(anyInt())).thenReturn(true); when(config.tapSensorAvailable()).thenReturn(true); @@ -67,6 +68,7 @@ public class DozeConfigurationUtil { when(config.dozePickupSensorAvailable()).thenReturn(false); when(config.wakeScreenGestureAvailable()).thenReturn(false); + when(config.quickPickupSensorEnabled(anyInt())).thenReturn(false); doneHolder[0] = true; return config; diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index 27187a85c040..1817fdfd4cdc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -104,7 +104,7 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers = new DozeTriggers(mContext, mHost, mAlarmManager, config, parameters, asyncSensorManager, wakeLock, mDockManager, mProximitySensor, mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(), - mAuthController); + mAuthController, mExecutor, mExecutor); mTriggers.setDozeMachine(mMachine); waitForSensorManager(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt index 1f9862c07a4c..3d4425cf4bd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt @@ -35,6 +35,7 @@ import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -49,7 +50,7 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class SeekBarViewModelTest : SysuiTestCase() { private lateinit var viewModel: SeekBarViewModel @@ -124,6 +125,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + @Ignore fun updateDurationWithPlayback() { // GIVEN that the duration is contained within the metadata val duration = 12000L @@ -146,6 +148,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + @Ignore fun updateDurationWithoutPlayback() { // GIVEN that the duration is contained within the metadata val duration = 12000L @@ -204,6 +207,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + @Ignore fun updateDurationNoMetadata() { // GIVEN that the metadata is null whenever(mockController.getMetadata()).thenReturn(null) @@ -235,6 +239,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + @Ignore fun updateSeekAvailable() { // GIVEN that seek is included in actions val state = PlaybackState.Builder().run { @@ -249,6 +254,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + @Ignore fun updateSeekNotAvailable() { // GIVEN that seek is not included in actions val state = PlaybackState.Builder().run { @@ -303,6 +309,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + @Ignore fun onSeekProgressWithSeekStarting() { val pos = 42L with(viewModel) { @@ -314,6 +321,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + @Ignore fun onProgressChangedFromUser() { // WHEN user starts dragging the seek bar val pos = 42 @@ -614,6 +622,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + @Ignore fun clearSeekBar() { // GIVEN that the duration is contained within the metadata val metadata = MediaMetadata.Builder().run { diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java index 2a4b41cbfe32..3fed07472c35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java @@ -22,8 +22,9 @@ import static android.app.people.ConversationStatus.ACTIVITY_GAME; import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY; import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE; -import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE; import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME; +import static com.android.systemui.people.PeopleSpaceUtils.getPeopleTileFromPersistentStorage; +import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE; import static com.google.common.truth.Truth.assertThat; @@ -76,6 +77,7 @@ import android.widget.TextView; import com.android.internal.appwidget.IAppWidgetService; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.people.widget.PeopleTileKey; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -232,7 +234,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; Bundle options = new Bundle(); - options.putParcelable(OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE); + options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE); when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT))) @@ -500,7 +502,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { .build(); PeopleSpaceTile actual = PeopleSpaceUtils .augmentTileFromVisibleNotifications(mContext, tile, - Map.of(PeopleSpaceUtils.getKey(mNotificationEntry1), mNotificationEntry1)); + Map.of(new PeopleTileKey(mNotificationEntry1), mNotificationEntry1)); assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2); } @@ -515,7 +517,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { .build(); PeopleSpaceTile actual = PeopleSpaceUtils .augmentTileFromVisibleNotifications(mContext, tile, - Map.of(PeopleSpaceUtils.getKey(mNotificationEntry1), mNotificationEntry1)); + Map.of(new PeopleTileKey(mNotificationEntry1), mNotificationEntry1)); assertThat(actual.getNotificationContent()).isEqualTo(null); } @@ -818,6 +820,23 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { assertEquals(statusContent.getText(), NOTIFICATION_CONTENT); } + @Test + public void testGetPeopleTileFromPersistentStorageExistingConversation() + throws Exception { + when(mPeopleManager.getConversation(PACKAGE_NAME, 0, SHORTCUT_ID_1)).thenReturn( + getConversationChannelWithoutTimestamp(SHORTCUT_ID_1)); + PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID_1, 0, PACKAGE_NAME); + PeopleSpaceTile tile = getPeopleTileFromPersistentStorage(mContext, key, mPeopleManager); + assertThat(tile.getId()).isEqualTo(key.getShortcutId()); + } + + @Test + public void testGetPeopleTileFromPersistentStorageNoConversation() { + PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID_2, 0, PACKAGE_NAME); + PeopleSpaceTile tile = getPeopleTileFromPersistentStorage(mContext, key, mPeopleManager); + assertThat(tile).isNull(); + } + private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId, boolean importantConversation, long lastInteractionTimestamp) throws Exception { ConversationChannelWrapper convo = new ConversationChannelWrapper(); @@ -843,4 +862,13 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { eq(shortcutId))).thenReturn(lastInteractionTimestamp); return convo; } + + private ConversationChannel getConversationChannelWithoutTimestamp(String shortcutId) + throws Exception { + ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel( + "name").build(); + ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null, + 0L, false); + return convo; + } }
\ No newline at end of file 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 f60fa099feaa..aef75beb3d56 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 @@ -23,10 +23,11 @@ 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 com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING; import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID; -import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE; import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME; import static com.android.systemui.people.PeopleSpaceUtils.USER_ID; +import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE; import static com.google.common.truth.Truth.assertThat; @@ -104,13 +105,14 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private static final int WIDGET_ID_WITH_SHORTCUT = 1; 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 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"; private static final String NOTIFICATION_CONTENT = "message text"; private static final Uri URI = Uri.parse("fake_uri"); private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android); - private static final String KEY = PeopleSpaceUtils.getKey(SHORTCUT_ID, TEST_PACKAGE_A, 0); + private static final PeopleTileKey KEY = new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A); private static final Person PERSON = new Person.Builder() .setName("name") .setKey("abc") @@ -172,10 +174,11 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); + clearStorage(); setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); Bundle options = new Bundle(); - options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE); + options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE); when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT))) .thenReturn(options); when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT))) @@ -395,7 +398,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), mBundleArgumentCaptor.capture()); Bundle bundle = mBundleArgumentCaptor.getValue(); - PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); + PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE); assertThat(tile.getStatuses()).containsExactly(status); verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), any()); @@ -439,7 +442,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), mBundleArgumentCaptor.capture()); Bundle bundle = mBundleArgumentCaptor.getValue(); - PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); + PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE); assertThat(tile.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); assertThat(tile.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT); verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), @@ -473,7 +476,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { throws Exception { addSecondWidgetForPersonTile(); - PeopleSpaceUtils.removeStorageForTile(mContext, KEY, SECOND_WIDGET_ID_WITH_SHORTCUT); + PeopleSpaceUtils.removeSharedPreferencesStorageForTile( + mContext, KEY, SECOND_WIDGET_ID_WITH_SHORTCUT); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(createNotification( SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false)) @@ -510,7 +514,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { mBundleArgumentCaptor.capture()); Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue()); - PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); + PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE); assertThat(tile.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); assertThat(tile.getNotificationContent()) .isEqualTo(mContext.getString(R.string.missed_call)); @@ -536,7 +540,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { mBundleArgumentCaptor.capture()); Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue()); - PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); + PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE); assertThat(tile.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); assertThat(tile.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT); verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), @@ -547,6 +551,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { 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); @@ -560,11 +565,11 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), mBundleArgumentCaptor.capture()); Bundle bundle = mBundleArgumentCaptor.getValue(); - PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); + PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE); assertThat(tile.getNotificationKey()).isEqualTo(null); assertThat(tile.getNotificationContent()).isEqualTo(null); assertThat(tile.getNotificationDataUri()).isEqualTo(null); - verify(mAppWidgetManager, times(2)).updateAppWidget(anyInt(), + verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), any()); } @@ -585,7 +590,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { assertThat(widgetSp.getString(SHORTCUT_ID, null)).isNull(); assertThat(widgetSp.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(INVALID_USER_ID); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - assertThat(sp.getStringSet(KEY, new HashSet<>())).containsExactly( + assertThat(sp.getStringSet(KEY.toString(), new HashSet<>())).containsExactly( String.valueOf(SECOND_WIDGET_ID_WITH_SHORTCUT)); // Check listener & shortcut caching remain for other widget. verify(mPeopleManager, never()).unregisterConversationListener(any()); @@ -603,7 +608,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { assertThat(secondWidgetSp.getString(PACKAGE_NAME, null)).isNull(); assertThat(secondWidgetSp.getString(SHORTCUT_ID, null)).isNull(); assertThat(secondWidgetSp.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(INVALID_USER_ID); - assertThat(sp.getStringSet(KEY, new HashSet<>())).isEmpty(); + assertThat(sp.getStringSet(KEY.toString(), new HashSet<>())).isEmpty(); // Check listener is removed and shortcut is uncached. verify(mPeopleManager, times(1)).unregisterConversationListener(any()); verify(mLauncherApps, times(1)).uncacheShortcuts(eq(TEST_PACKAGE_A), @@ -611,13 +616,96 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { eq(LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS)); } + @Test + public void testUpdateWidgetsWithEmptyOptionsAddsPeopleTileToOptions() throws Exception { + int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT))) + .thenReturn(new Bundle()); + + mManager.updateWidgets(widgetIdsArray); + mClock.advanceTime(MIN_LINGER_DURATION); + + // If we had to fetch Tile from persistent storage, we want to make sure we write it to + // options. + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = mBundleArgumentCaptor.getValue(); + PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tile.getId()).isEqualTo(SHORTCUT_ID); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + } + + @Test + public void testOnAppWidgetOptionsChangedNoWidgetAdded() { + Bundle newOptions = new Bundle(); + newOptions.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE); + mManager.onAppWidgetOptionsChanged(SECOND_WIDGET_ID_WITH_SHORTCUT, newOptions); + + + // Check that options is not modified + verify(mAppWidgetManager, never()).updateAppWidgetOptions( + eq(SECOND_WIDGET_ID_WITH_SHORTCUT), any()); + // Check listener is not added and shortcut is not cached. + verify(mPeopleManager, never()).registerConversationListener(any(), anyInt(), any(), any(), + any()); + verify(mLauncherApps, never()).cacheShortcuts(any(), any(), any(), anyInt()); + // Check no added storage. + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); + assertThat(sp.getStringSet(KEY.toString(), new HashSet<>())) + .doesNotContain(SECOND_WIDGET_ID_WITH_SHORTCUT); + SharedPreferences widgetSp = mContext.getSharedPreferences( + String.valueOf(SECOND_WIDGET_ID_WITH_SHORTCUT), + Context.MODE_PRIVATE); + assertThat(widgetSp.getString(PACKAGE_NAME, EMPTY_STRING)).isEqualTo(EMPTY_STRING); + assertThat(widgetSp.getString(SHORTCUT_ID, EMPTY_STRING)).isEqualTo(EMPTY_STRING); + assertThat(widgetSp.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(INVALID_USER_ID); + + } + + @Test + public void testOnAppWidgetOptionsChangedWidgetAdded() { + Bundle newOptions = new Bundle(); + newOptions.putString(PeopleSpaceUtils.SHORTCUT_ID, SHORTCUT_ID); + newOptions.putInt(USER_ID, 0); + newOptions.putString(PACKAGE_NAME, TEST_PACKAGE_A); + when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT))) + .thenReturn(newOptions); + + mManager.onAppWidgetOptionsChanged(SECOND_WIDGET_ID_WITH_SHORTCUT, newOptions); + + verify(mAppWidgetManager, times(1)).updateAppWidgetOptions( + eq(SECOND_WIDGET_ID_WITH_SHORTCUT), mBundleArgumentCaptor.capture()); + Bundle bundle = mBundleArgumentCaptor.getValue(); + assertThat(bundle.getString(PeopleSpaceUtils.SHORTCUT_ID, EMPTY_STRING)) + .isEqualTo(EMPTY_STRING); + assertThat(bundle.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(INVALID_USER_ID); + assertThat(bundle.getString(PACKAGE_NAME, EMPTY_STRING)).isEqualTo(EMPTY_STRING); + verify(mLauncherApps, times(1)).cacheShortcuts(eq(TEST_PACKAGE_A), + eq(Arrays.asList(SHORTCUT_ID)), eq(UserHandle.of(0)), + eq(LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS)); + + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); + assertThat(sp.getStringSet(KEY.toString(), new HashSet<>())).contains( + String.valueOf(SECOND_WIDGET_ID_WITH_SHORTCUT)); + SharedPreferences widgetSp = mContext.getSharedPreferences( + String.valueOf(SECOND_WIDGET_ID_WITH_SHORTCUT), + Context.MODE_PRIVATE); + assertThat(widgetSp.getString(PACKAGE_NAME, EMPTY_STRING)).isEqualTo(TEST_PACKAGE_A); + assertThat(widgetSp.getString(PeopleSpaceUtils.SHORTCUT_ID, EMPTY_STRING)) + .isEqualTo(SHORTCUT_ID); + assertThat(widgetSp.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(0); + } + /** * 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(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE); + options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE); when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT))) .thenReturn(options); // Set the same Person associated on another People Tile widget ID. @@ -676,6 +764,27 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { .build(); } + private void clearStorage() { + SharedPreferences widgetSp1 = mContext.getSharedPreferences( + String.valueOf(WIDGET_ID_WITH_SHORTCUT), + Context.MODE_PRIVATE); + widgetSp1.edit().clear().commit(); + SharedPreferences widgetSp2 = mContext.getSharedPreferences( + String.valueOf(WIDGET_ID_WITHOUT_SHORTCUT), + Context.MODE_PRIVATE); + widgetSp2.edit().clear().commit(); + SharedPreferences widgetSp3 = mContext.getSharedPreferences( + String.valueOf(SECOND_WIDGET_ID_WITH_SHORTCUT), + Context.MODE_PRIVATE); + widgetSp3.edit().clear().commit(); + SharedPreferences widgetSp4 = mContext.getSharedPreferences( + String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS), + Context.MODE_PRIVATE); + widgetSp4.edit().clear().commit(); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); + sp.edit().clear().commit(); + } + private void setStorageForTile(String shortcutId, String packageName, int widgetId) { SharedPreferences widgetSp = mContext.getSharedPreferences( String.valueOf(widgetId), @@ -689,7 +798,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); SharedPreferences.Editor editor = sp.edit(); editor.putString(String.valueOf(widgetId), shortcutId); - String key = PeopleSpaceUtils.getKey(shortcutId, packageName, 0); + 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); diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt index 072f7b8a7756..791dd121852f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt @@ -395,9 +395,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { `when`(permissionManager.indicatorAppOpUsageData).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) - `when`(privacyItemController.micCameraAvailable).thenReturn(false) - `when`(privacyItemController.locationAvailable).thenReturn(false) - `when`(privacyItemController.allIndicatorsAvailable).thenReturn(true) + `when`(privacyItemController.micCameraAvailable).thenReturn(true) + `when`(privacyItemController.locationAvailable).thenReturn(true) controller.showDialog(context) exhaustExecutors() @@ -422,7 +421,6 @@ class PrivacyDialogControllerTest : SysuiTestCase() { ) `when`(privacyItemController.micCameraAvailable).thenReturn(false) `when`(privacyItemController.locationAvailable).thenReturn(false) - `when`(privacyItemController.allIndicatorsAvailable).thenReturn(false) controller.showDialog(context) exhaustExecutors() @@ -525,7 +523,6 @@ class PrivacyDialogControllerTest : SysuiTestCase() { `when`(privacyItemController.locationAvailable).thenReturn(true) `when`(privacyItemController.micCameraAvailable).thenReturn(true) - `when`(privacyItemController.allIndicatorsAvailable).thenReturn(false) `when`(userTracker.userProfiles).thenReturn(listOf( UserInfo(USER_ID, "", 0), diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt index 132bee0e7fdf..f991e718122e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt @@ -37,7 +37,6 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.never import org.mockito.Mockito.verify @@ -51,8 +50,6 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { fun <T> eq(value: T): T = Mockito.eq(value) ?: value fun <T> any(): T = Mockito.any<T>() - private const val ALL_INDICATORS = - SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED private const val LOCATION = SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED } @@ -96,11 +93,6 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - fun testNotListeningAllByDefault() { - assertFalse(privacyItemController.allIndicatorsAvailable) - } - - @Test fun testMicCameraListeningByDefault() { assertTrue(privacyItemController.micCameraAvailable) } @@ -111,10 +103,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { executor.runAllReady() verify(callback).onFlagMicCameraChanged(false) - verify(callback, never()).onFlagAllChanged(anyBoolean()) assertFalse(privacyItemController.micCameraAvailable) - assertFalse(privacyItemController.allIndicatorsAvailable) } @Test @@ -127,26 +117,15 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - fun testAllChanged() { - changeAll(true) - executor.runAllReady() - - verify(callback).onFlagAllChanged(true) - verify(callback, never()).onFlagMicCameraChanged(anyBoolean()) - - assertTrue(privacyItemController.allIndicatorsAvailable) - } - - @Test fun testBothChanged() { changeAll(true) changeMicCamera(false) executor.runAllReady() - verify(callback, atLeastOnce()).onFlagAllChanged(true) + verify(callback, atLeastOnce()).onFlagLocationChanged(true) verify(callback, atLeastOnce()).onFlagMicCameraChanged(false) - assertTrue(privacyItemController.allIndicatorsAvailable) + assertTrue(privacyItemController.locationAvailable) assertFalse(privacyItemController.micCameraAvailable) } @@ -186,28 +165,6 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - fun testSomeListening_stillListening() { - // Mic and camera are true by default - changeAll(true) - executor.runAllReady() - changeAll(false) - executor.runAllReady() - - verify(appOpsController, never()).removeCallback(any(), any()) - } - - @Test - fun testAllDeleted_micCameraFalse_stopListening() { - changeMicCamera(false) - changeAll(true) - executor.runAllReady() - changeAll(null) - executor.runAllReady() - - verify(appOpsController).removeCallback(any(), any()) - } - - @Test fun testMicDeleted_stillListening() { changeMicCamera(true) executor.runAllReady() @@ -219,7 +176,10 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value) private fun changeLocation(value: Boolean?) = changeProperty(LOCATION, value) - private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value) + private fun changeAll(value: Boolean?) { + changeMicCamera(value) + changeLocation(value) + } private fun changeProperty(name: String, value: Boolean?) { deviceConfigProxy.setProperty( diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index 7ca468edfd9c..b87c7a6ad2d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -43,7 +43,6 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertThat import org.junit.Assert.assertTrue import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -72,8 +71,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE const val TEST_PACKAGE_NAME = "test" - private const val ALL_INDICATORS = - SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED + private const val LOCATION_INDICATOR = + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() fun <T> eq(value: T): T = Mockito.eq(value) ?: value @@ -119,7 +118,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { deviceConfigProxy = DeviceConfigProxyFake() // Listen to everything by default - changeAll(true) + changeMicCamera(true) + changeLocation(true) `when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(CURRENT_USER_ID, "", 0))) @@ -259,9 +259,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { } @Test - @Ignore // TODO(b/168209929) fun testNotListeningWhenIndicatorsDisabled() { - changeAll(false) + changeLocation(false) changeMicCamera(false) privacyItemController.addCallback(callback) executor.runAllReady() @@ -271,7 +270,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testNotSendingLocationWhenOnlyMicCamera() { - changeAll(false) + changeLocation(false) changeMicCamera(true) executor.runAllReady() @@ -294,7 +293,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) privacyItemController.addCallback(callback) - changeAll(false) + changeLocation(false) changeMicCamera(true) executor.runAllReady() reset(callback) // Clean callback @@ -521,7 +520,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { } private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value) - private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value) + private fun changeLocation(value: Boolean?) = changeProperty(LOCATION_INDICATOR, value) private fun changeProperty(name: String, value: Boolean?) { deviceConfigProxy.setProperty( diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index cfef5beb94e0..2ca8082f777a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -49,6 +49,7 @@ import com.android.internal.util.CollectionUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSFactory; @@ -360,6 +361,7 @@ public class QSTileHostTest extends SysuiTestCase { host, mLooper.getLooper(), new Handler(mLooper.getLooper()), + new FalsingManagerFake(), mock(MetricsLogger.class), mock(StatusBarStateController.class), mock(ActivityStarter.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index 97a845916185..4948c2b18746 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -148,7 +148,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @Test fun testIgnoredSlotsOnAttached_noIndicators() { - setPrivacyController(false, false, false) + setPrivacyController(micCamera = false, location = false) controller.init() @@ -160,7 +160,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @Test fun testIgnoredSlotsOnAttached_onlyMicCamera() { - setPrivacyController(false, true, false) + setPrivacyController(micCamera = true, location = false) controller.init() @@ -177,7 +177,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @Test fun testIgnoredSlotsOnAttached_onlyLocation() { - setPrivacyController(false, false, true) + setPrivacyController(micCamera = false, location = true) controller.init() @@ -192,26 +192,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @Test fun testIgnoredSlotsOnAttached_locationMicCamera() { - setPrivacyController(false, true, true) - - controller.init() - - val captor = argumentCaptor<List<String>>() - verify(iconContainer).setIgnoredSlots(capture(captor)) - - val cameraString = mContext.resources.getString( - com.android.internal.R.string.status_bar_camera) - val micString = mContext.resources.getString( - com.android.internal.R.string.status_bar_microphone) - val locationString = mContext.resources.getString( - com.android.internal.R.string.status_bar_location) - - assertThat(captor.value).containsExactly(cameraString, micString, locationString) - } - - @Test - fun testIgnoredSlotsOnAttached_all() { - setPrivacyController(true, false, false) + setPrivacyController(micCamera = true, location = true) controller.init() @@ -248,8 +229,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { `when`(view.findViewById<Clock>(R.id.clock)).thenReturn(clock) } - private fun setPrivacyController(all: Boolean, micCamera: Boolean, location: Boolean) { - `when`(privacyItemController.allIndicatorsAvailable).thenReturn(all) + private fun setPrivacyController(micCamera: Boolean, location: Boolean) { `when`(privacyItemController.micCameraAvailable).thenReturn(micCamera) `when`(privacyItemController.locationAvailable).thenReturn(location) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index 3aa40dec1fad..b1c3d1da8fea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -32,6 +32,7 @@ import android.testing.TestableLooper import android.view.IWindowManager import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -103,6 +104,7 @@ class CustomTileTest : SysuiTestCase() { { tileHost }, testableLooper.looper, Handler(testableLooper.looper), + FalsingManagerFake(), metricsLogger, statusBarStateController, activityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 61a0d6c17eed..937ab1c5c41d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -26,6 +26,8 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION; +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; @@ -55,7 +57,9 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSEvent; @@ -90,6 +94,7 @@ public class QSTileImplTest extends SysuiTestCase { private QSTileHost mHost; @Mock private MetricsLogger mMetricsLogger; + private final FalsingManagerFake mFalsingManager = new FalsingManagerFake(); @Mock private StatusBarStateController mStatusBarStateController; @Mock @@ -112,7 +117,7 @@ public class QSTileImplTest extends SysuiTestCase { Handler mainHandler = new Handler(mTestableLooper.getLooper()); - mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, + mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, mFalsingManager, mMetricsLogger, mStatusBarStateController, mActivityStarter, mQsLogger); mTile.setTileSpec(SPEC); } @@ -144,6 +149,19 @@ public class QSTileImplTest extends SysuiTestCase { } @Test + public void testClick_falsing() { + mFalsingManager.setFalseRobustTap(true); + mTile.click(); + mTestableLooper.processAllMessages(); + assertThat(mTile.mClicked).isFalse(); + + mFalsingManager.setFalseRobustTap(false); + mTile.click(); + mTestableLooper.processAllMessages(); + assertThat(mTile.mClicked).isTrue(); + } + + @Test public void testSecondaryClick_Metrics() { mTile.secondaryClick(); verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK))); @@ -360,17 +378,20 @@ public class QSTileImplTest extends SysuiTestCase { } private static class TileImpl extends QSTileImpl<QSTile.BooleanState> { + boolean mClicked; + protected TileImpl( QSHost host, Looper backgroundLooper, Handler mainHandler, + FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger ) { - super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController, - activityStarter, qsLogger); + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); getState().state = Tile.STATE_ACTIVE; } @@ -381,7 +402,7 @@ public class QSTileImplTest extends SysuiTestCase { @Override protected void handleClick() { - + mClicked = true; } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt index a9d10e9e6872..9674a60c1ac5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt @@ -10,6 +10,7 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost @@ -73,6 +74,7 @@ class AlarmTileTest : SysuiTestCase() { qsHost, testableLooper.looper, Handler(testableLooper.looper), + FalsingManagerFake(), metricsLogger, statusBarStateController, activityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt index bcfc83570345..f17bd56d0052 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt @@ -24,6 +24,7 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost @@ -79,6 +80,7 @@ class BatterySaverTileTest : SysuiTestCase() { qsHost, testableLooper.looper, Handler(testableLooper.looper), + FalsingManagerFake(), metricsLogger, statusBarStateController, activityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index 1c29a8174359..7d393610c2b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; @@ -101,6 +102,7 @@ public class CastTileTest extends SysuiTestCase { mHost, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), mMetricsLogger, mStatusBarStateController, mActivityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index ccd9548b269f..9fe568718908 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.qs.tiles import android.os.Handler +import android.content.Context +import android.content.Intent import android.provider.Settings import android.service.quicksettings.Tile import android.testing.AndroidTestingRunner @@ -26,11 +28,11 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.dagger.ControlsComponent import com.android.systemui.controls.management.ControlsListingController -import com.android.systemui.controls.ui.ControlsDialog import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -50,7 +52,9 @@ import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.doNothing import org.mockito.Mockito.never +import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.util.Optional @@ -80,8 +84,6 @@ class DeviceControlsTileTest : SysuiTestCase() { private lateinit var controlsController: ControlsController @Mock private lateinit var featureFlags: FeatureFlags - @Mock - private lateinit var controlsDialog: ControlsDialog private lateinit var globalSettings: GlobalSettings @Mock private lateinit var serviceInfo: ControlsServiceInfo @@ -95,6 +97,7 @@ class DeviceControlsTileTest : SysuiTestCase() { private lateinit var tile: DeviceControlsTile private lateinit var secureSettings: SecureSettings + private lateinit var spiedContext: Context private var featureEnabled = true @Before @@ -103,7 +106,9 @@ class DeviceControlsTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) secureSettings = FakeSettings() - `when`(qsHost.context).thenReturn(mContext) + spiedContext = spy(mContext) + doNothing().`when`(spiedContext).startActivity(any(Intent::class.java)) + `when`(qsHost.context).thenReturn(spiedContext) `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) `when`(controlsController.available).thenReturn(true) `when`(controlsComponent.isEnabled()).thenReturn(true) @@ -276,7 +281,7 @@ class DeviceControlsTileTest : SysuiTestCase() { tile.click() testableLooper.processAllMessages() - verify(controlsDialog, never()).show(any(ControlsUiController::class.java)) + verify(spiedContext, never()).startActivity(any(Intent::class.java)) } @Test @@ -293,7 +298,7 @@ class DeviceControlsTileTest : SysuiTestCase() { tile.click() testableLooper.processAllMessages() - verify(controlsDialog).show(controlsUiController) + verify(spiedContext).startActivity(any(Intent::class.java)) } @Test @@ -311,25 +316,7 @@ class DeviceControlsTileTest : SysuiTestCase() { tile.click() testableLooper.processAllMessages() - verify(controlsDialog, never()).show(any(ControlsUiController::class.java)) - } - - @Test - fun testDialogDismissedOnDestroy() { - verify(controlsListingController).observe( - any(LifecycleOwner::class.java), - capture(listingCallbackCaptor) - ) - - listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo)) - testableLooper.processAllMessages() - - tile.click() - testableLooper.processAllMessages() - - tile.destroy() - testableLooper.processAllMessages() - verify(controlsDialog).dismiss() + verify(spiedContext, never()).startActivity(any(Intent::class.java)) } private fun createTile(): DeviceControlsTile { @@ -337,13 +324,13 @@ class DeviceControlsTileTest : SysuiTestCase() { qsHost, testableLooper.looper, Handler(testableLooper.looper), + FalsingManagerFake(), metricsLogger, statusBarStateController, activityStarter, qsLogger, controlsComponent, featureFlags, - { controlsDialog }, globalSettings ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java index b37ac4a2b759..99d028cd8c5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java @@ -33,6 +33,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; @@ -84,6 +85,7 @@ public class NfcTileTest extends SysuiTestCase { mHost, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), mMetricsLogger, mStatusBarStateController, mActivityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index 880c290802df..6032e51ab554 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; @@ -82,6 +83,7 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { mHost, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), mMetricsLogger, mStatusBarStateController, mActivityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index 6b54791dd143..22154332c953 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; @@ -81,6 +82,7 @@ public class ScreenRecordTileTest extends SysuiTestCase { mHost, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), mMetricsLogger, mStatusBarStateController, mActivityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java deleted file mode 100644 index 25104b8b1d20..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents; - -import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.pm.PackageManager; -import android.os.RemoteException; -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; -import android.testing.TestableContext; -import android.testing.TestableLooper; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.model.SysUiState; -import com.android.systemui.navigationbar.NavigationBarController; -import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.shared.recents.IPinnedStackAnimationListener; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; -import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.splitscreen.SplitScreen; -import com.android.wm.shell.startingsurface.StartingSurface; -import com.android.wm.shell.transition.RemoteTransitions; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Optional; - -import dagger.Lazy; - -/** - * Unit tests for {@link com.android.systemui.recents.OverviewProxyService} - */ -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class OverviewProxyServiceTest extends SysuiTestCase { - private OverviewProxyService mSpiedOverviewProxyService; - private TestableContext mSpiedContext; - - @Mock private BroadcastDispatcher mMockBroadcastDispatcher; - @Mock private CommandQueue mMockCommandQueue; - @Mock private Lazy<NavigationBarController> mMockNavBarControllerLazy; - @Mock private IPinnedStackAnimationListener mMockPinnedStackAnimationListener; - @Mock private NavigationModeController mMockNavModeController; - @Mock private NotificationShadeWindowController mMockStatusBarWinController; - @Mock private Optional<Pip> mMockPipOptional; - @Mock private Optional<LegacySplitScreen> mMockLegacySplitScreenOptional; - @Mock private Optional<SplitScreen> mMockSplitScreenOptional; - @Mock private Optional<Lazy<StatusBar>> mMockStatusBarOptionalLazy; - @Mock private Optional<com.android.wm.shell.onehanded.OneHanded> mMockOneHandedOptional; - @Mock private PackageManager mPackageManager; - @Mock private SysUiState mMockSysUiState; - @Mock private RemoteTransitions mMockTransitions; - @Mock private Optional<StartingSurface> mStartingSurface; - - @Before - public void setUp() throws RemoteException { - MockitoAnnotations.initMocks(this); - - mSpiedContext = spy(mContext); - - when(mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false); - when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager); - - mSpiedOverviewProxyService = spy(new OverviewProxyService(mSpiedContext, mMockCommandQueue, - mMockNavBarControllerLazy, mMockNavModeController, mMockStatusBarWinController, - mMockSysUiState, mMockPipOptional, mMockLegacySplitScreenOptional, - mMockSplitScreenOptional, mMockStatusBarOptionalLazy, mMockOneHandedOptional, - mMockBroadcastDispatcher, mMockTransitions, mStartingSurface)); - } - - @Test - public void testNonPipDevice_shouldNotNotifySwipeToHomeFinished() throws RemoteException { - mSpiedOverviewProxyService.mSysUiProxy.notifySwipeToHomeFinished(); - - verify(mMockPipOptional, never()).ifPresent(any()); - } - - @Test - public void testNonPipDevice_shouldNotSetPinnedStackAnimationListener() throws RemoteException { - mSpiedOverviewProxyService.mSysUiProxy.setPinnedStackAnimationListener( - mMockPinnedStackAnimationListener); - - verify(mMockPipOptional, never()).ifPresent(any()); - } - - @Test - public void testNonPipDevice_shouldNotSetShelfHeight() throws RemoteException { - mSpiedOverviewProxyService.mSysUiProxy.setShelfHeight(true /* visible */, - 100 /* shelfHeight */); - - verify(mMockPipOptional, never()).ifPresent(any()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 2917dfafd6a6..8ec03d76cfea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -476,4 +476,11 @@ public class CommandQueueTest extends SysuiTestCase { waitForIdleSync(); verify(mCallbacks).requestWindowMagnificationConnection(true); } + + @Test + public void testSetEnableNavigationBarLumaSampling() { + mCommandQueue.setNavigationBarLumaSamplingEnabled(1, true); + waitForIdleSync(); + verify(mCallbacks).setNavigationBarLumaSamplingEnabled(eq(1), eq(true)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index 8cd71031a8f8..c1d2ea88a1b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -322,23 +322,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { } @Test - public void testPeopleFiltering_addHeadersFromShowingOnlyGentle() { - enablePeopleFiltering(); - - setStackState( - GENTLE_HEADER, - PERSON, - ALERTING, - GENTLE); - mSectionsManager.updateSectionBoundaries(); - - verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2); - verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1); - verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0); - } - - @Test - public void testPeopleFiltering_addAllHeaders() { + public void testPeopleFiltering_onlyAddSilentHeader() { enablePeopleFiltering(); setStackState( @@ -348,26 +332,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { mSectionsManager.updateSectionBoundaries(); verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 2); - verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1); - verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0); - } - - @Test - public void testPeopleFiltering_moveAllHeaders() { - enablePeopleFiltering(); - - setStackState( - PEOPLE_HEADER, - ALERTING_HEADER, - GENTLE_HEADER, - PERSON, - ALERTING, - GENTLE); - mSectionsManager.updateSectionBoundaries(); - - verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 4); - verify(mNssl).changeViewPosition(mSectionsManager.getAlertingHeaderView(), 2); - verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0); } @Test @@ -385,9 +349,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { mSectionsManager.updateSectionBoundaries(); verifyMockStack( - ChildType.INCOMING_HEADER, ChildType.HEADS_UP, - ChildType.PEOPLE_HEADER, ChildType.PERSON, ChildType.GENTLE_HEADER, ChildType.GENTLE @@ -408,10 +370,8 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { mSectionsManager.updateSectionBoundaries(); verifyMockStack( - ChildType.INCOMING_HEADER, ChildType.HEADS_UP, ChildType.HEADS_UP, - ChildType.PEOPLE_HEADER, ChildType.PERSON ); } @@ -428,7 +388,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { mSectionsManager.updateSectionBoundaries(); verifyMockStack( - ChildType.PEOPLE_HEADER, ChildType.PERSON, ChildType.PERSON ); @@ -444,9 +403,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { ); mSectionsManager.updateSectionBoundaries(); verifyMockStack( - ChildType.INCOMING_HEADER, ChildType.HEADS_UP, - ChildType.PEOPLE_HEADER, ChildType.PERSON ); } @@ -467,12 +424,9 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { mSectionsManager.updateSectionBoundaries(); verifyMockStack( - ChildType.INCOMING_HEADER, ChildType.HEADS_UP, ChildType.FSN, - ChildType.PEOPLE_HEADER, ChildType.PERSON, - ChildType.ALERTING_HEADER, ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE @@ -517,7 +471,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { } @Test - public void testRemoveIncomingHeader() { + public void testRemoveNonSilentHeader() { enablePeopleFiltering(); enableMediaControls(); @@ -539,9 +493,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { verifyMockStack( ChildType.MEDIA_CONTROLS, - ChildType.PEOPLE_HEADER, ChildType.PERSON, - ChildType.ALERTING_HEADER, ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, @@ -569,13 +521,10 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { mSectionsManager.updateSectionBoundaries(); verifyMockStack( - ChildType.INCOMING_HEADER, ChildType.HEADS_UP, ChildType.HEADS_UP, ChildType.HEADS_UP, - ChildType.PEOPLE_HEADER, ChildType.PERSON, - ChildType.ALERTING_HEADER, ChildType.ALERTING ); } @@ -593,7 +542,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { mSectionsManager.updateSectionBoundaries(); verifyMockStack( - ChildType.ALERTING_HEADER, ChildType.PERSON, ChildType.ALERTING, ChildType.GENTLE_HEADER, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index bdde82289e86..8b5ba3848500 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -174,6 +174,7 @@ public class DozeServiceHostTest extends SysuiTestCase { DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, DozeLog.PULSE_REASON_DOCKING, DozeLog.REASON_SENSOR_WAKE_UP, + DozeLog.REASON_SENSOR_QUICK_PICKUP, DozeLog.REASON_SENSOR_TAP)); HashSet<Integer> reasonsThatDontPulse = new HashSet<>( Arrays.asList(DozeLog.REASON_SENSOR_PICKUP, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index a844d099d43a..a60baa54541e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -45,6 +45,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; @@ -86,6 +87,7 @@ public class RemoteInputViewTest extends SysuiTestCase { mRemoteInputQuickSettingsDisabler); mDependency.injectTestDependency(LightBarController.class, mLightBarController); + mDependency.injectMockDependency(NotificationRemoteInputManager.class); mReceiver = new BlockingQueueIntentReceiver(); mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION), null, diff --git a/services/api/Android.bp b/services/api/Android.bp index e69de29bb2d1..b8ca5488c5cd 100644 --- a/services/api/Android.bp +++ b/services/api/Android.bp @@ -0,0 +1,29 @@ +// 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 { + default_visibility: ["//visibility:private"], +} + +filegroup { + name: "non-updatable-system-server-current.txt", + srcs: ["non-updatable-current.txt"], + visibility: ["//frameworks/base/api"], +} + +filegroup { + name: "non-updatable-system-server-removed.txt", + srcs: ["non-updatable-removed.txt"], + visibility: ["//frameworks/base/api"], +}
\ No newline at end of file diff --git a/services/core/Android.bp b/services/core/Android.bp index 8ccfad6fe061..b00689be3656 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -98,7 +98,6 @@ java_library_static { ":platform-compat-overrides", ":display-device-config", ":display-layout-config", - ":cec-config", ":device-state-config", "java/com/android/server/EventLogTags.logtags", "java/com/android/server/am/EventLogTags.logtags", @@ -119,7 +118,6 @@ java_library_static { ], required: [ - "cec_config.xml", "gps_debug.conf", "protolog.conf.json.gz", ], @@ -185,11 +183,6 @@ java_library_host { } prebuilt_etc { - name: "cec_config.xml", - src: "java/com/android/server/hdmi/cec_config.xml", -} - -prebuilt_etc { name: "gps_debug.conf", src: "java/com/android/server/location/gnss/gps_debug.conf", } @@ -223,6 +216,7 @@ filegroup { "java/com/android/server/TestNetworkService.java", "java/com/android/server/connectivity/AutodestructReference.java", "java/com/android/server/connectivity/ConnectivityConstants.java", + "java/com/android/server/connectivity/ConnectivityResources.java", "java/com/android/server/connectivity/DnsManager.java", "java/com/android/server/connectivity/KeepaliveTracker.java", "java/com/android/server/connectivity/LingerMonitor.java", diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index bb4bbd5bc6d4..1e608f5c1240 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -918,19 +918,7 @@ public final class BatteryService extends SystemService { int opts = parseOptions(shell); getContext().enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); - if (!mUpdatesStopped) { - copy(mLastHealthInfo, mHealthInfo); - } - mHealthInfo.chargerAcOnline = false; - mHealthInfo.chargerUsbOnline = false; - mHealthInfo.chargerWirelessOnline = false; - final long ident = Binder.clearCallingIdentity(); - try { - mUpdatesStopped = true; - processValuesFromShellLocked(pw, opts); - } finally { - Binder.restoreCallingIdentity(ident); - } + unplugBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw); } break; case "set": { int opts = parseOptions(shell); @@ -990,7 +978,8 @@ public final class BatteryService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { mUpdatesStopped = true; - processValuesFromShellLocked(pw, opts); + processValuesLocked( + /* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw); } finally { Binder.restoreCallingIdentity(ident); } @@ -1004,30 +993,12 @@ public final class BatteryService extends SystemService { int opts = parseOptions(shell); getContext().enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); - final long ident = Binder.clearCallingIdentity(); - try { - if (mUpdatesStopped) { - mUpdatesStopped = false; - copy(mHealthInfo, mLastHealthInfo); - processValuesFromShellLocked(pw, opts); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - if (mBatteryInputSuspended) { - PowerProperties.battery_input_suspended(false); - mBatteryInputSuspended = false; - } + resetBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw); } break; case "suspend_input": { - if (!Build.IS_DEBUGGABLE) { - throw new SecurityException( - "battery suspend_input is only supported on debuggable builds"); - } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); - PowerProperties.battery_input_suspended(true); - mBatteryInputSuspended = true; + suspendBatteryInput(); } break; default: return shell.handleDefaultCommands(cmd); @@ -1035,9 +1006,59 @@ public final class BatteryService extends SystemService { return 0; } - private void processValuesFromShellLocked(PrintWriter pw, int opts) { - processValuesLocked((opts & OPTION_FORCE_UPDATE) != 0); - if ((opts & OPTION_FORCE_UPDATE) != 0) { + private void setChargerAcOnline(boolean online, boolean forceUpdate) { + if (!mUpdatesStopped) { + copy(mLastHealthInfo, mHealthInfo); + } + mHealthInfo.chargerAcOnline = online; + mUpdatesStopped = true; + Binder.withCleanCallingIdentity(() -> processValuesLocked(forceUpdate)); + } + + private void setBatteryLevel(int level, boolean forceUpdate) { + if (!mUpdatesStopped) { + copy(mLastHealthInfo, mHealthInfo); + } + mHealthInfo.batteryLevel = level; + mUpdatesStopped = true; + Binder.withCleanCallingIdentity(() -> processValuesLocked(forceUpdate)); + } + + private void unplugBattery(boolean forceUpdate, PrintWriter pw) { + if (!mUpdatesStopped) { + copy(mLastHealthInfo, mHealthInfo); + } + mHealthInfo.chargerAcOnline = false; + mHealthInfo.chargerUsbOnline = false; + mHealthInfo.chargerWirelessOnline = false; + mUpdatesStopped = true; + Binder.withCleanCallingIdentity(() -> processValuesLocked(forceUpdate, pw)); + } + + private void resetBattery(boolean forceUpdate, @Nullable PrintWriter pw) { + if (mUpdatesStopped) { + mUpdatesStopped = false; + copy(mHealthInfo, mLastHealthInfo); + Binder.withCleanCallingIdentity(() -> processValuesLocked(forceUpdate, pw)); + } + if (mBatteryInputSuspended) { + PowerProperties.battery_input_suspended(false); + mBatteryInputSuspended = false; + } + } + + private void suspendBatteryInput() { + if (!Build.IS_DEBUGGABLE) { + throw new SecurityException( + "battery suspend_input is only supported on debuggable builds"); + } + PowerProperties.battery_input_suspended(true); + mBatteryInputSuspended = true; + } + + private void processValuesLocked(boolean forceUpdate, @Nullable PrintWriter pw) { + processValuesLocked(forceUpdate); + if (pw != null && forceUpdate) { pw.println(mSequence); } } @@ -1363,6 +1384,41 @@ public final class BatteryService extends SystemService { return mInvalidCharger; } } + + @Override + public void setChargerAcOnline(boolean online, boolean forceUpdate) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, /* message= */ null); + BatteryService.this.setChargerAcOnline(online, forceUpdate); + } + + @Override + public void setBatteryLevel(int level, boolean forceUpdate) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, /* message= */ null); + BatteryService.this.setBatteryLevel(level, forceUpdate); + } + + @Override + public void unplugBattery(boolean forceUpdate) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, /* message= */ null); + BatteryService.this.unplugBattery(forceUpdate, /* printWriter= */ null); + } + + @Override + public void resetBattery(boolean forceUpdate) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, /* message= */ null); + BatteryService.this.resetBattery(forceUpdate, /* printWriter= */ null); + } + + @Override + public void suspendBatteryInput() { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, /* message= */ null); + BatteryService.this.suspendBatteryInput(); + } } /** @@ -1539,6 +1595,8 @@ public final class BatteryService extends SystemService { if (Objects.equals(newService, oldService)) return; Slog.i(TAG, "health: new instance registered " + mInstanceName); + // #init() may be called with null callback. Skip null callbacks. + if (mCallback == null) return; mCallback.onRegistration(oldService, newService, mInstanceName); } catch (NoSuchElementException | RemoteException ex) { Slog.e(TAG, "health: Cannot get instance '" + mInstanceName diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 709f5b2106f3..b4fcaeedd845 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -85,6 +85,7 @@ import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager.ConnectivityReport; import android.net.ConnectivityDiagnosticsManager.DataStallReport; import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; import android.net.DataStallReportParcelable; import android.net.DnsResolverServiceManager; import android.net.ICaptivePortal; @@ -175,6 +176,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.sysprop.NetworkProperties; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; @@ -186,10 +188,8 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.connectivity.aidl.INetworkAgent; -import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.LocationPermissionChecker; import com.android.internal.util.MessageUtils; @@ -201,6 +201,7 @@ import com.android.net.module.util.LinkPropertiesUtils.CompareResult; import com.android.net.module.util.NetworkCapabilitiesUtils; import com.android.net.module.util.PermissionUtils; import com.android.server.connectivity.AutodestructReference; +import com.android.server.connectivity.ConnectivityResources; import com.android.server.connectivity.DnsManager; import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate; import com.android.server.connectivity.KeepaliveTracker; @@ -316,6 +317,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private boolean mRestrictBackground; private final Context mContext; + private final ConnectivityResources mResources; // The Context is created for UserHandle.ALL. private final Context mUserAllContext; private final Dependencies mDeps; @@ -343,8 +345,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private String mCurrentTcpBufferSizes; private static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames( - new Class[] { AsyncChannel.class, ConnectivityService.class, NetworkAgent.class, - NetworkAgentInfo.class }); + new Class[] { ConnectivityService.class, NetworkAgent.class, NetworkAgentInfo.class }); private enum ReapUnvalidatedNetworks { // Tear down networks that have no chance (e.g. even if validated) of becoming @@ -604,7 +605,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private Intent mInitialBroadcast; private PowerManager.WakeLock mNetTransitionWakeLock; - private int mNetTransitionWakeLockTimeout; private final PowerManager.WakeLock mPendingIntentWakeLock; // A helper object to track the current default HTTP proxy. ConnectivityService needs to tell @@ -1012,6 +1012,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** + * Get the {@link ConnectivityResources} to use in ConnectivityService. + */ + public ConnectivityResources getResources(@NonNull Context ctx) { + return new ConnectivityResources(ctx); + } + + /** * Create a HandlerThread to use in ConnectivityService. */ public HandlerThread makeHandlerThread() { @@ -1069,10 +1076,14 @@ public class ConnectivityService extends IConnectivityManager.Stub */ public void reportNetworkInterfaceForTransports(Context context, String iface, int[] transportTypes) { - final BatteryStatsManager batteryStats = + final BatteryStatsManager batteryStats = context.getSystemService(BatteryStatsManager.class); batteryStats.reportNetworkInterfaceForTransports(iface, transportTypes); } + + public boolean getCellular464XlatEnabled() { + return NetworkProperties.isCellular464XlatEnabled().orElse(true); + } } public ConnectivityService(Context context) { @@ -1089,13 +1100,15 @@ public class ConnectivityService extends IConnectivityManager.Stub mSystemProperties = mDeps.getSystemProperties(); mNetIdManager = mDeps.makeNetIdManager(); mContext = Objects.requireNonNull(context, "missing Context"); + mResources = deps.getResources(mContext); mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID); mMetricsLog = logger; mNetworkRanker = new NetworkRanker(); final NetworkRequest defaultInternetRequest = createDefaultRequest(); mDefaultRequest = new NetworkRequestInfo( - defaultInternetRequest, null, new Binder(), + defaultInternetRequest, null, + new Binder(), NetworkCallback.FLAG_INCLUDE_LOCATION_INFO, null /* attributionTags */); mNetworkRequests.put(defaultInternetRequest, mDefaultRequest); mDefaultNetworkRequests.add(mDefaultRequest); @@ -1149,8 +1162,6 @@ public class ConnectivityService extends IConnectivityManager.Stub final PowerManager powerManager = (PowerManager) context.getSystemService( Context.POWER_SERVICE); mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - mNetTransitionWakeLockTimeout = mContext.getResources().getInteger( - com.android.internal.R.integer.config_networkTransitionTimeout); mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1]; @@ -1217,10 +1228,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - mWolSupportedInterfaces = new ArraySet( - mContext.getResources().getStringArray( - com.android.internal.R.array.config_wakeonlan_supported_interfaces)); - mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mPermissionMonitor = new PermissionMonitor(mContext, mNetd); @@ -1271,8 +1278,7 @@ public class ConnectivityService extends IConnectivityManager.Stub new NetworkInfo(TYPE_NONE, 0, "", ""), new LinkProperties(), new NetworkCapabilities(), 0, mContext, null, new NetworkAgentConfig(), this, null, - null, 0, INVALID_UID, - mQosCallbackTracker); + null, 0, INVALID_UID, mQosCallbackTracker, mDeps); } private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { @@ -1352,7 +1358,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (enable) { handleRegisterNetworkRequest(new NetworkRequestInfo( - networkRequest, null, new Binder(), + networkRequest, null, + new Binder(), + NetworkCallback.FLAG_INCLUDE_LOCATION_INFO, null /* attributionTags */)); } else { handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID, @@ -1514,11 +1522,11 @@ public class ConnectivityService extends IConnectivityManager.Stub // but only exists if an app asks about them or requests them. Ensure the requesting app // gets the type it asks for. filtered.setType(type); - final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked) - ? DetailedState.BLOCKED - : filtered.getDetailedState(); - filtered.setDetailedState(getLegacyLockdownState(state), - "" /* reason */, null /* extraInfo */); + if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) { + filtered.setDetailedState(DetailedState.BLOCKED, null /* reason */, + null /* extraInfo */); + } + filterForLegacyLockdown(filtered); return filtered; } @@ -1594,8 +1602,8 @@ public class ConnectivityService extends IConnectivityManager.Stub final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, false) ? DetailedState.BLOCKED : DetailedState.DISCONNECTED; - info.setDetailedState(getLegacyLockdownState(state), - "" /* reason */, null /* extraInfo */); + info.setDetailedState(state, null /* reason */, null /* extraInfo */); + filterForLegacyLockdown(info); return info; } @@ -1713,8 +1721,8 @@ public class ConnectivityService extends IConnectivityManager.Stub result.put( nai.network, createWithLocationInfoSanitizedIfNecessaryWhenParceled( - nc, mDeps.getCallingUid(), callingPackageName, - callingAttributionTag)); + nc, false /* includeLocationSensitiveInfo */, + mDeps.getCallingUid(), callingPackageName, callingAttributionTag)); } } @@ -1727,7 +1735,9 @@ public class ConnectivityService extends IConnectivityManager.Stub result.put( network, createWithLocationInfoSanitizedIfNecessaryWhenParceled( - nc, mDeps.getCallingUid(), callingPackageName, + nc, + false /* includeLocationSensitiveInfo */, + mDeps.getCallingUid(), callingPackageName, callingAttributionTag)); } } @@ -1809,6 +1819,7 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceAccessPermission(); return createWithLocationInfoSanitizedIfNecessaryWhenParceled( getNetworkCapabilitiesInternal(network), + false /* includeLocationSensitiveInfo */, mDeps.getCallingUid(), callingPackageName, callingAttributionTag); } @@ -1842,8 +1853,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting @Nullable NetworkCapabilities createWithLocationInfoSanitizedIfNecessaryWhenParceled( - @Nullable NetworkCapabilities nc, int callerUid, @NonNull String callerPkgName, - @Nullable String callingAttributionTag) { + @Nullable NetworkCapabilities nc, boolean includeLocationSensitiveInfo, + int callerUid, @NonNull String callerPkgName, @Nullable String callingAttributionTag) { if (nc == null) { return null; } @@ -1851,7 +1862,9 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkCapabilities newNc; // Avoid doing location permission check if the transport info has no location sensitive // data. - if (nc.getTransportInfo() != null && nc.getTransportInfo().hasLocationSensitiveFields()) { + if (includeLocationSensitiveInfo + && nc.getTransportInfo() != null + && nc.getTransportInfo().hasLocationSensitiveFields()) { hasLocationPermission = hasLocationPermission(callerUid, callerPkgName, callingAttributionTag); newNc = new NetworkCapabilities(nc, hasLocationPermission); @@ -1868,6 +1881,16 @@ public class ConnectivityService extends IConnectivityManager.Stub // Owner UIDs already checked above. No need to re-check. return newNc; } + // If the caller does not want location sensitive data & target SDK >= S, then mask info. + // Else include the owner UID iff the caller has location permission to provide backwards + // compatibility for older apps. + if (!includeLocationSensitiveInfo + && isTargetSdkAtleast( + Build.VERSION_CODES.S, callerUid, callerPkgName)) { + newNc.setOwnerUid(INVALID_UID); + return newNc; + } + if (hasLocationPermission == null) { // Location permission not checked yet, check now for masking owner UID. hasLocationPermission = @@ -2389,9 +2412,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService"); } - // Public because it's used by mLockdownTracker. - public void sendConnectedBroadcast(NetworkInfo info) { - PermissionUtils.enforceNetworkStackPermission(mContext); + private void sendConnectedBroadcast(NetworkInfo info) { sendGeneralBroadcast(info, CONNECTIVITY_ACTION); } @@ -2888,22 +2909,6 @@ public class ConnectivityService extends IConnectivityManager.Stub super(looper); } - private boolean maybeHandleAsyncChannelMessage(Message msg) { - switch (msg.what) { - default: - return false; - case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: { - handleAsyncChannelHalfConnect(msg); - break; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { - handleAsyncChannelDisconnected(msg); - break; - } - } - return true; - } - private void maybeHandleNetworkAgentMessage(Message msg) { final Pair<NetworkAgentInfo, Object> arg = (Pair<NetworkAgentInfo, Object>) msg.obj; final NetworkAgentInfo nai = arg.first; @@ -3195,8 +3200,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void handleMessage(Message msg) { - if (!maybeHandleAsyncChannelMessage(msg) - && !maybeHandleNetworkMonitorMessage(msg) + if (!maybeHandleNetworkMonitorMessage(msg) && !maybeHandleNetworkAgentInfoMessage(msg)) { maybeHandleNetworkAgentMessage(msg); } @@ -3460,21 +3464,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return false; } - private void handleAsyncChannelHalfConnect(Message msg) { - ensureRunningOnConnectivityServiceThread(); - if (mNetworkProviderInfos.containsKey(msg.replyTo)) { - if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { - if (VDBG) log("NetworkFactory connected"); - // Finish setting up the full connection - NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo); - sendAllRequestsToProvider(npi); - } else { - loge("Error connecting NetworkFactory"); - mNetworkProviderInfos.remove(msg.obj); - } - } - } - private void handleNetworkAgentRegistered(Message msg) { final NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj; if (!mNetworkAgentInfos.contains(nai)) { @@ -3505,14 +3494,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - // This is a no-op if it's called with a message designating a provider that has - // already been destroyed, because its reference will not be found in the relevant - // maps. - private void handleAsyncChannelDisconnected(Message msg) { - NetworkProviderInfo npi = mNetworkProviderInfos.remove(msg.replyTo); - if (DBG && npi != null) log("unregisterNetworkFactory for " + npi.name); - } - // Destroys a network, remove references to it from the internal state managed by // ConnectivityService, free its interfaces and clean up. // Must be called on the Handler thread. @@ -4621,7 +4602,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } mWakelockLogs.log("ACQUIRE for " + forWhom); Message msg = mHandler.obtainMessage(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK); - mHandler.sendMessageDelayed(msg, mNetTransitionWakeLockTimeout); + final int lockTimeout = mResources.get().getInteger( + com.android.connectivity.resources.R.integer.config_networkTransitionTimeout); + mHandler.sendMessageDelayed(msg, lockTimeout); } // Called when we gain a new default network to release the network transition wakelock in a @@ -5036,8 +5019,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // The legacy lockdown VPN always uses the default network. // If the VPN's underlying network is no longer the current default network, it means that // the default network has just switched, and the VPN is about to disconnect. - // Report that the VPN is not connected, so when the state of NetworkInfo objects - // overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED. + // Report that the VPN is not connected, so the state of NetworkInfo objects overwritten + // by filterForLegacyLockdown will be set to CONNECTING and not CONNECTED. final NetworkAgentInfo defaultNetwork = getDefaultNetwork(); if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) { return null; @@ -5046,6 +5029,9 @@ public class ConnectivityService extends IConnectivityManager.Stub return nai; }; + // TODO: move all callers to filterForLegacyLockdown and delete this method. + // This likely requires making sendLegacyNetworkBroadcast take a NetworkInfo object instead of + // just a DetailedState object. private DetailedState getLegacyLockdownState(DetailedState origState) { if (origState != DetailedState.CONNECTED) { return origState; @@ -5055,6 +5041,23 @@ public class ConnectivityService extends IConnectivityManager.Stub : DetailedState.CONNECTED; } + private void filterForLegacyLockdown(NetworkInfo ni) { + if (!mLockdownEnabled || !ni.isConnected()) return; + // The legacy lockdown VPN replaces the state of every network in CONNECTED state with the + // state of its VPN. This is to ensure that when an underlying network connects, apps will + // not see a CONNECTIVITY_ACTION broadcast for a network in state CONNECTED until the VPN + // comes up, at which point there is a new CONNECTIVITY_ACTION broadcast for the underlying + // network, this time with a state of CONNECTED. + // + // Now that the legacy lockdown code lives in ConnectivityService, and no longer has access + // to the internal state of the Vpn object, always replace the state with CONNECTING. This + // is not too far off the truth, since an always-on VPN, when not connected, is always + // trying to reconnect. + if (getLegacyLockdownNai() == null) { + ni.setDetailedState(DetailedState.CONNECTING, "", null); + } + } + @Override public void setProvisioningNotificationVisible(boolean visible, int networkType, String action) { @@ -5133,8 +5136,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private final IBinder.DeathRecipient mDeathRecipient; public final int providerId; - NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel, - int providerId, @NonNull IBinder.DeathRecipient deathRecipient) { + NetworkProviderInfo(String name, Messenger messenger, int providerId, + @NonNull IBinder.DeathRecipient deathRecipient) { this.name = name; this.messenger = messenger; this.providerId = providerId; @@ -5228,6 +5231,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private final IBinder mBinder; final int mPid; final int mUid; + final @NetworkCallback.Flag int mCallbackFlags; @Nullable final String mCallingAttributionTag; // In order to preserve the mapping of NetworkRequest-to-callback when apps register @@ -5275,17 +5279,26 @@ public class ConnectivityService extends IConnectivityManager.Stub mPid = getCallingPid(); mUid = mDeps.getCallingUid(); mNetworkRequestCounter.incrementCountOrThrow(mUid); + /** + * Location sensitive data not included in pending intent. Only included in + * {@link NetworkCallback}. + */ + mCallbackFlags = NetworkCallback.FLAG_NONE; mCallingAttributionTag = callingAttributionTag; } NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m, - @Nullable final IBinder binder, @Nullable String callingAttributionTag) { - this(Collections.singletonList(r), r, m, binder, callingAttributionTag); + @Nullable final IBinder binder, + @NetworkCallback.Flag int callbackFlags, + @Nullable String callingAttributionTag) { + this(Collections.singletonList(r), r, m, binder, callbackFlags, callingAttributionTag); } NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m, - @Nullable final IBinder binder, @Nullable String callingAttributionTag) { + @Nullable final IBinder binder, + @NetworkCallback.Flag int callbackFlags, + @Nullable String callingAttributionTag) { super(); ensureAllNetworkRequestsHaveType(r); mRequests = initializeRequests(r); @@ -5296,6 +5309,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mUid = mDeps.getCallingUid(); mPendingIntent = null; mNetworkRequestCounter.incrementCountOrThrow(mUid); + mCallbackFlags = callbackFlags; mCallingAttributionTag = callingAttributionTag; try { @@ -5337,6 +5351,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mUid = nri.mUid; mPendingIntent = nri.mPendingIntent; mNetworkRequestCounter.incrementCountOrThrow(mUid); + mCallbackFlags = nri.mCallbackFlags; mCallingAttributionTag = nri.mCallingAttributionTag; } @@ -5386,7 +5401,8 @@ public class ConnectivityService extends IConnectivityManager.Stub + " callback request Id: " + mNetworkRequestForCallback.requestId + " " + mRequests - + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent); + + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent) + + "callback flags: " + mCallbackFlags; } } @@ -5470,13 +5486,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private boolean checkUnsupportedStartingFrom(int version, String callingPackageName) { - final UserHandle user = UserHandle.getUserHandleForUid(mDeps.getCallingUid()); + private boolean isTargetSdkAtleast(int version, int callingUid, + @NonNull String callingPackageName) { + final UserHandle user = UserHandle.getUserHandleForUid(callingUid); final PackageManager pm = mContext.createContextAsUser(user, 0 /* flags */).getPackageManager(); try { - final int callingVersion = pm.getApplicationInfo( - callingPackageName, 0 /* flags */).targetSdkVersion; + final int callingVersion = pm.getTargetSdkVersion(callingPackageName); if (callingVersion < version) return false; } catch (PackageManager.NameNotFoundException e) { } return true; @@ -5485,10 +5501,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities, int reqTypeInt, Messenger messenger, int timeoutMs, IBinder binder, - int legacyType, @NonNull String callingPackageName, + int legacyType, int callbackFlags, @NonNull String callingPackageName, @Nullable String callingAttributionTag) { if (legacyType != TYPE_NONE && !checkNetworkStackPermission()) { - if (checkUnsupportedStartingFrom(Build.VERSION_CODES.M, callingPackageName)) { + if (isTargetSdkAtleast(Build.VERSION_CODES.M, mDeps.getCallingUid(), + callingPackageName)) { throw new SecurityException("Insufficient permissions to specify legacy type"); } } @@ -5525,6 +5542,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // request if the app changes network state. http://b/29964605 enforceMeteredApnPolicy(networkCapabilities); break; + case TRACK_BEST: + throw new UnsupportedOperationException("Not implemented yet"); default: throw new IllegalArgumentException("Unsupported request type " + reqType); } @@ -5548,7 +5567,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, nextNetworkRequestId(), reqType); final NetworkRequestInfo nri = getNriToRegister( - networkRequest, messenger, binder, callingAttributionTag); + networkRequest, messenger, binder, callbackFlags, callingAttributionTag); if (DBG) log("requestNetwork for " + nri); // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were @@ -5583,6 +5602,7 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private NetworkRequestInfo getNriToRegister(@NonNull final NetworkRequest nr, @Nullable final Messenger msgr, @Nullable final IBinder binder, + @NetworkCallback.Flag int callbackFlags, @Nullable String callingAttributionTag) { final List<NetworkRequest> requests; if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) { @@ -5591,7 +5611,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } else { requests = Collections.singletonList(nr); } - return new NetworkRequestInfo(requests, nr, msgr, binder, callingAttributionTag); + return new NetworkRequestInfo( + requests, nr, msgr, binder, callbackFlags, callingAttributionTag); } private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities, @@ -5717,8 +5738,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities, - Messenger messenger, IBinder binder, @NonNull String callingPackageName, - @Nullable String callingAttributionTag) { + Messenger messenger, IBinder binder, + @NetworkCallback.Flag int callbackFlags, + @NonNull String callingPackageName, @NonNull String callingAttributionTag) { final int callingUid = mDeps.getCallingUid(); if (!hasWifiNetworkListenPermission(networkCapabilities)) { enforceAccessPermission(); @@ -5739,7 +5761,8 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); NetworkRequestInfo nri = - new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag); + new NetworkRequestInfo(networkRequest, messenger, binder, callbackFlags, + callingAttributionTag); if (VDBG) log("listenForNetwork for " + nri); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri)); @@ -5808,8 +5831,7 @@ public class ConnectivityService extends IConnectivityManager.Stub public int registerNetworkProvider(Messenger messenger, String name) { enforceNetworkFactoryOrSettingsPermission(); NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, - null /* asyncChannel */, nextNetworkProviderId(), - () -> unregisterNetworkProvider(messenger)); + nextNetworkProviderId(), () -> unregisterNetworkProvider(messenger)); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi)); return npi.providerId; } @@ -6130,7 +6152,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkAgentInfo nai = new NetworkAgentInfo(na, new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), - this, mNetd, mDnsResolver, providerId, uid, mQosCallbackTracker); + this, mNetd, mDnsResolver, providerId, uid, mQosCallbackTracker, mDeps); // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says. processCapabilitiesFromAgent(nai, nc); @@ -6496,6 +6518,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void updateWakeOnLan(@NonNull LinkProperties lp) { + if (mWolSupportedInterfaces == null) { + mWolSupportedInterfaces = new ArraySet<>(mResources.get().getStringArray( + com.android.connectivity.resources.R.array + .config_wakeonlan_supported_interfaces)); + } lp.setWakeOnLanSupported(mWolSupportedInterfaces.contains(lp.getInterfaceName())); } @@ -7068,6 +7095,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) { putParcelable(bundle, networkAgent.network); } + final boolean includeLocationSensitiveInfo = + (nri.mCallbackFlags & NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) != 0; switch (notificationType) { case ConnectivityManager.CALLBACK_AVAILABLE: { final NetworkCapabilities nc = @@ -7076,7 +7105,8 @@ public class ConnectivityService extends IConnectivityManager.Stub putParcelable( bundle, createWithLocationInfoSanitizedIfNecessaryWhenParceled( - nc, nri.mUid, nrForCallback.getRequestorPackageName(), + nc, includeLocationSensitiveInfo, nri.mUid, + nrForCallback.getRequestorPackageName(), nri.mCallingAttributionTag)); putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions( networkAgent.linkProperties, nri.mPid, nri.mUid)); @@ -7096,7 +7126,8 @@ public class ConnectivityService extends IConnectivityManager.Stub putParcelable( bundle, createWithLocationInfoSanitizedIfNecessaryWhenParceled( - netCap, nri.mUid, nrForCallback.getRequestorPackageName(), + netCap, includeLocationSensitiveInfo, nri.mUid, + nrForCallback.getRequestorPackageName(), nri.mCallingAttributionTag)); break; } @@ -7938,6 +7969,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // and is still connected. NetworkInfo info = new NetworkInfo(nai.networkInfo); info.setType(type); + filterForLegacyLockdown(info); if (state != DetailedState.DISCONNECTED) { info.setDetailedState(state, null, info.getExtraInfo()); sendConnectedBroadcast(info); @@ -8052,8 +8084,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public String getCaptivePortalServerUrl() { enforceNetworkStackOrSettingsPermission(); - String settingUrl = mContext.getResources().getString( - R.string.config_networkCaptivePortalServerUrl); + String settingUrl = mResources.get().getString( + com.android.connectivity.resources.R.string.config_networkCaptivePortalServerUrl); if (!TextUtils.isEmpty(settingUrl)) { return settingUrl; diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 81d4b9da63c8..4c3c6ef21fc5 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -56,6 +56,7 @@ import android.system.Os; import android.system.OsConstants; import android.text.TextUtils; import android.util.Log; +import android.util.Range; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -756,13 +757,9 @@ public class IpSecService extends IIpSecService.Stub { } } - // These values have been reserved in NetIdManager - @VisibleForTesting static final int TUN_INTF_NETID_START = 0xFC00; - - public static final int TUN_INTF_NETID_RANGE = 0x0400; - private final SparseBooleanArray mTunnelNetIds = new SparseBooleanArray(); - private int mNextTunnelNetIdIndex = 0; + final Range<Integer> mNetIdRange = ConnectivityManager.getIpSecNetIdRange(); + private int mNextTunnelNetId = mNetIdRange.getLower(); /** * Reserves a netId within the range of netIds allocated for IPsec tunnel interfaces @@ -775,11 +772,13 @@ public class IpSecService extends IIpSecService.Stub { */ @VisibleForTesting int reserveNetId() { + final int range = mNetIdRange.getUpper() - mNetIdRange.getLower() + 1; synchronized (mTunnelNetIds) { - for (int i = 0; i < TUN_INTF_NETID_RANGE; i++) { - int index = mNextTunnelNetIdIndex; - int netId = index + TUN_INTF_NETID_START; - if (++mNextTunnelNetIdIndex >= TUN_INTF_NETID_RANGE) mNextTunnelNetIdIndex = 0; + for (int i = 0; i < range; i++) { + final int netId = mNextTunnelNetId; + if (++mNextTunnelNetId > mNetIdRange.getUpper()) { + mNextTunnelNetId = mNetIdRange.getLower(); + } if (!mTunnelNetIds.get(netId)) { mTunnelNetIds.put(netId, true); return netId; diff --git a/services/core/java/com/android/server/NetIdManager.java b/services/core/java/com/android/server/NetIdManager.java index 097fb3ae47e3..61925c80a22b 100644 --- a/services/core/java/com/android/server/NetIdManager.java +++ b/services/core/java/com/android/server/NetIdManager.java @@ -17,6 +17,7 @@ package com.android.server; import android.annotation.NonNull; +import android.net.ConnectivityManager; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; @@ -31,7 +32,7 @@ public class NetIdManager { // Sequence number for Networks; keep in sync with system/netd/NetworkController.cpp public static final int MIN_NET_ID = 100; // some reserved marks // Top IDs reserved by IpSecService - public static final int MAX_NET_ID = 65535 - IpSecService.TUN_INTF_NETID_RANGE; + public static final int MAX_NET_ID = ConnectivityManager.getIpSecNetIdRange().getLower() - 1; @GuardedBy("mNetIdInUse") private final SparseBooleanArray mNetIdInUse = new SparseBooleanArray(); diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index e12586bfdc06..c9836001da77 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -18,6 +18,7 @@ per-file ServiceWatcher.java = sooniln@google.com per-file *Alarm* = file:/apex/jobscheduler/OWNERS per-file *AppOp* = file:/core/java/android/permission/OWNERS +per-file *Battery* = file:/BATTERY_STATS_OWNERS per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index f2782f64995a..18d47c6fb7b3 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -51,6 +51,7 @@ import android.graphics.drawable.Icon; import android.hardware.ISensorPrivacyListener; import android.hardware.ISensorPrivacyManager; import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManagerInternal; import android.os.Binder; import android.os.Environment; import android.os.Handler; @@ -67,6 +68,7 @@ import android.service.SensorPrivacyServiceDumpProto; import android.service.SensorPrivacyUserProto; import android.text.Html; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Log; @@ -80,6 +82,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FunctionalUtils; import com.android.internal.util.XmlUtils; @@ -137,6 +140,8 @@ public final class SensorPrivacyService extends SystemService { private final ActivityManager mActivityManager; private final ActivityTaskManager mActivityTaskManager; + private SensorPrivacyManagerInternalImpl mSensorPrivacyManagerInternal; + public SensorPrivacyService(Context context) { super(context); mUserManagerInternal = getLocalService(UserManagerInternal.class); @@ -148,6 +153,9 @@ public final class SensorPrivacyService extends SystemService { @Override public void onStart() { publishBinderService(Context.SENSOR_PRIVACY_SERVICE, mSensorPrivacyServiceImpl); + mSensorPrivacyManagerInternal = new SensorPrivacyManagerInternalImpl(); + publishLocalService(SensorPrivacyManagerInternal.class, + mSensorPrivacyManagerInternal); } class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub implements @@ -1108,6 +1116,7 @@ public final class SensorPrivacyService extends SystemService { } public void handleSensorPrivacyChanged(int userId, int sensor, boolean enabled) { + mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled); SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser = mIndividualSensorListeners.get(userId); if (listenersForUser == null) { @@ -1168,4 +1177,87 @@ public final class SensorPrivacyService extends SystemService { c.accept(userIds[i]); } } + + private class SensorPrivacyManagerInternalImpl extends SensorPrivacyManagerInternal { + + private ArrayMap<Integer, ArrayMap<Integer, ArraySet<OnSensorPrivacyChangedListener>>> + mListeners = new ArrayMap<>(); + private ArrayMap<Integer, ArraySet<OnUserSensorPrivacyChangedListener>> mAllUserListeners = + new ArrayMap<>(); + + private final Object mLock = new Object(); + + private void dispatch(int userId, int sensor, boolean enabled) { + synchronized (mLock) { + ArraySet<OnUserSensorPrivacyChangedListener> allUserSensorListeners = + mAllUserListeners.get(sensor); + if (allUserSensorListeners != null) { + for (int i = 0; i < allUserSensorListeners.size(); i++) { + OnUserSensorPrivacyChangedListener listener = + allUserSensorListeners.valueAt(i); + BackgroundThread.getHandler().post(() -> + listener.onSensorPrivacyChanged(userId, enabled)); + } + } + + ArrayMap<Integer, ArraySet<OnSensorPrivacyChangedListener>> userSensorListeners = + mListeners.get(userId); + if (userSensorListeners != null) { + ArraySet<OnSensorPrivacyChangedListener> sensorListeners = + userSensorListeners.get(sensor); + if (sensorListeners != null) { + for (int i = 0; i < sensorListeners.size(); i++) { + OnSensorPrivacyChangedListener listener = sensorListeners.valueAt(i); + BackgroundThread.getHandler().post(() -> + listener.onSensorPrivacyChanged(enabled)); + } + } + } + } + } + + @Override + public boolean isSensorPrivacyEnabled(int userId, int sensor) { + return SensorPrivacyService.this + .mSensorPrivacyServiceImpl.isIndividualSensorPrivacyEnabled(userId, sensor); + } + + @Override + public void addSensorPrivacyListener(int userId, int sensor, + OnSensorPrivacyChangedListener listener) { + synchronized (mLock) { + ArrayMap<Integer, ArraySet<OnSensorPrivacyChangedListener>> userSensorListeners = + mListeners.get(userId); + if (userSensorListeners == null) { + userSensorListeners = new ArrayMap<>(); + mListeners.put(userId, userSensorListeners); + } + + ArraySet<OnSensorPrivacyChangedListener> sensorListeners = + userSensorListeners.get(sensor); + if (sensorListeners == null) { + sensorListeners = new ArraySet<>(); + userSensorListeners.put(sensor, sensorListeners); + } + + sensorListeners.add(listener); + } + } + + @Override + public void addSensorPrivacyListenerForAllUsers(int sensor, + OnUserSensorPrivacyChangedListener listener) { + synchronized (mLock) { + ArraySet<OnUserSensorPrivacyChangedListener> sensorListeners = + mAllUserListeners.get(sensor); + if (sensorListeners == null) { + sensorListeners = new ArraySet<>(); + mAllUserListeners.put(sensor, sensorListeners); + } + + sensorListeners.add(listener); + } + } + } + } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 27b648e53a38..740a1c16a486 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -4553,6 +4553,13 @@ class StorageManagerService extends IStorageManager.Stub private final List<StorageManagerInternal.ResetListener> mResetListeners = new ArrayList<>(); + @Override + public boolean isFuseMounted(int userId) { + synchronized (mLock) { + return mFuseMountedUser.contains(userId); + } + } + /** * Check if fuse is running in target user, if it's running then setup its storage dirs. * Return true if storage dirs are mounted. diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 95af84293377..a09dbc7e599d 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -21,7 +21,9 @@ } ], "file_patterns": ["NotificationManagerService\\.java"] - }, + } + ], + "presubmit-large": [ { "name": "CtsScopedStorageCoreHostTest", "file_patterns": ["StorageManagerService\\.java"] diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index a9904ba0de91..5adbdff150ea 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -318,7 +318,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private int[] mDataEnabledReason; - private Map<Integer, Long> mAllowedNetworkTypesList; + private int[] mAllowedNetworkTypeReason; + private long[] mAllowedNetworkTypeValue; /** * Per-phone map of precise data connection state. The key of the map is the pair of transport @@ -383,7 +384,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private boolean isPrivilegedPhoneStatePermissionRequired(Set<Integer> events) { return events.contains(TelephonyCallback.EVENT_SRVCC_STATE_CHANGED) || events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED) - || events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED); + || events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED) + || events.contains(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED); } private static final int MSG_USER_SWITCHED = 1; @@ -527,6 +529,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones); mIsDataEnabled= copyOf(mIsDataEnabled, mNumPhones); mDataEnabledReason = copyOf(mDataEnabledReason, mNumPhones); + mAllowedNetworkTypeReason = copyOf(mAllowedNetworkTypeReason, mNumPhones); + mAllowedNetworkTypeValue = copyOf(mAllowedNetworkTypeValue, mNumPhones); // ds -> ss switch. if (mNumPhones < oldNumPhones) { @@ -571,6 +575,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mIsDataEnabled[i] = false; mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER; mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build()); + mAllowedNetworkTypeReason[i] = -1; + mAllowedNetworkTypeValue[i] = -1; } } @@ -630,9 +636,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mBarringInfo = new ArrayList<>(); mTelephonyDisplayInfos = new TelephonyDisplayInfo[numPhones]; mPhysicalChannelConfigs = new ArrayList<>(); - mAllowedNetworkTypesList = new HashMap<>(); + mAllowedNetworkTypeReason = new int[numPhones]; + mAllowedNetworkTypeValue = new long[numPhones]; mIsDataEnabled = new boolean[numPhones]; mDataEnabledReason = new int[numPhones]; + for (int i = 0; i < numPhones; i++) { mCallState[i] = TelephonyManager.CALL_STATE_IDLE; mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE; @@ -665,6 +673,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mIsDataEnabled[i] = false; mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER; mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build()); + mAllowedNetworkTypeReason[i] = -1; + mAllowedNetworkTypeValue[i] = -1; } mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -1172,14 +1182,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if (events.contains( - TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)) { - try { - r.callback.onAllowedNetworkTypesChanged(mAllowedNetworkTypesList); - } catch (RemoteException ex) { - remove(r.binder); - } - } } } } @@ -2454,18 +2456,19 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { * * @param phoneId the phone id. * @param subId the subId. - * @param allowedNetworkTypesList Map associating all allowed network type reasons with reason's - * allowed network type values. + * @param reason the allowed network type reason. + * @param allowedNetworkType the allowed network type value. */ - public void notifyAllowedNetworkTypesChanged(int phoneId, int subId, - Map allowedNetworkTypesList) { + public void notifyAllowedNetworkTypesChanged(int phoneId, int subId, int reason, + long allowedNetworkType) { if (!checkNotifyPermission("notifyAllowedNetworkTypesChanged()")) { return; } synchronized (mRecords) { if (validatePhoneId(phoneId)) { - mAllowedNetworkTypesList = allowedNetworkTypesList; + mAllowedNetworkTypeReason[phoneId] = reason; + mAllowedNetworkTypeValue[phoneId] = allowedNetworkType; for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( @@ -2473,10 +2476,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { && idMatch(r.subId, subId, phoneId)) { try { if (VDBG) { - log("notifyAllowedNetworkTypesChanged: AllowedNetworkTypesList= " - + mAllowedNetworkTypesList.toString()); + log("notifyAllowedNetworkTypesChanged: reason= " + reason + + ", allowed network type:" + + TelephonyManager.convertNetworkTypeBitmaskToString( + allowedNetworkType)); } - r.callback.onAllowedNetworkTypesChanged(mAllowedNetworkTypesList); + r.callback.onAllowedNetworkTypesChanged(reason, allowedNetworkType); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -2531,6 +2536,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mTelephonyDisplayInfo=" + mTelephonyDisplayInfos[i]); pw.println("mIsDataEnabled=" + mIsDataEnabled); pw.println("mDataEnabledReason=" + mDataEnabledReason); + pw.println("mAllowedNetworkTypeReason=" + mAllowedNetworkTypeReason[i]); + pw.println("mAllowedNetworkTypeValue=" + mAllowedNetworkTypeValue[i]); pw.decreaseIndent(); } pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState); diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index cd3892da43e4..140f24f7cf8f 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -666,6 +666,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull IVcnUnderlyingNetworkPolicyListener listener) { requireNonNull(listener, "listener was null"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.NETWORK_FACTORY, + "Must have permission NETWORK_FACTORY to unregister a policy listener"); + Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { PolicyListenerBinderDeath listenerBinderDeath = diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 68c4a7336745..673749c08318 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1841,7 +1841,7 @@ public final class ActiveServices { ServiceState stracker = r.getTracker(); if (stracker != null) { stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), - r.lastActivity); + SystemClock.uptimeMillis()); } } if (alreadyStartedOp) { @@ -1863,7 +1863,7 @@ public final class ActiveServices { ServiceState stracker = r.getTracker(); if (stracker != null) { stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), - r.lastActivity); + SystemClock.uptimeMillis()); } mAm.mAppOpsService.finishOperation( AppOpsManager.getToken(mAm.mAppOpsService), @@ -3765,6 +3765,7 @@ public final class ActiveServices { } } + final long now = SystemClock.uptimeMillis(); // Check to see if the service had been started as foreground, but being // brought down before actually showing a notification. That is not allowed. if (r.fgRequired) { @@ -3774,8 +3775,7 @@ public final class ActiveServices { r.fgWaiting = false; ServiceState stracker = r.getTracker(); if (stracker != null) { - stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), - r.lastActivity); + stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), now); } mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); @@ -3834,8 +3834,7 @@ public final class ActiveServices { decActiveForegroundAppLocked(smap, r); ServiceState stracker = r.getTracker(); if (stracker != null) { - stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), - r.lastActivity); + stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), now); } mAm.mAppOpsService.finishOperation( AppOpsManager.getToken(mAm.mAppOpsService), @@ -3902,7 +3901,6 @@ public final class ActiveServices { } int memFactor = mAm.mProcessStats.getMemFactorLocked(); - long now = SystemClock.uptimeMillis(); if (r.tracker != null) { r.tracker.setStarted(false, memFactor, now); r.tracker.setBound(false, memFactor, now); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8ea6194a9535..06a1abb72607 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -44,11 +44,11 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.FactoryTest.FACTORY_TEST_OFF; -import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import static android.os.IServiceManager.DUMP_FLAG_PROTO; +import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALLOW_LISTED; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.Process.BLUETOOTH_UID; @@ -273,6 +273,9 @@ import android.os.TransactionTooLargeException; import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; +import android.os.incremental.IIncrementalService; +import android.os.incremental.IncrementalManager; +import android.os.incremental.IncrementalMetrics; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.provider.DeviceConfig; @@ -7697,18 +7700,32 @@ public class ActivityManagerService extends IActivityManager.Stub */ void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName, ApplicationErrorReport.CrashInfo crashInfo) { - boolean isPackageLoading = false; + boolean isIncremental = false; + float loadingProgress = 1; + long millisSinceOldestPendingRead = 0; // Notify package manager service to possibly update package state if (r != null && r.info != null && r.info.packageName != null) { + final String codePath = r.info.getCodePath(); mPackageManagerInt.notifyPackageCrashOrAnr(r.info.packageName); IncrementalStatesInfo incrementalStatesInfo = mPackageManagerInt.getIncrementalStatesInfo(r.info.packageName, r.uid, r.userId); - isPackageLoading = incrementalStatesInfo.isLoading(); - if (isPackageLoading) { - // Report in the main log that the package is still loading - Slog.e(TAG, "App crashed when package " + r.info.packageName + " is " - + ((int) (incrementalStatesInfo.getProgress() * 100)) + "% loaded."); + if (incrementalStatesInfo != null) { + loadingProgress = incrementalStatesInfo.getProgress(); + } + isIncremental = IncrementalManager.isIncrementalPath(codePath); + if (isIncremental) { + // Report in the main log about the incremental package + Slog.e(TAG, "App crashed on incremental package " + r.info.packageName + + " which is " + ((int) (loadingProgress * 100)) + "% loaded."); + final IBinder incrementalService = ServiceManager.getService( + Context.INCREMENTAL_SERVICE); + if (incrementalService != null) { + final IncrementalManager incrementalManager = new IncrementalManager( + IIncrementalService.Stub.asInterface(incrementalService)); + IncrementalMetrics metrics = incrementalManager.getMetrics(codePath); + millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead(); + } } } @@ -7737,7 +7754,7 @@ public class ActivityManagerService extends IActivityManager.Stub processName.equals("system_server") ? ServerProtoEnums.SYSTEM_SERVER : (r != null) ? r.getProcessClassEnum() : ServerProtoEnums.ERROR_SOURCE_UNKNOWN, - isPackageLoading + isIncremental, loadingProgress, millisSinceOldestPendingRead ); final int relaunchReason = r == null ? RELAUNCH_REASON_NONE diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index dbfa7f34c6f9..6b9fc0718879 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -32,6 +32,7 @@ import android.net.ConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.Network; import android.net.NetworkCapabilities; +import android.os.BatteryManagerInternal; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.BatteryUsageStats; @@ -184,6 +185,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub } }; + private BatteryManagerInternal mBatteryManagerInternal; + private void populatePowerEntityMaps() { PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo(); if (entities == null) { @@ -370,6 +373,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub Slog.e(TAG, "Could not register PowerStatsInternal"); } } + mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); Watchdog.getInstance().addMonitor(this); @@ -2715,4 +2719,44 @@ public final class BatteryStatsService extends IBatteryStats.Stub }); } } + + /** + * Sets battery AC charger to enabled/disabled, and freezes the battery state. + */ + @Override + public void setChargerAcOnline(boolean online, boolean forceUpdate) { + mBatteryManagerInternal.setChargerAcOnline(online, forceUpdate); + } + + /** + * Sets battery level, and freezes the battery state. + */ + @Override + public void setBatteryLevel(int level, boolean forceUpdate) { + mBatteryManagerInternal.setBatteryLevel(level, forceUpdate); + } + + /** + * Unplugs battery, and freezes the battery state. + */ + @Override + public void unplugBattery(boolean forceUpdate) { + mBatteryManagerInternal.unplugBattery(forceUpdate); + } + + /** + * Unfreezes battery state, returning to current hardware values. + */ + @Override + public void resetBattery(boolean forceUpdate) { + mBatteryManagerInternal.resetBattery(forceUpdate); + } + + /** + * Suspend charging even if plugged in. + */ + @Override + public void suspendBatteryInput() { + mBatteryManagerInternal.suspendBatteryInput(); + } } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 81c4c8605fb4..e79f09665153 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -1416,7 +1416,7 @@ public final class BroadcastQueue { } } if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID && - r.requiredPermissions != null && r.requiredPermissions.length > 0) { + r.requiredPermissions != null && r.requiredPermissions.length > 0) { for (int i = 0; i < r.requiredPermissions.length; i++) { String requiredPermission = r.requiredPermissions[i]; try { @@ -1424,7 +1424,7 @@ public final class BroadcastQueue { checkPermission(requiredPermission, info.activityInfo.applicationInfo.packageName, UserHandle - .getUserId(info.activityInfo.applicationInfo.uid)); + .getUserId(info.activityInfo.applicationInfo.uid)); } catch (RemoteException e) { perm = PackageManager.PERMISSION_DENIED; } @@ -1439,36 +1439,18 @@ public final class BroadcastQueue { break; } int appOp = AppOpsManager.permissionToOpCode(requiredPermission); - if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp - && mService.getAppOpsManager().noteOpNoThrow(appOp, - info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, - null /* default featureId */, - "Broadcast delivered to " + info.activityInfo.name) - != AppOpsManager.MODE_ALLOWED) { - Slog.w(TAG, "Appop Denial: receiving " - + r.intent + " to " - + component.flattenToShortString() - + " requires appop " + AppOpsManager.permissionToOp( - requiredPermission) - + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - skip = true; - break; + if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) { + if (!noteOpForManifestReceiver(appOp, r, info, component)) { + skip = true; + break; + } } } } - if (!skip && r.appOp != AppOpsManager.OP_NONE - && mService.getAppOpsManager().noteOpNoThrow(r.appOp, - info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, - null /* default featureId */, "Broadcast delivered to " + info.activityInfo.name) - != AppOpsManager.MODE_ALLOWED) { - Slog.w(TAG, "Appop Denial: receiving " - + r.intent + " to " - + component.flattenToShortString() - + " requires appop " + AppOpsManager.opToName(r.appOp) - + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - skip = true; + if (!skip && r.appOp != AppOpsManager.OP_NONE) { + if (!noteOpForManifestReceiver(r.appOp, r, info, component)) { + skip = true; + } } boolean isSingleton = false; try { @@ -1717,6 +1699,40 @@ public final class BroadcastQueue { mPendingBroadcastRecvIndex = recIdx; } + private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info, + ComponentName component) { + if (info.activityInfo.attributionTags == null) { + return noteOpForManifestReceiverInner(appOp, r, info, component, null); + } else { + // Attribution tags provided, noteOp each tag + for (String tag : info.activityInfo.attributionTags) { + if (!noteOpForManifestReceiverInner(appOp, r, info, component, tag)) { + return false; + } + } + return true; + } + } + + private boolean noteOpForManifestReceiverInner(int appOp, BroadcastRecord r, ResolveInfo info, + ComponentName component, String tag) { + if (mService.getAppOpsManager().noteOpNoThrow(appOp, + info.activityInfo.applicationInfo.uid, + info.activityInfo.packageName, + tag, + "Broadcast delivered to " + info.activityInfo.name) + != AppOpsManager.MODE_ALLOWED) { + Slog.w(TAG, "Appop Denial: receiving " + + r.intent + " to " + + component.flattenToShortString() + + " requires appop " + AppOpsManager.opToName(appOp) + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + return false; + } + return true; + } + private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) { if (r == null || proc == null || !r.allowBackgroundActivityStarts) { return; diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index d03a47afed8a..93f30cc8ac5e 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -26,12 +26,18 @@ import android.app.AnrController; import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; import android.content.ComponentName; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IncrementalStatesInfo; import android.content.pm.PackageManagerInternal; +import android.os.IBinder; import android.os.Message; import android.os.Process; +import android.os.ServiceManager; import android.os.SystemClock; +import android.os.incremental.IIncrementalService; +import android.os.incremental.IncrementalManager; +import android.os.incremental.IncrementalMetrics; import android.provider.Settings; import android.util.EventLog; import android.util.Slog; @@ -294,14 +300,31 @@ class ProcessErrorStateRecord { } // Check if package is still being loaded - boolean isPackageLoading = false; + boolean isIncremental = false; + float loadingProgress = 1; + long millisSinceOldestPendingRead = 0; final PackageManagerInternal packageManagerInternal = mService.getPackageManagerInternal(); if (aInfo != null && aInfo.packageName != null) { IncrementalStatesInfo incrementalStatesInfo = packageManagerInternal.getIncrementalStatesInfo( aInfo.packageName, mApp.uid, mApp.userId); if (incrementalStatesInfo != null) { - isPackageLoading = incrementalStatesInfo.isLoading(); + loadingProgress = incrementalStatesInfo.getProgress(); + } + final String codePath = aInfo.getCodePath(); + isIncremental = IncrementalManager.isIncrementalPath(codePath); + if (isIncremental) { + // Report in the main log that the incremental package is still loading + Slog.e(TAG, "App crashed on incremental package " + aInfo.packageName + + " which is " + ((int) (loadingProgress * 100)) + "% loaded."); + final IBinder incrementalService = ServiceManager.getService( + Context.INCREMENTAL_SERVICE); + if (incrementalService != null) { + final IncrementalManager incrementalManager = new IncrementalManager( + IIncrementalService.Stub.asInterface(incrementalService)); + IncrementalMetrics metrics = incrementalManager.getMetrics(codePath); + millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead(); + } } } @@ -322,10 +345,8 @@ class ProcessErrorStateRecord { info.append("Parent: ").append(parentShortComponentName).append("\n"); } - if (isPackageLoading) { - // Report in the main log that the package is still loading - final float loadingProgress = packageManagerInternal.getIncrementalStatesInfo( - aInfo.packageName, mApp.uid, mApp.userId).getProgress(); + if (isIncremental) { + // Report in the main log about the incremental package info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n"); } @@ -412,7 +433,8 @@ class ProcessErrorStateRecord { ? FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__FOREGROUND : FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND, mApp.getProcessClassEnum(), - (mApp.info != null) ? mApp.info.packageName : "", isPackageLoading); + (mApp.info != null) ? mApp.info.packageName : "", + isIncremental, loadingProgress, millisSinceOldestPendingRead); final ProcessRecord parentPr = parentProcess != null ? (ProcessRecord) parentProcess.mOwner : null; mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName, diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 38330fe770fb..ed8d696f98c4 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2315,11 +2315,12 @@ public final class ProcessList { StorageManagerInternal storageManagerInternal = LocalServices.getService( StorageManagerInternal.class); if (needsStorageDataIsolation(storageManagerInternal, app)) { - bindMountAppStorageDirs = true; - if (pkgDataInfoMap == null || - !storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(), - app.processName)) { - // Cannot prepare Android/app and Android/obb directory or inode == 0, + // We will run prepareStorageDirs() after we trigger zygote fork, so it won't + // slow down app starting speed as those dirs might not be cached. + if (pkgDataInfoMap != null && storageManagerInternal.isFuseMounted(userId)) { + bindMountAppStorageDirs = true; + } else { + // Fuse is not mounted or inode == 0, // so we won't mount it in zygote, but resume the mount after unlocking device. app.setBindMountPending(true); bindMountAppStorageDirs = false; @@ -2367,6 +2368,13 @@ public final class ProcessList { allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs, new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()}); } + // This runs after Process.start() as this method may block app process starting time + // if dir is not cached. Running this method after Process.start() can make it + // cache the dir asynchronously, so zygote can use it without waiting for it. + if (bindMountAppStorageDirs) { + storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(), + app.processName); + } checkSlow(startTime, "startProcess: returned from zygote!"); return startResult; } finally { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index caf2510e5b1c..ec2020f94969 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -122,7 +122,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; /** * Helper class for {@link ActivityManagerService} responsible for multi-user functionality. @@ -518,18 +517,12 @@ class UserController implements Handler.Callback { if (!mInjector.getUserManager().isPreCreated(userId)) { mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG, userId, 0)); - Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null); - intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT - | Intent.FLAG_RECEIVER_OFFLOAD - | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - mInjector.broadcastIntent(intent, null, resultTo, 0, null, null, - new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED}, - AppOpsManager.OP_NONE, - getTemporaryAppAllowlistBroadcastOptions(REASON_LOCKED_BOOT_COMPLETED) - .toBundle(), true, - false, MY_PID, SYSTEM_UID, - Binder.getCallingUid(), Binder.getCallingPid(), userId); + // In case of headless system user mode, do not send boot complete broadcast for + // system user as it is sent by sendBootCompleted call. + if (!(UserManager.isHeadlessSystemUserMode() && uss.mHandle.isSystem())) { + // ACTION_LOCKED_BOOT_COMPLETED + sendLockedBootCompletedBroadcast(resultTo, userId); + } } } @@ -552,6 +545,21 @@ class UserController implements Handler.Callback { } } + private void sendLockedBootCompletedBroadcast(IIntentReceiver receiver, @UserIdInt int userId) { + final Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT + | Intent.FLAG_RECEIVER_OFFLOAD + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mInjector.broadcastIntent(intent, null, receiver, 0, null, null, + new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED}, + AppOpsManager.OP_NONE, + getTemporaryAppAllowlistBroadcastOptions(REASON_LOCKED_BOOT_COMPLETED) + .toBundle(), true, + false, MY_PID, SYSTEM_UID, + Binder.getCallingUid(), Binder.getCallingPid(), userId); + } + /** * Step from {@link UserState#STATE_RUNNING_LOCKED} to * {@link UserState#STATE_RUNNING_UNLOCKING}. @@ -2167,26 +2175,22 @@ class UserController implements Handler.Callback { } void sendBootCompleted(IIntentReceiver resultTo) { - final boolean systemUserFinishedBooting; - // Get a copy of mStartedUsers to use outside of lock SparseArray<UserState> startedUsers; synchronized (mLock) { - systemUserFinishedBooting = mCurrentUserId != UserHandle.USER_SYSTEM; startedUsers = mStartedUsers.clone(); } for (int i = 0; i < startedUsers.size(); i++) { UserState uss = startedUsers.valueAt(i); - if (systemUserFinishedBooting && uss.mHandle.isSystem()) { - // On Automotive, at this point the system user has already been started and - // unlocked, and some of the tasks we do here have already been done. So skip those - // in that case. - // TODO(b/132262830): this workdound shouldn't be necessary once we move the - // headless-user start logic to UserManager-land - Slog.d(TAG, "sendBootCompleted(): skipping on non-current system user"); - continue; + if (!UserManager.isHeadlessSystemUserMode()) { + finishUserBoot(uss, resultTo); + } else if (uss.mHandle.isSystem()) { + // In case of headless system user mode, send only locked boot complete broadcast + // for system user since finishUserBoot call will be made using other code path; + // for non-system user, do nothing since finishUserBoot will be called elsewhere. + sendLockedBootCompletedBroadcast(resultTo, uss.mHandle.getIdentifier()); + return; } - finishUserBoot(uss, resultTo); } } diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java index 3e32380b60a9..2982545a5e6f 100644 --- a/services/core/java/com/android/server/app/GameManagerSettings.java +++ b/services/core/java/com/android/server/app/GameManagerSettings.java @@ -137,6 +137,11 @@ public class GameManagerSettings { boolean readPersistentDataLocked() { mGameModes.clear(); + if (!mSettingsFile.exists()) { + Slog.v(GameManagerService.TAG, "Settings file doesn't exists, skip reading"); + return false; + } + try { final FileInputStream str = mSettingsFile.openRead(); diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 6614e06aba8c..1122f7f4115a 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2098,26 +2098,28 @@ public class AppOpsService extends IAppOpsService.Stub { ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter, beginTimeMillis, endTimeMillis, flags); Objects.requireNonNull(callback, "callback cannot be null"); - ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class); - boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid()); - boolean isCallerSystem = Binder.getCallingPid() == Process.myPid(); - boolean isCallerPermissionController; - try { - isCallerPermissionController = pm.getPackageUid( - mContext.getPackageManager().getPermissionControllerPackageName(), 0) - == Binder.getCallingUid(); - } catch (PackageManager.NameNotFoundException doesNotHappen) { - return; - } + boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid(); + if (!isSelfRequest) { + boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid()); + boolean isCallerSystem = Binder.getCallingPid() == Process.myPid(); + boolean isCallerPermissionController; + try { + isCallerPermissionController = pm.getPackageUid( + mContext.getPackageManager().getPermissionControllerPackageName(), 0) + == Binder.getCallingUid(); + } catch (PackageManager.NameNotFoundException doesNotHappen) { + return; + } - if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController) { - mHandler.post(() -> callback.sendResult(new Bundle())); - return; - } + if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController) { + mHandler.post(() -> callback.sendResult(new Bundle())); + return; + } - mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, - Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps"); + mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, + Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps"); + } final String[] opNamesArray = (opNames != null) ? opNames.toArray(new String[opNames.size()]) : null; diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index ed62abc7d773..2b0157c88136 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -49,8 +49,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; -import libcore.util.EmptyArray; - import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -85,6 +83,8 @@ final class DiscreteRegistry { private static final String TAG = DiscreteRegistry.class.getSimpleName(); private static final long TIMELINE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis(); + private static final long TIMELINE_QUANTIZATION = Duration.ofMinutes(1).toMillis(); + private static final String TAG_HISTORY = "h"; private static final String ATTR_VERSION = "v"; private static final int CURRENT_VERSION = 1; @@ -107,6 +107,8 @@ final class DiscreteRegistry { private static final String ATTR_UID_STATE = "us"; private static final String ATTR_FLAGS = "f"; + private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED; + // Lock for read/write access to on disk state private final Object mOnDiskLock = new Object(); @@ -119,6 +121,9 @@ final class DiscreteRegistry { @GuardedBy("mInMemoryLock") private DiscreteOps mDiscreteOps; + @GuardedBy("mOnDiskLock") + private DiscreteOps mCachedOps = null; + DiscreteRegistry(Object inMemoryLock) { mInMemoryLock = inMemoryLock; mDiscreteAccessDir = new File(new File(Environment.getDataSystemDirectory(), "appops"), @@ -173,23 +178,25 @@ final class DiscreteRegistry { } } } - } - DiscreteOps discreteOps; - synchronized (mInMemoryLock) { - discreteOps = mDiscreteOps; - mDiscreteOps = new DiscreteOps(); - } - if (discreteOps.isEmpty()) { - return; - } - long currentTimeStamp = Instant.now().toEpochMilli(); - try { - final File file = new File(mDiscreteAccessDir, currentTimeStamp + TIMELINE_FILE_SUFFIX); - discreteOps.writeToFile(file); - } catch (Throwable t) { - Slog.e(TAG, - "Error writing timeline state: " + t.getMessage() + " " - + Arrays.toString(t.getStackTrace())); + DiscreteOps discreteOps; + synchronized (mInMemoryLock) { + discreteOps = mDiscreteOps; + mDiscreteOps = new DiscreteOps(); + mCachedOps = null; + } + if (discreteOps.isEmpty()) { + return; + } + long currentTimeStamp = Instant.now().toEpochMilli(); + try { + final File file = new File(mDiscreteAccessDir, + currentTimeStamp + TIMELINE_FILE_SUFFIX); + discreteOps.writeToFile(file); + } catch (Throwable t) { + Slog.e(TAG, + "Error writing timeline state: " + t.getMessage() + " " + + Arrays.toString(t.getStackTrace())); + } } } @@ -197,25 +204,33 @@ final class DiscreteRegistry { long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { - writeAndClearAccessHistory(); - DiscreteOps discreteOps = new DiscreteOps(); - readDiscreteOpsFromDisk(discreteOps, beginTimeMillis, endTimeMillis, filter, uidFilter, - packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter); + DiscreteOps discreteOps = getAndCacheDiscreteOps(); + discreteOps.filter(beginTimeMillis, endTimeMillis, filter, uidFilter, packageNameFilter, + opNamesFilter, attributionTagFilter, flagsFilter); discreteOps.applyToHistoricalOps(result); return; } - private void readDiscreteOpsFromDisk(DiscreteOps discreteOps, long beginTimeMillis, - long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, - @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, - @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + private DiscreteOps getAndCacheDiscreteOps() { + DiscreteOps discreteOps = new DiscreteOps(); + synchronized (mOnDiskLock) { - long historyBeginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF, - ChronoUnit.MILLIS).toEpochMilli(); - if (historyBeginTimeMillis > endTimeMillis) { - return; + synchronized (mInMemoryLock) { + discreteOps.merge(mDiscreteOps); + } + if (mCachedOps == null) { + mCachedOps = new DiscreteOps(); + readDiscreteOpsFromDisk(mCachedOps); } - beginTimeMillis = max(beginTimeMillis, historyBeginTimeMillis); + discreteOps.merge(mCachedOps); + } + return discreteOps; + } + + private void readDiscreteOpsFromDisk(DiscreteOps discreteOps) { + synchronized (mOnDiskLock) { + long beginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF, + ChronoUnit.MILLIS).toEpochMilli(); final File[] files = mDiscreteAccessDir.listFiles(); if (files != null && files.length > 0) { @@ -229,8 +244,7 @@ final class DiscreteRegistry { if (timestamp < beginTimeMillis) { continue; } - discreteOps.readFromFile(f, beginTimeMillis, endTimeMillis, filter, uidFilter, - packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter); + discreteOps.readFromFile(f, beginTimeMillis); } } } @@ -251,15 +265,11 @@ final class DiscreteRegistry { @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps) { - DiscreteOps discreteOps = new DiscreteOps(); - synchronized (mOnDiskLock) { - writeAndClearAccessHistory(); - String[] opNamesFilter = dumpOp == OP_NONE ? EmptyArray.STRING - : new String[]{AppOpsManager.opToPublicName(dumpOp)}; - readDiscreteOpsFromDisk(discreteOps, 0, Instant.now().toEpochMilli(), filter, - uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter, - OP_FLAGS_ALL); - } + DiscreteOps discreteOps = getAndCacheDiscreteOps(); + String[] opNamesFilter = dumpOp == OP_NONE ? null + : new String[]{AppOpsManager.opToPublicName(dumpOp)}; + discreteOps.filter(0, Instant.now().toEpochMilli(), filter, uidFilter, packageNameFilter, + opNamesFilter, attributionTagFilter, OP_FLAGS_ALL); discreteOps.dump(pw, sdf, date, prefix, nDiscreteOps); } @@ -270,7 +280,7 @@ final class DiscreteRegistry { if (!isDiscreteUid(uid)) { return false; } - if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) == 0) { + if ((flags & (OP_FLAGS_DISCRETE)) == 0) { return false; } return true; @@ -298,6 +308,19 @@ final class DiscreteRegistry { mUids = new ArrayMap<>(); } + boolean isEmpty() { + return mUids.isEmpty(); + } + + void merge(DiscreteOps other) { + int nUids = other.mUids.size(); + for (int i = 0; i < nUids; i++) { + int uid = other.mUids.keyAt(i); + DiscreteUidOps uidOps = other.mUids.valueAt(i); + getOrCreateDiscreteUidOps(uid).merge(uidOps); + } + } + void addDiscreteAccess(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { @@ -305,6 +328,25 @@ final class DiscreteRegistry { uidState, accessTime, accessDuration); } + private void filter(long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, + @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + if ((filter & FILTER_BY_UID) != 0) { + ArrayMap<Integer, DiscreteUidOps> uids = new ArrayMap<>(); + uids.put(uidFilter, getOrCreateDiscreteUidOps(uidFilter)); + mUids = uids; + } + int nUids = mUids.size(); + for (int i = nUids - 1; i >= 0; i--) { + mUids.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, packageNameFilter, + opNamesFilter, attributionTagFilter, flagsFilter); + if (mUids.valueAt(i).isEmpty()) { + mUids.removeAt(i); + } + } + } + private void applyToHistoricalOps(AppOpsManager.HistoricalOps result) { int nUids = mUids.size(); for (int i = 0; i < nUids; i++) { @@ -353,14 +395,7 @@ final class DiscreteRegistry { return result; } - boolean isEmpty() { - return mUids.isEmpty(); - } - - private void readFromFile(File f, long beginTimeMillis, long endTimeMillis, - @AppOpsManager.HistoricalOpsRequestFilter int filter, - int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, - @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + private void readFromFile(File f, long beginTimeMillis) { try { FileInputStream stream = new FileInputStream(f); TypedXmlPullParser parser = Xml.resolvePullParser(stream); @@ -377,12 +412,7 @@ final class DiscreteRegistry { while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_UID.equals(parser.getName())) { int uid = parser.getAttributeInt(null, ATTR_UID, -1); - if ((filter & FILTER_BY_UID) != 0 && uid != uidFilter) { - continue; - } - getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis, - endTimeMillis, filter, packageNameFilter, opNamesFilter, - attributionTagFilter, flagsFilter); + getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis); } } } catch (Throwable t) { @@ -400,6 +430,38 @@ final class DiscreteRegistry { mPackages = new ArrayMap<>(); } + boolean isEmpty() { + return mPackages.isEmpty(); + } + + void merge(DiscreteUidOps other) { + int nPackages = other.mPackages.size(); + for (int i = 0; i < nPackages; i++) { + String packageName = other.mPackages.keyAt(i); + DiscretePackageOps p = other.mPackages.valueAt(i); + getOrCreateDiscretePackageOps(packageName).merge(p); + } + } + + private void filter(long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + if ((filter & FILTER_BY_PACKAGE_NAME) != 0) { + ArrayMap<String, DiscretePackageOps> packages = new ArrayMap<>(); + packages.put(packageNameFilter, getOrCreateDiscretePackageOps(packageNameFilter)); + mPackages = packages; + } + int nPackages = mPackages.size(); + for (int i = nPackages - 1; i >= 0; i--) { + mPackages.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, opNamesFilter, + attributionTagFilter, flagsFilter); + if (mPackages.valueAt(i).isEmpty()) { + mPackages.removeAt(i); + } + } + } + void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { @@ -445,22 +507,12 @@ final class DiscreteRegistry { } } - void deserialize(TypedXmlPullParser parser, long beginTimeMillis, - long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, - @Nullable String packageNameFilter, - @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, - @AppOpsManager.OpFlags int flagsFilter) throws Exception { + void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception { int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_PACKAGE.equals(parser.getName())) { String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); - if ((filter & FILTER_BY_PACKAGE_NAME) != 0 - && !packageName.equals(packageNameFilter)) { - continue; - } - getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis, - endTimeMillis, filter, opNamesFilter, attributionTagFilter, - flagsFilter); + getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis); } } } @@ -473,6 +525,10 @@ final class DiscreteRegistry { mPackageOps = new ArrayMap<>(); } + boolean isEmpty() { + return mPackageOps.isEmpty(); + } + void addDiscreteAccess(int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { @@ -480,6 +536,35 @@ final class DiscreteRegistry { accessDuration); } + void merge(DiscretePackageOps other) { + int nOps = other.mPackageOps.size(); + for (int i = 0; i < nOps; i++) { + int opId = other.mPackageOps.keyAt(i); + DiscreteOp op = other.mPackageOps.valueAt(i); + getOrCreateDiscreteOp(opId).merge(op); + } + } + + private void filter(long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, + @AppOpsManager.OpFlags int flagsFilter) { + int nOps = mPackageOps.size(); + for (int i = nOps - 1; i >= 0; i--) { + int opId = mPackageOps.keyAt(i); + if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter, + AppOpsManager.opToPublicName(opId))) { + mPackageOps.removeAt(i); + continue; + } + mPackageOps.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, + attributionTagFilter, flagsFilter); + if (mPackageOps.valueAt(i).isEmpty()) { + mPackageOps.removeAt(i); + } + } + } + private DiscreteOp getOrCreateDiscreteOp(int op) { DiscreteOp result = mPackageOps.get(op); if (result == null) { @@ -519,20 +604,12 @@ final class DiscreteRegistry { } } - void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis, - @AppOpsManager.HistoricalOpsRequestFilter int filter, - @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, - @AppOpsManager.OpFlags int flagsFilter) throws Exception { + void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception { int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_OP.equals(parser.getName())) { int op = parser.getAttributeInt(null, ATTR_OP_ID); - if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter, - AppOpsManager.opToPublicName(op))) { - continue; - } - getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis, endTimeMillis, - filter, attributionTagFilter, flagsFilter); + getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis); } } } @@ -545,31 +622,66 @@ final class DiscreteRegistry { mAttributedOps = new ArrayMap<>(); } + boolean isEmpty() { + return mAttributedOps.isEmpty(); + } + + void merge(DiscreteOp other) { + int nTags = other.mAttributedOps.size(); + for (int i = 0; i < nTags; i++) { + String tag = other.mAttributedOps.keyAt(i); + List<DiscreteOpEvent> otherEvents = other.mAttributedOps.valueAt(i); + List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList(tag); + mAttributedOps.put(tag, stableListMerge(events, otherEvents)); + } + } + + private void filter(long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) { + if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0) { + ArrayMap<String, List<DiscreteOpEvent>> attributedOps = new ArrayMap<>(); + attributedOps.put(attributionTagFilter, + getOrCreateDiscreteOpEventsList(attributionTagFilter)); + mAttributedOps = attributedOps; + } + + int nTags = mAttributedOps.size(); + for (int i = nTags - 1; i >= 0; i--) { + String tag = mAttributedOps.keyAt(i); + List<DiscreteOpEvent> list = mAttributedOps.valueAt(i); + list = filterEventsList(list, beginTimeMillis, endTimeMillis, flagsFilter); + mAttributedOps.put(tag, list); + if (list.size() == 0) { + mAttributedOps.removeAt(i); + } + } + } + void addDiscreteAccess(@Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList( attributionTag); - accessTime = Instant.ofEpochMilli(accessTime).truncatedTo( - ChronoUnit.MINUTES).toEpochMilli(); + accessTime = accessTime / TIMELINE_QUANTIZATION * TIMELINE_QUANTIZATION; int nAttributedOps = attributedOps.size(); - for (int i = nAttributedOps - 1; i >= 0; i--) { - DiscreteOpEvent previousOp = attributedOps.get(i); - if (i == nAttributedOps - 1 && previousOp.mNoteTime == accessTime - && accessDuration > -1) { - // existing event with updated duration - attributedOps.remove(i); - break; - } + int i = nAttributedOps; + for (; i > 0; i--) { + DiscreteOpEvent previousOp = attributedOps.get(i - 1); if (previousOp.mNoteTime < accessTime) { break; } if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) { - return; + if (accessDuration != previousOp.mNoteDuration + && accessDuration > TIMELINE_QUANTIZATION) { + break; + } else { + return; + } } } - attributedOps.add(new DiscreteOpEvent(accessTime, accessDuration, uidState, flags)); + attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags)); } private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) { @@ -633,18 +745,11 @@ final class DiscreteRegistry { } } - void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis, - @AppOpsManager.HistoricalOpsRequestFilter int filter, - @Nullable String attributionTagFilter, - @AppOpsManager.OpFlags int flagsFilter) throws Exception { + void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception { int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (TAG_TAG.equals(parser.getName())) { String attributionTag = parser.getAttributeValue(null, ATTR_TAG); - if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !attributionTag.equals( - attributionTagFilter)) { - continue; - } List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList( attributionTag); int innerDepth = parser.getDepth(); @@ -655,11 +760,7 @@ final class DiscreteRegistry { -1); int uidState = parser.getAttributeInt(null, ATTR_UID_STATE); int opFlags = parser.getAttributeInt(null, ATTR_FLAGS); - if ((flagsFilter & opFlags) == 0) { - continue; - } - if ((noteTime + noteDuration < beginTimeMillis - && noteTime > endTimeMillis)) { + if (noteTime + noteDuration < beginTimeMillis) { continue; } DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration, @@ -715,5 +816,41 @@ final class DiscreteRegistry { out.attributeInt(null, ATTR_FLAGS, mOpFlag); } } + + private static List<DiscreteOpEvent> stableListMerge(List<DiscreteOpEvent> a, + List<DiscreteOpEvent> b) { + int nA = a.size(); + int nB = b.size(); + int i = 0; + int k = 0; + List<DiscreteOpEvent> result = new ArrayList<>(nA + nB); + while (i < nA || k < nB) { + if (i == nA) { + result.add(b.get(k++)); + } else if (k == nB) { + result.add(a.get(i++)); + } else if (a.get(i).mNoteTime < b.get(k).mNoteTime) { + result.add(a.get(i++)); + } else { + result.add(b.get(k++)); + } + } + return result; + } + + private static List<DiscreteOpEvent> filterEventsList(List<DiscreteOpEvent> list, + long beginTimeMillis, long endTimeMillis, @AppOpsManager.OpFlags int flagsFilter) { + int n = list.size(); + List<DiscreteOpEvent> result = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + DiscreteOpEvent event = list.get(i); + if ((event.mOpFlag & flagsFilter) != 0 + && event.mNoteTime + event.mNoteDuration > beginTimeMillis + && event.mNoteTime < endTimeMillis) { + result.add(event); + } + } + return result; + } } diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 22d628b8e789..4435c4795730 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -532,7 +532,7 @@ final class HistoricalRegistry { System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName, attributionTag, uidState, flags, increment); mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag, - flags, uidState, increment, eventStartTime); + flags, uidState, eventStartTime, increment); } } } @@ -795,7 +795,7 @@ final class HistoricalRegistry { private static boolean isApiEnabled() { return Binder.getCallingUid() == Process.myUid() || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_PERMISSIONS_HUB_ENABLED, false); + PROPERTY_PERMISSIONS_HUB_ENABLED, true); } private static final class Persistence { diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 5020917f8eb1..6712c5474921 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -57,6 +57,7 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.autofill.AutofillManagerInternal; import android.widget.Toast; @@ -274,6 +275,9 @@ public class ClipboardService extends SystemService { /** Package of the app that set {@link #primaryClip}. */ String mPrimaryClipPackage; + /** Uids that have already triggered a toast notification for {@link #primaryClip} */ + final SparseBooleanArray mNotifiedUids = new SparseBooleanArray(); + final HashSet<String> activePermissionOwners = new HashSet<String>(); @@ -649,6 +653,7 @@ public class ClipboardService extends SystemService { return; } clipboard.primaryClip = clip; + clipboard.mNotifiedUids.clear(); if (clip != null) { clipboard.primaryClipUid = uid; clipboard.mPrimaryClipPackage = sourcePackage; @@ -939,35 +944,42 @@ public class ClipboardService extends SystemService { && mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) { return; } + // Don't notify if already notified for this uid and clip. + if (clipboard.mNotifiedUids.get(uid)) { + return; + } + clipboard.mNotifiedUids.put(uid, true); - // 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 + 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) { - message = getContext().getString( - R.string.pasted_from_app, callingAppLabel, sourceAppLabel); - } else { - message = getContext().getString(R.string.pasted_from_clipboard, callingAppLabel); + try { + CharSequence callingAppLabel = mPm.getApplicationLabel( + mPm.getApplicationInfoAsUser(callingPackage, 0, userId)); + String message; + if (sourceAppLabel != null) { + message = getContext().getString( + R.string.pasted_from_app, callingAppLabel, sourceAppLabel); + } else { + message = getContext().getString( + R.string.pasted_from_clipboard, callingAppLabel); + } + Slog.i(TAG, message); + Toast.makeText( + getContext(), UiThread.get().getLooper(), message, Toast.LENGTH_SHORT) + .show(); + } catch (PackageManager.NameNotFoundException e) { + // do nothing } - Slog.i(TAG, message); - Binder.withCleanCallingIdentity(() -> - Toast.makeText(getContext(), UiThread.get().getLooper(), message, - Toast.LENGTH_SHORT) - .show()); - } catch (PackageManager.NameNotFoundException e) { - // do nothing - } + }); } } diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 5cf478a3ef1f..ae9b0015de43 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.CompatibilityChangeInfo; import com.android.internal.compat.OverrideAllowedState; import com.android.server.compat.config.Change; @@ -55,7 +56,7 @@ public final class CompatChange extends CompatibilityChangeInfo { * A change ID to be used only in the CTS test for this SystemApi */ @ChangeId - @EnabledSince(targetSdkVersion = 1235) // Needs to be > test APK targetSdkVersion. + @EnabledSince(targetSdkVersion = 31) // Needs to be > test APK targetSdkVersion. static final long CTS_SYSTEM_API_CHANGEID = 149391281; // This is a bug id. /** @@ -233,7 +234,7 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param app Info about the app in question * @return {@code true} if the change should be enabled for the package. */ - boolean isEnabled(ApplicationInfo app) { + boolean isEnabled(ApplicationInfo app, AndroidBuildClassifier buildClassifier) { if (app == null) { return defaultValue(); } @@ -244,7 +245,13 @@ public final class CompatChange extends CompatibilityChangeInfo { return false; } if (getEnableSinceTargetSdk() != -1) { - return app.targetSdkVersion >= getEnableSinceTargetSdk(); + // If the change is gated by a platform version newer than the one currently installed + // on the device, disregard the app's target sdk version. + int compareSdk = Math.min(app.targetSdkVersion, buildClassifier.platformTargetSdk()); + if (compareSdk != app.targetSdkVersion) { + compareSdk = app.targetSdkVersion; + } + return compareSdk >= getEnableSinceTargetSdk(); } return true; } diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 2c053b421904..ef86f42d6c3c 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -74,12 +74,14 @@ final class CompatConfig { private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); private final OverrideValidatorImpl mOverrideValidator; + private final AndroidBuildClassifier mAndroidBuildClassifier; private Context mContext; private File mOverridesFile; @VisibleForTesting CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) { mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this); + mAndroidBuildClassifier = androidBuildClassifier; mContext = context; } @@ -133,7 +135,7 @@ final class CompatConfig { synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { CompatChange c = mChanges.valueAt(i); - if (!c.isEnabled(app)) { + if (!c.isEnabled(app, mAndroidBuildClassifier)) { disabled.add(c.getId()); } } @@ -175,7 +177,7 @@ final class CompatConfig { // we know nothing about this change: default behaviour is enabled. return true; } - return c.isEnabled(app); + return c.isEnabled(app, mAndroidBuildClassifier); } } @@ -475,7 +477,7 @@ final class CompatConfig { synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { CompatChange c = mChanges.valueAt(i); - if (c.isEnabled(applicationInfo)) { + if (c.isEnabled(applicationInfo, mAndroidBuildClassifier)) { enabled.add(c.getId()); } else { disabled.add(c.getId()); diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java index fe5b4a98797d..aa66a1a8b01f 100644 --- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java +++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java @@ -22,6 +22,7 @@ import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARG import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE; import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH; import static com.android.internal.compat.OverrideAllowedState.LOGGING_ONLY_CHANGE; +import static com.android.internal.compat.OverrideAllowedState.PLATFORM_TOO_OLD; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -85,6 +86,9 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub { if (debuggableBuild) { return new OverrideAllowedState(ALLOWED, -1, -1); } + if (maxTargetSdk >= mAndroidBuildClassifier.platformTargetSdk()) { + return new OverrideAllowedState(PLATFORM_TOO_OLD, -1, maxTargetSdk); + } PackageManager packageManager = mContext.getPackageManager(); if (packageManager == null) { throw new IllegalStateException("No PackageManager!"); diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index d17753fe81bd..2be39aa24294 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -66,18 +66,22 @@ public class PlatformCompat extends IPlatformCompat.Stub { private final Context mContext; private final ChangeReporter mChangeReporter; private final CompatConfig mCompatConfig; + private final AndroidBuildClassifier mBuildClassifier; public PlatformCompat(Context context) { mContext = context; mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER); - mCompatConfig = CompatConfig.create(new AndroidBuildClassifier(), mContext); + mBuildClassifier = new AndroidBuildClassifier(); + mCompatConfig = CompatConfig.create(mBuildClassifier, mContext); } @VisibleForTesting - PlatformCompat(Context context, CompatConfig compatConfig) { + PlatformCompat(Context context, CompatConfig compatConfig, + AndroidBuildClassifier buildClassifier) { mContext = context; mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER); mCompatConfig = compatConfig; + mBuildClassifier = buildClassifier; registerPackageReceiver(context); } @@ -392,7 +396,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { return false; } if (change.getEnableSinceTargetSdk() > 0) { - return change.getEnableSinceTargetSdk() >= Build.VERSION_CODES.Q; + return change.getEnableSinceTargetSdk() >= Build.VERSION_CODES.Q + && change.getEnableSinceTargetSdk() <= mBuildClassifier.platformTargetSdk(); } return true; } diff --git a/services/core/java/com/android/server/connectivity/ConnectivityResources.java b/services/core/java/com/android/server/connectivity/ConnectivityResources.java new file mode 100644 index 000000000000..45cf21e035ca --- /dev/null +++ b/services/core/java/com/android/server/connectivity/ConnectivityResources.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.util.Log; + +import com.android.server.ConnectivityService; + +import java.util.List; + +/** + * Utility to obtain the {@link ConnectivityService} {@link Resources}, in the + * ServiceConnectivityResources APK. + */ +public class ConnectivityResources { + private static final String RESOURCES_APK_INTENT = + "com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK"; + private static final String RES_PKG_DIR = "/apex/com.android.tethering/"; + + @NonNull + private final Context mContext; + + @Nullable + private Resources mResources = null; + + public ConnectivityResources(Context context) { + mContext = context; + } + + /** + * Get the {@link Resources} of the ServiceConnectivityResources APK. + */ + public synchronized Resources get() { + if (mResources != null) { + return mResources; + } + + final List<ResolveInfo> pkgs = mContext.getPackageManager() + .queryIntentActivities(new Intent(RESOURCES_APK_INTENT), MATCH_SYSTEM_ONLY); + pkgs.removeIf(pkg -> !pkg.activityInfo.applicationInfo.sourceDir.startsWith(RES_PKG_DIR)); + if (pkgs.size() > 1) { + Log.wtf(ConnectivityResources.class.getSimpleName(), + "More than one package found: " + pkgs); + } + if (pkgs.isEmpty()) { + throw new IllegalStateException("No connectivity resource package found"); + } + + final Context pkgContext; + try { + pkgContext = mContext.createPackageContext( + pkgs.get(0).activityInfo.applicationInfo.packageName, 0 /* flags */); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException("Resolved package not found", e); + } + + mResources = pkgContext.getResources(); + return mResources; + } +} diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index fa80b25f9026..c66a280f2b02 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -16,6 +16,8 @@ package com.android.server.connectivity; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; + import static com.android.net.module.util.CollectionUtils.contains; import android.annotation.NonNull; @@ -35,6 +37,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.NetworkStackConstants; +import com.android.server.ConnectivityService; import java.net.Inet6Address; import java.util.Objects; @@ -94,12 +97,15 @@ public class Nat464Xlat { private Inet6Address mIPv6Address; private State mState = State.IDLE; + private boolean mEnableClatOnCellular; private boolean mPrefixDiscoveryRunning; - public Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver) { + public Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver, + ConnectivityService.Dependencies deps) { mDnsResolver = dnsResolver; mNetd = netd; mNetwork = nai; + mEnableClatOnCellular = deps.getCellular464XlatEnabled(); } /** @@ -111,7 +117,7 @@ public class Nat464Xlat { * @return true if the network requires clat, false otherwise. */ @VisibleForTesting - protected static boolean requiresClat(NetworkAgentInfo nai) { + protected boolean requiresClat(NetworkAgentInfo nai) { // TODO: migrate to NetworkCapabilities.TRANSPORT_*. final boolean supported = contains(NETWORK_TYPES, nai.networkInfo.getType()); final boolean connected = contains(NETWORK_STATES, nai.networkInfo.getState()); @@ -126,7 +132,9 @@ public class Nat464Xlat { final boolean skip464xlat = (nai.netAgentConfig() != null) && nai.netAgentConfig().skip464xlat; - return supported && connected && isIpv6OnlyNetwork && !skip464xlat; + return supported && connected && isIpv6OnlyNetwork && !skip464xlat + && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR) + ? isCellular464XlatEnabled() : true); } /** @@ -137,7 +145,7 @@ public class Nat464Xlat { * @return true if the network should start clat, false otherwise. */ @VisibleForTesting - protected static boolean shouldStartClat(NetworkAgentInfo nai) { + protected boolean shouldStartClat(NetworkAgentInfo nai) { LinkProperties lp = nai.linkProperties; return requiresClat(nai) && lp != null && lp.getNat64Prefix() != null; } @@ -507,4 +515,9 @@ public class Nat464Xlat { protected int getNetId() { return mNetwork.network.getNetId(); } + + @VisibleForTesting + protected boolean isCellular464XlatEnabled() { + return mEnableClatOnCellular; + } } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 1d0e11569c80..803cc9d31c35 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -341,7 +341,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, int score, Context context, Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, IDnsResolver dnsResolver, int factorySerialNumber, int creatorUid, - QosCallbackTracker qosCallbackTracker) { + QosCallbackTracker qosCallbackTracker, ConnectivityService.Dependencies deps) { Objects.requireNonNull(net); Objects.requireNonNull(info); Objects.requireNonNull(lp); @@ -355,7 +355,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { linkProperties = lp; networkCapabilities = nc; mScore = score; - clatd = new Nat464Xlat(this, netd, dnsResolver); + clatd = new Nat464Xlat(this, netd, dnsResolver, deps); mConnService = connService; mContext = context; mHandler = handler; diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index 9411e33434d8..488677ac1b59 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -31,14 +31,17 @@ import static android.os.Process.SYSTEM_UID; import static com.android.net.module.util.CollectionUtils.toIntArray; import android.annotation.NonNull; +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.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.PackageManagerInternal; import android.net.INetd; import android.net.UidRange; +import android.net.Uri; import android.os.Build; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -54,7 +57,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.net.module.util.CollectionUtils; -import com.android.server.LocalServices; import java.util.ArrayList; import java.util.HashMap; @@ -71,7 +73,7 @@ import java.util.Set; * * @hide */ -public class PermissionMonitor implements PackageManagerInternal.PackageListObserver { +public class PermissionMonitor { private static final String TAG = "PermissionMonitor"; private static final boolean DBG = true; protected static final Boolean SYSTEM = Boolean.TRUE; @@ -83,6 +85,7 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse private final SystemConfigManager mSystemConfigManager; private final INetd mNetd; private final Dependencies mDeps; + private final Context mContext; @GuardedBy("this") private final Set<UserHandle> mUsers = new HashSet<>(); @@ -102,6 +105,25 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse @GuardedBy("this") private final Set<Integer> mAllApps = new HashSet<>(); + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); + final Uri packageData = intent.getData(); + final String packageName = + packageData != null ? packageData.getSchemeSpecificPart() : null; + + if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + onPackageAdded(packageName, uid); + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + onPackageRemoved(packageName, uid); + } else { + Log.wtf(TAG, "received unexpected intent: " + action); + } + } + }; + /** * Dependencies of PermissionMonitor, for injection in tests. */ @@ -127,6 +149,7 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse mSystemConfigManager = context.getSystemService(SystemConfigManager.class); mNetd = netd; mDeps = deps; + mContext = context; } // Intended to be called only once at startup, after the system is ready. Installs a broadcast @@ -134,12 +157,14 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse public synchronized void startMonitoring() { log("Monitoring"); - PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); - if (pmi != null) { - pmi.getPackageList(this); - } else { - loge("failed to get the PackageManagerInternal service"); - } + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */).registerReceiver( + mIntentReceiver, intentFilter, null /* broadcastPermission */, + null /* scheduler */); + List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS | MATCH_ANY_USER); if (apps == null) { @@ -347,9 +372,10 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse * * @hide */ - @Override public synchronized void onPackageAdded(@NonNull final String packageName, final int uid) { - sendPackagePermissionsForUid(uid, getPermissionForUid(uid)); + // TODO: Netd is using appId for checking traffic permission. Correct the methods that are + // using appId instead of uid actually + sendPackagePermissionsForUid(UserHandle.getAppId(uid), getPermissionForUid(uid)); // If multiple packages share a UID (cf: android:sharedUserId) and ask for different // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is). @@ -384,9 +410,10 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse * * @hide */ - @Override public synchronized void onPackageRemoved(@NonNull final String packageName, final int uid) { - sendPackagePermissionsForUid(uid, getPermissionForUid(uid)); + // TODO: Netd is using appId for checking traffic permission. Correct the methods that are + // using appId instead of uid actually + sendPackagePermissionsForUid(UserHandle.getAppId(uid), getPermissionForUid(uid)); // If the newly-removed package falls within some VPN's uid range, update Netd with it. // This needs to happen before the mApps update below, since removeBypassingUids() depends @@ -432,19 +459,6 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse } } - /** - * Called when a package is changed. - * - * @param packageName The name of the changed package. - * @param uid The uid of the changed package. - * - * @hide - */ - @Override - public synchronized void onPackageChanged(@NonNull final String packageName, final int uid) { - sendPackagePermissionsForUid(uid, getPermissionForUid(uid)); - } - private static int getNetdPermissionMask(String[] requestedPermissions, int[] requestedPermissionsFlags) { int permissions = 0; diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 027b9afba392..0e714969a69b 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -791,12 +791,13 @@ public final class ContentService extends IContentService.Stub { public SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) { enforceCrossUserPermission(userId, "no permission to read sync settings for user " + userId); + final int callingUid = Binder.getCallingUid(); // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. final long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); - return syncManager.getSyncAdapterTypes(userId); + return syncManager.getSyncAdapterTypes(callingUid, userId); } finally { restoreCallingIdentity(identityToken); } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index df870125e253..ac7e01ed4d72 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -89,6 +89,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.provider.Settings; +import android.text.TextUtils; import android.text.format.TimeMigrationUtils; import android.util.EventLog; import android.util.Log; @@ -1257,16 +1258,19 @@ public class SyncManager { syncExemptionFlag, callingUid, callingPid, callingPackage); } - public SyncAdapterType[] getSyncAdapterTypes(int userId) { + public SyncAdapterType[] getSyncAdapterTypes(int callingUid, int userId) { final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos; serviceInfos = mSyncAdapters.getAllServices(userId); - SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()]; - int i = 0; + final List<SyncAdapterType> types = new ArrayList<>(serviceInfos.size()); for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) { - types[i] = serviceInfo.type; - ++i; + final String packageName = serviceInfo.type.getPackageName(); + if (!TextUtils.isEmpty(packageName) && mPackageManagerInternal.filterAppAccess( + packageName, callingUid, userId)) { + continue; + } + types.add(serviceInfo.type); } - return types; + return types.toArray(new SyncAdapterType[] {}); } public String[] getSyncAdapterPackagesForAuthorityAsUser(String authority, int userId) { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 174d4b2fe00d..96a74161a6eb 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1128,7 +1128,7 @@ public final class DisplayManagerService extends SystemService { recordTopInsetLocked(display); } addDisplayPowerControllerLocked(display); - mDisplayStates.append(displayId, Display.STATE_OFF); + mDisplayStates.append(displayId, Display.STATE_UNKNOWN); mDisplayBrightnesses.append(displayId, display.getDisplayInfoLocked().brightnessDefault); DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); @@ -1204,16 +1204,15 @@ public final class DisplayManagerService extends SystemService { DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); - final int state; final int displayId = display.getDisplayIdLocked(); + final int state = mDisplayStates.get(displayId); - if (display.isEnabled()) { - state = mDisplayStates.get(displayId); - } else { - state = Display.STATE_OFF; + // Only send a request for display state if the display state has already been + // initialized by DisplayPowercontroller. + if (state != Display.STATE_UNKNOWN) { + final float brightness = mDisplayBrightnesses.get(displayId); + return device.requestDisplayStateLocked(state, brightness); } - final float brightness = mDisplayBrightnesses.get(displayId); - return device.requestDisplayStateLocked(state, brightness); } return null; } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 011732682ace..7b107b857a79 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -982,7 +982,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mWaitingForNegativeProximity = false; mIgnoreProximityUntilChanged = false; } - if (mScreenOffBecauseOfProximity) { + + if (!mLogicalDisplay.isEnabled() || mScreenOffBecauseOfProximity) { state = Display.STATE_OFF; } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index d88896c01e4b..aaec89afa94c 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -682,7 +682,10 @@ final class LocalDisplayAdapter extends DisplayAdapter { || oldState == Display.STATE_ON_SUSPEND) { setDisplayState(Display.STATE_ON); currentState = Display.STATE_ON; - } else { + + // If UNKNOWN, we still want to set the initial display state, + // otherwise, return early. + } else if (oldState != Display.STATE_UNKNOWN) { return; // old state and new state is off } } diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index 06adce81d8a1..2b7d2074a7e0 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -31,6 +31,7 @@ import android.os.SharedMemory; import android.os.ShellCallback; import android.system.ErrnoException; import android.text.FontConfig; +import android.text.TextUtils; import android.util.AndroidException; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -66,6 +67,8 @@ public final class FontManagerService extends IFontManager.Stub { @Override public FontConfig getFontConfig() { + getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS, + "UPDATE_FONTS permission required."); return getSystemFontConfig(); } @@ -148,10 +151,24 @@ public final class FontManagerService extends IFontManager.Stub { /* package */ static class OtfFontFileParser implements UpdatableFontDir.FontFileParser { @Override - public String getPostScriptName(File file) throws IOException { + public String getCanonicalFileName(File file) throws IOException { ByteBuffer buffer = mmap(file); try { - return FontFileUtil.getPostScriptName(buffer, 0); + String psName = FontFileUtil.getPostScriptName(buffer, 0); + int isType1Font = FontFileUtil.isPostScriptType1Font(buffer, 0); + int isCollection = FontFileUtil.isCollectionFont(buffer); + + if (TextUtils.isEmpty(psName) || isType1Font == -1 || isCollection == -1) { + return null; + } + + String extension; + if (isCollection == 1) { + extension = isType1Font == 1 ? ".otc" : ".ttc"; + } else { + extension = isType1Font == 1 ? ".otf" : ".ttf"; + } + return psName + extension; } finally { NioUtils.freeDirectBuffer(buffer); } 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 86dbe86f85ee..4f95d27a085e 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -56,14 +56,12 @@ final class UpdatableFontDir { private static final String TAG = "UpdatableFontDir"; private static final String RANDOM_DIR_PREFIX = "~~"; - // TODO: Support .otf - private static final String ALLOWED_EXTENSION = ".ttf"; private static final String CONFIG_XML_FILE = "/data/fonts/config/config.xml"; /** Interface to mock font file access in tests. */ interface FontFileParser { - String getPostScriptName(File file) throws IOException; + String getCanonicalFileName(File file) throws IOException; long getRevision(File file) throws IOException; } @@ -321,20 +319,20 @@ final class UpdatableFontDir { FontManager.RESULT_ERROR_VERIFICATION_FAILURE, "Failed to setup fs-verity.", e); } - String postScriptName; + String canonicalFileName; try { - postScriptName = mParser.getPostScriptName(tempNewFontFile); + canonicalFileName = mParser.getCanonicalFileName(tempNewFontFile); } catch (IOException e) { throw new SystemFontException( FontManager.RESULT_ERROR_INVALID_FONT_FILE, "Failed to read PostScript name from font file", e); } - if (postScriptName == null) { + if (canonicalFileName == null) { throw new SystemFontException( FontManager.RESULT_ERROR_INVALID_FONT_NAME, "Failed to read PostScript name from font file"); } - File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION); + File newFontFile = new File(newDir, canonicalFileName); if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) { throw new SystemFontException( FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE, @@ -380,20 +378,38 @@ final class UpdatableFontDir { return dir; } + private FontFileInfo lookupFontFileInfo(File file) { + String name = file.getName(); + + if (!name.endsWith(".ttf") && !name.endsWith(".otf") && !name.endsWith(".ttc") + && !name.endsWith(".otc")) { + return null; + } + String key = name.substring(0, name.length() - 4); + return mFontFileInfoMap.get(key); + } + + private void putFontFileInfo(FontFileInfo info) { + String name = info.getFile().getName(); + // The file name in FontFileInfo is already validated. Thus, just strip last 4 chars. + String key = name.substring(0, name.length() - 4); + mFontFileInfoMap.put(key, info); + } + /** * Add the given {@link FontFileInfo} to {@link #mFontFileInfoMap} if its font revision is * higher than the currently used font file (either in {@link #mFontFileInfoMap} or {@link * #mPreinstalledFontDirs}). */ private boolean addFileToMapIfNewer(FontFileInfo fontFileInfo, boolean deleteOldFile) { - String name = fontFileInfo.getFile().getName(); - FontFileInfo existingInfo = mFontFileInfoMap.get(name); + FontFileInfo existingInfo = lookupFontFileInfo(fontFileInfo.getFile()); final boolean shouldAddToMap; if (existingInfo == null) { // We got a new updatable font. We need to check if it's newer than preinstalled fonts. // Note that getPreinstalledFontRevision() returns -1 if there is no preinstalled font // with 'name'. - shouldAddToMap = getPreinstalledFontRevision(name) < fontFileInfo.getRevision(); + long preInstalledRev = getPreinstalledFontRevision(fontFileInfo.getFile().getName()); + shouldAddToMap = preInstalledRev < fontFileInfo.getRevision(); } else { shouldAddToMap = existingInfo.getRevision() < fontFileInfo.getRevision(); } @@ -401,7 +417,7 @@ final class UpdatableFontDir { if (deleteOldFile && existingInfo != null) { FileUtils.deleteContentsAndDir(existingInfo.getRandomizedFontDir()); } - mFontFileInfoMap.put(name, fontFileInfo); + putFontFileInfo(fontFileInfo); } else { if (deleteOldFile) { FileUtils.deleteContentsAndDir(fontFileInfo.getRandomizedFontDir()); @@ -464,15 +480,18 @@ final class UpdatableFontDir { */ private boolean validateFontFileName(File file) { String fileName = file.getName(); - String postScriptName = getPostScriptName(file); - return (postScriptName + ALLOWED_EXTENSION).equals(fileName); + String canonicalFileName = getCanonicalFileName(file); + if (canonicalFileName == null) { + return false; + } + return canonicalFileName.equals(fileName); } /** Returns the PostScript name of the given font file, or null. */ @Nullable - private String getPostScriptName(File file) { + private String getCanonicalFileName(File file) { try { - return mParser.getPostScriptName(file); + return mParser.getCanonicalFileName(file); } catch (IOException e) { Slog.e(TAG, "Failed to read font file", e); return null; @@ -514,7 +533,7 @@ final class UpdatableFontDir { List<FontConfig.Font> fontList = fontFamily.getFontList(); for (int i = 0; i < fontList.size(); i++) { FontConfig.Font font = fontList.get(i); - FontFileInfo info = mFontFileInfoMap.get(font.getFile().getName()); + FontFileInfo info = lookupFontFileInfo(font.getFile()); if (info == null) { return null; } @@ -537,8 +556,9 @@ final class UpdatableFontDir { Map<String, File> getFontFileMap() { Map<String, File> map = new ArrayMap<>(); - for (Map.Entry<String, FontFileInfo> entry : mFontFileInfoMap.entrySet()) { - map.put(entry.getKey(), entry.getValue().getFile()); + for (int i = 0; i < mFontFileInfoMap.size(); ++i) { + File file = mFontFileInfoMap.valueAt(i).getFile(); + map.put(file.getName(), file); } return map; } diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java index c23e2e691b55..947ee24f8e02 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java @@ -97,11 +97,16 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { @Override public boolean start() { - // Wake-up on <Set Stream Path> was not mandatory before CEC 2.0. - // The message is re-sent at the end of the action for devices that don't support 2.0. - sendSetStreamPath(); - int targetPowerStatus = localDevice().mService.getHdmiCecNetwork() - .getCecDeviceInfo(getTargetAddress()).getDevicePowerStatus(); + // Wake-up on <Set Stream Path> was not mandatory before CEC 2.0. + // The message is re-sent at the end of the action for devices that don't support 2.0. + sendSetStreamPath(); + int targetPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; + HdmiDeviceInfo targetDevice = localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo( + getTargetAddress()); + if (targetDevice != null) { + targetPowerStatus = targetDevice.getDevicePowerStatus(); + } + if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) { queryDevicePowerStatus(); } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java index 624af30854dc..6fbb26c4a5ee 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java @@ -35,33 +35,19 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings.Global; import android.util.ArrayMap; -import android.util.Slog; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ConcurrentUtils; -import com.android.server.hdmi.cec.config.CecSettings; -import com.android.server.hdmi.cec.config.Setting; -import com.android.server.hdmi.cec.config.Value; -import com.android.server.hdmi.cec.config.XmlParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; -import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map.Entry; -import java.util.Set; import java.util.concurrent.Executor; -import javax.xml.datatype.DatatypeConfigurationException; - /** * The {@link HdmiCecConfig} class is used for getting information about * available HDMI CEC settings. @@ -74,6 +60,10 @@ public class HdmiCecConfig { private static final String SHARED_PREFS_DIR = "shared_prefs"; private static final String SHARED_PREFS_NAME = "cec_config.xml"; + private static final int STORAGE_SYSPROPS = 0; + private static final int STORAGE_GLOBAL_SETTINGS = 1; + private static final int STORAGE_SHARED_PREFS = 2; + @IntDef({ STORAGE_SYSPROPS, STORAGE_GLOBAL_SETTINGS, @@ -81,10 +71,6 @@ public class HdmiCecConfig { }) private @interface Storage {} - private static final int STORAGE_SYSPROPS = 0; - private static final int STORAGE_GLOBAL_SETTINGS = 1; - private static final int STORAGE_SHARED_PREFS = 2; - private static final String VALUE_TYPE_STRING = "string"; private static final String VALUE_TYPE_INT = "int"; @@ -96,8 +82,6 @@ public class HdmiCecConfig { @NonNull private final Context mContext; @NonNull private final StorageAdapter mStorageAdapter; - @Nullable private final CecSettings mSystemConfig; - @Nullable private final CecSettings mVendorOverride; private final Object mLock = new Object(); @@ -107,6 +91,18 @@ public class HdmiCecConfig { private SettingsObserver mSettingsObserver; + private LinkedHashMap<String, Setting> mSettings = new LinkedHashMap<>(); + + /** + * Exception thrown when the CEC Configuration setup verification fails. + * This usually means a settings lacks default value or storage/storage key. + */ + public static class VerificationException extends RuntimeException { + public VerificationException(String message) { + super(message); + } + } + /** * Listener used to get notifications when value of a setting changes. */ @@ -202,91 +198,297 @@ public class HdmiCecConfig { } } - @VisibleForTesting - HdmiCecConfig(@NonNull Context context, - @NonNull StorageAdapter storageAdapter, - @Nullable CecSettings systemConfig, - @Nullable CecSettings vendorOverride) { - mContext = context; - mStorageAdapter = storageAdapter; - mSystemConfig = systemConfig; - mVendorOverride = vendorOverride; - if (mSystemConfig == null) { - Slog.i(TAG, "CEC system configuration XML missing."); + private class Value { + private final String mStringValue; + private final Integer mIntValue; + + Value(@NonNull String value) { + mStringValue = value; + mIntValue = null; } - if (mVendorOverride == null) { - Slog.i(TAG, "CEC OEM configuration override XML missing."); + + Value(@NonNull Integer value) { + mStringValue = null; + mIntValue = value; } - } - HdmiCecConfig(@NonNull Context context) { - this(context, new StorageAdapter(context), - readSettingsFromFile(Environment.buildPath(Environment.getRootDirectory(), - ETC_DIR, CONFIG_FILE)), - readSettingsFromFile(Environment.buildPath(Environment.getVendorDirectory(), - ETC_DIR, CONFIG_FILE))); + String getStringValue() { + return mStringValue; + } + + Integer getIntValue() { + return mIntValue; + } } - @Nullable - private static CecSettings readSettingsFromFile(@NonNull File file) { - if (!file.exists()) { - return null; + private class Setting { + @NonNull private final Context mContext; + @NonNull private final @CecSettingName String mName; + private final boolean mUserConfigurable; + + private Value mDefaultValue = null; + private List<Value> mAllowedValues = new ArrayList<>(); + + Setting(@NonNull Context context, + @NonNull @CecSettingName String name, + int userConfResId) { + mContext = context; + mName = name; + mUserConfigurable = mContext.getResources().getBoolean(userConfResId); } - if (!file.isFile()) { - Slog.e(TAG, "CEC configuration is not a file: " + file + ", skipping."); - return null; + + public @CecSettingName String getName() { + return mName; } - try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { - return XmlParser.read(in); - } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { - Slog.e(TAG, "Encountered an error while reading/parsing CEC config file: " + file, e); + + public @ValueType String getValueType() { + return getDefaultValue().getStringValue() != null + ? VALUE_TYPE_STRING + : VALUE_TYPE_INT; } - return null; - } - @NonNull - @VisibleForTesting - static HdmiCecConfig createFromStrings(@NonNull Context context, - @NonNull StorageAdapter storageAdapter, - @Nullable String productConfigXml, - @Nullable String vendorOverrideXml) { - CecSettings productConfig = null; - CecSettings vendorOverride = null; - try { - if (productConfigXml != null) { - productConfig = XmlParser.read( - new ByteArrayInputStream(productConfigXml.getBytes())); - } - if (vendorOverrideXml != null) { - vendorOverride = XmlParser.read( - new ByteArrayInputStream(vendorOverrideXml.getBytes())); + public Value getDefaultValue() { + if (mDefaultValue == null) { + throw new VerificationException("Invalid CEC setup for '" + + this.getName() + "' setting. " + + "Setting has no default value."); } - } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { - Slog.e(TAG, "Encountered an error while reading/parsing CEC config strings", e); + return mDefaultValue; } - return new HdmiCecConfig(context, storageAdapter, productConfig, vendorOverride); - } - @Nullable - private Setting getSetting(@NonNull String name) { - if (mSystemConfig == null) { - return null; - } - if (mVendorOverride != null) { - // First read from the vendor override. - for (Setting setting : mVendorOverride.getSetting()) { - if (setting.getName().equals(name)) { - return setting; + public boolean getUserConfigurable() { + return mUserConfigurable; + } + + private void registerValue(@NonNull Value value, + int allowedResId, int defaultResId) { + if (mContext.getResources().getBoolean(allowedResId)) { + mAllowedValues.add(value); + if (mContext.getResources().getBoolean(defaultResId)) { + if (mDefaultValue != null) { + throw new VerificationException("Invalid CEC setup for '" + + this.getName() + "' setting. " + + "Setting already has a default value."); + } + mDefaultValue = value; } } } - // If not found, try the system config. - for (Setting setting : mSystemConfig.getSetting()) { - if (setting.getName().equals(name)) { - return setting; - } + + public void registerValue(@NonNull String value, int allowedResId, + int defaultResId) { + registerValue(new Value(value), allowedResId, defaultResId); } - return null; + + public void registerValue(int value, int allowedResId, + int defaultResId) { + registerValue(new Value(value), allowedResId, defaultResId); + } + + + public List<Value> getAllowedValues() { + return mAllowedValues; + } + } + + @VisibleForTesting + HdmiCecConfig(@NonNull Context context, + @NonNull StorageAdapter storageAdapter) { + mContext = context; + mStorageAdapter = storageAdapter; + + Setting hdmiCecEnabled = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, + R.bool.config_cecHdmiCecEnabled_userConfigurable); + hdmiCecEnabled.registerValue(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED, + R.bool.config_cecHdmiCecControlEnabled_allowed, + R.bool.config_cecHdmiCecControlEnabled_default); + hdmiCecEnabled.registerValue(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED, + R.bool.config_cecHdmiCecControlDisabled_allowed, + R.bool.config_cecHdmiCecControlDisabled_default); + + Setting hdmiCecVersion = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + R.bool.config_cecHdmiCecVersion_userConfigurable); + hdmiCecVersion.registerValue(HdmiControlManager.HDMI_CEC_VERSION_1_4_B, + R.bool.config_cecHdmiCecVersion14b_allowed, + R.bool.config_cecHdmiCecVersion14b_default); + hdmiCecVersion.registerValue(HdmiControlManager.HDMI_CEC_VERSION_2_0, + R.bool.config_cecHdmiCecVersion20_allowed, + R.bool.config_cecHdmiCecVersion20_default); + + Setting powerControlMode = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE, + R.bool.config_cecSendStandbyOnSleep_userConfigurable); + powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV, + R.bool.config_cecPowerControlModeTv_allowed, + R.bool.config_cecPowerControlModeTv_default); + powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST, + R.bool.config_cecPowerControlModeBroadcast_allowed, + R.bool.config_cecPowerControlModeBroadcast_default); + powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_NONE, + R.bool.config_cecPowerControlModeNone_allowed, + R.bool.config_cecPowerControlModeNone_default); + + Setting powerStateChangeOnActiveSourceLost = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, + R.bool.config_cecPowerStateChangeOnActiveSourceLost_userConfigurable); + powerStateChangeOnActiveSourceLost.registerValue( + HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE, + R.bool.config_cecPowerStateChangeOnActiveSourceLostNone_allowed, + R.bool.config_cecPowerStateChangeOnActiveSourceLostNone_default); + powerStateChangeOnActiveSourceLost.registerValue( + HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW, + R.bool.config_cecPowerStateChangeOnActiveSourceLostStandbyNow_allowed, + R.bool.config_cecPowerStateChangeOnActiveSourceLostStandbyNow_default); + + Setting systemAudioModeMuting = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, + R.bool.config_cecSystemAudioModeMuting_userConfigurable); + systemAudioModeMuting.registerValue(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_ENABLED, + R.bool.config_cecSystemAudioModeMutingEnabled_allowed, + R.bool.config_cecSystemAudioModeMutingEnabled_default); + systemAudioModeMuting.registerValue(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED, + R.bool.config_cecSystemAudioModeMutingDisabled_allowed, + R.bool.config_cecSystemAudioModeMutingDisabled_default); + + Setting volumeControlMode = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, + R.bool.config_cecVolumeControlMode_userConfigurable); + volumeControlMode.registerValue(HdmiControlManager.VOLUME_CONTROL_ENABLED, + R.bool.config_cecVolumeControlModeEnabled_allowed, + R.bool.config_cecVolumeControlModeEnabled_default); + volumeControlMode.registerValue(HdmiControlManager.VOLUME_CONTROL_DISABLED, + R.bool.config_cecVolumeControlModeDisabled_allowed, + R.bool.config_cecVolumeControlModeDisabled_default); + + Setting tvWakeOnOneTouchPlay = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, + R.bool.config_cecTvWakeOnOneTouchPlay_userConfigurable); + tvWakeOnOneTouchPlay.registerValue(HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED, + R.bool.config_cecTvWakeOnOneTouchPlayEnabled_allowed, + R.bool.config_cecTvWakeOnOneTouchPlayEnabled_default); + tvWakeOnOneTouchPlay.registerValue(HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED, + R.bool.config_cecTvWakeOnOneTouchPlayDisabled_allowed, + R.bool.config_cecTvWakeOnOneTouchPlayDisabled_default); + + Setting tvSendStandbyOnSleep = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP, + R.bool.config_cecTvSendStandbyOnSleep_userConfigurable); + tvSendStandbyOnSleep.registerValue(HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED, + R.bool.config_cecTvSendStandbyOnSleepEnabled_allowed, + R.bool.config_cecTvSendStandbyOnSleepEnabled_default); + tvSendStandbyOnSleep.registerValue(HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_DISABLED, + R.bool.config_cecTvSendStandbyOnSleepDisabled_allowed, + R.bool.config_cecTvSendStandbyOnSleepDisabled_default); + + Setting rcProfileTv = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV, + R.bool.config_cecRcProfileTv_userConfigurable); + rcProfileTv.registerValue(HdmiControlManager.RC_PROFILE_TV_NONE, + R.bool.config_cecRcProfileTvNone_allowed, + R.bool.config_cecRcProfileTvNone_default); + rcProfileTv.registerValue(HdmiControlManager.RC_PROFILE_TV_ONE, + R.bool.config_cecRcProfileTvOne_allowed, + R.bool.config_cecRcProfileTvOne_default); + rcProfileTv.registerValue(HdmiControlManager.RC_PROFILE_TV_TWO, + R.bool.config_cecRcProfileTvTwo_allowed, + R.bool.config_cecRcProfileTvTwo_default); + rcProfileTv.registerValue(HdmiControlManager.RC_PROFILE_TV_THREE, + R.bool.config_cecRcProfileTvThree_allowed, + R.bool.config_cecRcProfileTvThree_default); + rcProfileTv.registerValue(HdmiControlManager.RC_PROFILE_TV_FOUR, + R.bool.config_cecRcProfileTvFour_allowed, + R.bool.config_cecRcProfileTvFour_default); + + Setting rcProfileSourceRootMenu = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU, + R.bool.config_cecRcProfileSourceRootMenu_userConfigurable); + rcProfileSourceRootMenu.registerValue( + HdmiControlManager.RC_PROFILE_SOURCE_ROOT_MENU_HANDLED, + R.bool.config_cecRcProfileSourceRootMenuHandled_allowed, + R.bool.config_cecRcProfileSourceRootMenuHandled_default); + rcProfileSourceRootMenu.registerValue( + HdmiControlManager.RC_PROFILE_SOURCE_ROOT_MENU_NOT_HANDLED, + R.bool.config_cecRcProfileSourceRootMenuNotHandled_allowed, + R.bool.config_cecRcProfileSourceRootMenuNotHandled_default); + + Setting rcProfileSourceSetupMenu = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU, + R.bool.config_cecRcProfileSourceSetupMenu_userConfigurable); + rcProfileSourceSetupMenu.registerValue( + HdmiControlManager.RC_PROFILE_SOURCE_SETUP_MENU_HANDLED, + R.bool.config_cecRcProfileSourceSetupMenuHandled_allowed, + R.bool.config_cecRcProfileSourceSetupMenuHandled_default); + rcProfileSourceSetupMenu.registerValue( + HdmiControlManager.RC_PROFILE_SOURCE_SETUP_MENU_NOT_HANDLED, + R.bool.config_cecRcProfileSourceSetupMenuNotHandled_allowed, + R.bool.config_cecRcProfileSourceSetupMenuNotHandled_default); + + Setting rcProfileSourceContentsMenu = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU, + R.bool.config_cecRcProfileSourceContentsMenu_userConfigurable); + rcProfileSourceContentsMenu.registerValue( + HdmiControlManager.RC_PROFILE_SOURCE_CONTENTS_MENU_HANDLED, + R.bool.config_cecRcProfileSourceContentsMenuHandled_allowed, + R.bool.config_cecRcProfileSourceContentsMenuHandled_default); + rcProfileSourceContentsMenu.registerValue( + HdmiControlManager.RC_PROFILE_SOURCE_CONTENTS_MENU_NOT_HANDLED, + R.bool.config_cecRcProfileSourceContentsMenuNotHandled_allowed, + R.bool.config_cecRcProfileSourceContentsMenuNotHandled_default); + + Setting rcProfileSourceTopMenu = registerSetting( + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU, + R.bool.config_cecRcProfileSourceTopMenu_userConfigurable); + rcProfileSourceTopMenu.registerValue( + HdmiControlManager.RC_PROFILE_SOURCE_TOP_MENU_HANDLED, + R.bool.config_cecRcProfileSourceTopMenuHandled_allowed, + R.bool.config_cecRcProfileSourceTopMenuHandled_default); + rcProfileSourceTopMenu.registerValue( + HdmiControlManager.RC_PROFILE_SOURCE_TOP_MENU_NOT_HANDLED, + R.bool.config_cecRcProfileSourceTopMenuNotHandled_allowed, + R.bool.config_cecRcProfileSourceTopMenuNotHandled_default); + + Setting rcProfileSourceMediaContextSensitiveMenu = registerSetting( + HdmiControlManager + .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU, + R.bool.config_cecRcProfileSourceMediaContextSensitiveMenu_userConfigurable); + rcProfileSourceMediaContextSensitiveMenu.registerValue( + HdmiControlManager.RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_HANDLED, + R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuHandled_allowed, + R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuHandled_default); + rcProfileSourceMediaContextSensitiveMenu.registerValue( + HdmiControlManager.RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_NOT_HANDLED, + R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_allowed, + R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_default); + + verifySettings(); + } + + HdmiCecConfig(@NonNull Context context) { + this(context, new StorageAdapter(context)); + } + + private Setting registerSetting(@NonNull @CecSettingName String name, + int userConfResId) { + Setting setting = new Setting(mContext, name, userConfResId); + mSettings.put(name, setting); + return setting; + } + + private void verifySettings() { + for (Setting setting: mSettings.values()) { + // This will throw an exception when a setting + // doesn't have a default value assigned. + setting.getDefaultValue(); + getStorage(setting); + getStorageKey(setting); + } + } + + @Nullable + private Setting getSetting(@NonNull String name) { + return mSettings.containsKey(name) ? mSettings.get(name) : null; } @Storage @@ -322,7 +524,7 @@ public class HdmiCecConfig { .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU: return STORAGE_SHARED_PREFS; default: - throw new RuntimeException("Invalid CEC setting '" + setting.getName() + throw new VerificationException("Invalid CEC setting '" + setting.getName() + "' storage."); } } @@ -359,7 +561,7 @@ public class HdmiCecConfig { .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU: return setting.getName(); default: - throw new RuntimeException("Invalid CEC setting '" + setting.getName() + throw new VerificationException("Invalid CEC setting '" + setting.getName() + "' storage key."); } } @@ -396,10 +598,6 @@ public class HdmiCecConfig { } } - private int getIntValue(@NonNull Value value) { - return Integer.decode(value.getIntValue()); - } - private void notifyGlobalSettingChanged(String setting) { switch (setting) { case Global.HDMI_CONTROL_ENABLED: @@ -533,41 +731,20 @@ public class HdmiCecConfig { * Returns a list of all settings based on the XML metadata. */ public @CecSettingName List<String> getAllSettings() { - if (mSystemConfig == null) { - return new ArrayList<String>(); - } - List<String> allSettings = new ArrayList<String>(); - for (Setting setting : mSystemConfig.getSetting()) { - allSettings.add(setting.getName()); - } - return allSettings; + return new ArrayList<>(mSettings.keySet()); } /** * Returns a list of user-modifiable settings based on the XML metadata. */ public @CecSettingName List<String> getUserSettings() { - if (mSystemConfig == null) { - return new ArrayList<String>(); - } - Set<String> userSettings = new HashSet<String>(); - // First read from the system config. - for (Setting setting : mSystemConfig.getSetting()) { + List<String> settings = new ArrayList<>(); + for (Setting setting: mSettings.values()) { if (setting.getUserConfigurable()) { - userSettings.add(setting.getName()); - } - } - if (mVendorOverride != null) { - // Next either add or remove based on the vendor override. - for (Setting setting : mVendorOverride.getSetting()) { - if (setting.getUserConfigurable()) { - userSettings.add(setting.getName()); - } else { - userSettings.remove(setting.getName()); - } + settings.add(setting.getName()); } } - return new ArrayList(userSettings); + return settings; } /** @@ -607,7 +784,7 @@ public class HdmiCecConfig { + "' is not a string-type setting."); } List<String> allowedValues = new ArrayList<String>(); - for (Value allowedValue : setting.getAllowedValues().getValue()) { + for (Value allowedValue : setting.getAllowedValues()) { allowedValues.add(allowedValue.getStringValue()); } return allowedValues; @@ -626,8 +803,8 @@ public class HdmiCecConfig { + "' is not a string-type setting."); } List<Integer> allowedValues = new ArrayList<Integer>(); - for (Value allowedValue : setting.getAllowedValues().getValue()) { - allowedValues.add(getIntValue(allowedValue)); + for (Value allowedValue : setting.getAllowedValues()) { + allowedValues.add(allowedValue.getIntValue()); } return allowedValues; } @@ -659,7 +836,7 @@ public class HdmiCecConfig { throw new IllegalArgumentException("Setting '" + name + "' is not a string-type setting."); } - return getIntValue(getSetting(name).getDefaultValue()); + return getSetting(name).getDefaultValue().getIntValue(); } /** @@ -691,7 +868,7 @@ public class HdmiCecConfig { + "' is not a int-type setting."); } HdmiLogger.debug("Getting CEC setting value '" + name + "'."); - String defaultValue = Integer.toString(getIntValue(setting.getDefaultValue())); + String defaultValue = Integer.toString(setting.getDefaultValue().getIntValue()); String value = retrieveValue(setting, defaultValue); return Integer.parseInt(value); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index d8914b389191..bdc4e66cf7f9 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -500,7 +500,8 @@ abstract class HdmiCecLocalDevice { HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address); // If no non-default display name is available for the device, request the devices OSD name. - if (cecDeviceInfo.getDisplayName().equals(HdmiUtils.getDefaultDeviceName(address))) { + if (cecDeviceInfo != null && cecDeviceInfo.getDisplayName().equals( + HdmiUtils.getDefaultDeviceName(address))) { mService.sendCecCommand( HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address)); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 7235a921254d..90d64339eac0 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -30,6 +30,7 @@ import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANAL import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; +import android.annotation.Nullable; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -1147,6 +1148,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { && getAvrDeviceInfo() != null; } + @Nullable @ServiceThreadOnly HdmiDeviceInfo getAvrDeviceInfo() { assertRunOnServiceThread(); @@ -1157,6 +1159,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return getSafeAvrDeviceInfo() != null; } + @Nullable HdmiDeviceInfo getSafeAvrDeviceInfo() { return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java index b748ae026cfc..7ceaa959212e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java @@ -185,6 +185,12 @@ public class HdmiCecNetwork { mLocalDevices.clear(); } + /** + * Get the device info of a local device or a device in the CEC network by a device id. + * @param id id of the device to get + * @return the device with the given id, or {@code null} + */ + @Nullable public HdmiDeviceInfo getDeviceInfo(int id) { return mDeviceInfos.get(id); } @@ -717,6 +723,7 @@ public class HdmiCecNetwork { * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. * Returns null if no logical address matched */ + @Nullable HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) { for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 115cafedca93..e6e2f9631d45 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -988,6 +988,7 @@ public class HdmiControlService extends SystemService { return mCecController.getVendorId(); } + @Nullable @ServiceThreadOnly HdmiDeviceInfo getDeviceInfo(int logicalAddress) { assertRunOnServiceThread(); diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java index 73ecbe0432b7..9d2db94cac8e 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback; import android.hardware.hdmi.IHdmiControlCallback; import android.util.Slog; @@ -62,8 +63,7 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { Slog.e(TAG, "Wrong arguments"); return null; } - return new OneTouchPlayAction(source, targetAddress, - callback); + return new OneTouchPlayAction(source, targetAddress, callback); } private OneTouchPlayAction(HdmiCecLocalDevice localDevice, int targetAddress, @@ -71,8 +71,8 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { this(localDevice, targetAddress, callback, localDevice.getDeviceInfo().getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0 - && localDevice.mService.getHdmiCecNetwork().getCecDeviceInfo( - targetAddress).getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0); + && getTargetCecVersion(localDevice, targetAddress) + >= HdmiControlManager.HDMI_CEC_VERSION_2_0); } @VisibleForTesting @@ -88,9 +88,9 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { // Because only source device can create this action, it's safe to cast. mSource = source(); sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress)); - boolean targetOnBefore = localDevice().mService.getHdmiCecNetwork() - .getCecDeviceInfo(mTargetAddress).getDevicePowerStatus() - == HdmiControlManager.POWER_STATUS_ON; + + boolean targetOnBefore = getTargetDevicePowerStatus(mSource, mTargetAddress, + HdmiControlManager.POWER_STATUS_UNKNOWN) == HdmiControlManager.POWER_STATUS_ON; broadcastActiveSource(); // If the device is not an audio system itself, request the connected audio system to // turn on. @@ -98,8 +98,8 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { sendCommand(HdmiCecMessageBuilder.buildSystemAudioModeRequest(getSourceAddress(), Constants.ADDR_AUDIO_SYSTEM, getSourcePath(), true)); } - int targetPowerStatus = localDevice().mService.getHdmiCecNetwork() - .getCecDeviceInfo(mTargetAddress).getDevicePowerStatus(); + int targetPowerStatus = getTargetDevicePowerStatus(mSource, mTargetAddress, + HdmiControlManager.POWER_STATUS_UNKNOWN); if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) { queryDevicePowerStatus(); } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) { @@ -179,4 +179,23 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { return sendStandbyOnSleep.equals(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST); } + private static int getTargetCecVersion(HdmiCecLocalDevice localDevice, + int targetLogicalAddress) { + HdmiDeviceInfo targetDevice = localDevice.mService.getHdmiCecNetwork().getCecDeviceInfo( + targetLogicalAddress); + if (targetDevice != null) { + return targetDevice.getCecVersion(); + } + return HdmiControlManager.HDMI_CEC_VERSION_1_4_B; + } + + private static int getTargetDevicePowerStatus(HdmiCecLocalDevice localDevice, + int targetLogicalAddress, int defaultPowerStatus) { + HdmiDeviceInfo targetDevice = localDevice.mService.getHdmiCecNetwork().getCecDeviceInfo( + targetLogicalAddress); + if (targetDevice != null) { + return targetDevice.getDevicePowerStatus(); + } + return defaultPowerStatus; + } } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java index f7e871d0b645..56e538b1abfa 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java @@ -17,6 +17,8 @@ package com.android.server.hdmi; import android.hardware.tv.cec.V1_0.SendMessageResult; + +import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; /** @@ -30,6 +32,14 @@ final class SystemAudioAutoInitiationAction extends HdmiCecFeatureAction { // <Give System Audio Mode Status> to AV Receiver. private static final int STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS = 1; + @VisibleForTesting + static final int RETRIES_ON_TIMEOUT = 1; + + // On some audio devices the <System Audio Mode Status> message can be delayed as the device + // is just waking up. Retry the <Give System Audio Mode Status> message to ensure we properly + // initialize system audio. + private int mRetriesOnTimeOut = RETRIES_ON_TIMEOUT; + SystemAudioAutoInitiationAction(HdmiCecLocalDevice source, int avrAddress) { super(source); mAvrAddress = avrAddress; @@ -100,6 +110,13 @@ final class SystemAudioAutoInitiationAction extends HdmiCecFeatureAction { switch (mState) { case STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS: + if (mRetriesOnTimeOut > 0) { + mRetriesOnTimeOut--; + addTimer(mState, HdmiConfig.TIMEOUT_MS); + sendGiveSystemAudioModeStatus(); + return; + } + handleSystemAudioModeStatusTimeout(); break; } diff --git a/services/core/java/com/android/server/hdmi/cec_config.xml b/services/core/java/com/android/server/hdmi/cec_config.xml deleted file mode 100644 index 191e725181ca..000000000000 --- a/services/core/java/com/android/server/hdmi/cec_config.xml +++ /dev/null @@ -1,133 +0,0 @@ -<?xml version='1.0' encoding='utf-8' standalone='yes' ?> -<cec-settings> - <setting name="hdmi_cec_enabled" - value-type="int" - user-configurable="true"> - <allowed-values> - <value int-value="0" /> - <value int-value="1" /> - </allowed-values> - <default-value int-value="1" /> - </setting> - <setting name="hdmi_cec_version" - value-type="int" - user-configurable="true"> - <allowed-values> - <value int-value="0x05" /> - <value int-value="0x06" /> - </allowed-values> - <default-value int-value="0x05" /> - </setting> - <setting name="send_standby_on_sleep" - value-type="string" - user-configurable="true"> - <allowed-values> - <value string-value="to_tv" /> - <value string-value="broadcast" /> - <value string-value="none" /> - </allowed-values> - <default-value string-value="to_tv" /> - </setting> - <setting name="power_state_change_on_active_source_lost" - value-type="string" - user-configurable="true"> - <allowed-values> - <value string-value="none" /> - <value string-value="standby_now" /> - </allowed-values> - <default-value string-value="none" /> - </setting> - <setting name="system_audio_mode_muting" - value-type="int" - user-configurable="true"> - <allowed-values> - <value int-value="0" /> - <value int-value="1" /> - </allowed-values> - <default-value int-value="1" /> - </setting> - <setting name="volume_control_enabled" - value-type="int" - user-configurable="true"> - <allowed-values> - <value int-value="0" /> - <value int-value="1" /> - </allowed-values> - <default-value int-value="1" /> - </setting> - <setting name="tv_wake_on_one_touch_play" - value-type="int" - user-configurable="true"> - <allowed-values> - <value int-value="0" /> - <value int-value="1" /> - </allowed-values> - <default-value int-value="1" /> - </setting> - <setting name="tv_send_standby_on_sleep" - value-type="int" - user-configurable="true"> - <allowed-values> - <value int-value="0" /> - <value int-value="1" /> - </allowed-values> - <default-value int-value="1" /> - </setting> - <setting name="rc_profile_tv" - value-type="int" - user-configurable="false"> - <allowed-values> - <value int-value="0x0" /> - <value int-value="0x2" /> - <value int-value="0x6" /> - <value int-value="0xA" /> - <value int-value="0xE" /> - </allowed-values> - <default-value int-value="0x0" /> - </setting> - <setting name="rc_profile_source_handles_root_menu" - value-type="int" - user-configurable="false"> - <allowed-values> - <value int-value="0" /> - <value int-value="1" /> - </allowed-values> - <default-value int-value="1" /> - </setting> - <setting name="rc_profile_source_handles_setup_menu" - value-type="int" - user-configurable="false"> - <allowed-values> - <value int-value="0" /> - <value int-value="1" /> - </allowed-values> - <default-value int-value="1" /> - </setting> - <setting name="rc_profile_source_handles_contents_menu" - value-type="int" - user-configurable="false"> - <allowed-values> - <value int-value="0" /> - <value int-value="1" /> - </allowed-values> - <default-value int-value="0" /> - </setting> - <setting name="rc_profile_source_handles_top_menu" - value-type="int" - user-configurable="false"> - <allowed-values> - <value int-value="0" /> - <value int-value="1" /> - </allowed-values> - <default-value int-value="0" /> - </setting> - <setting name="rc_profile_source_handles_media_context_sensitive_menu" - value-type="int" - user-configurable="false"> - <allowed-values> - <value int-value="0" /> - <value int-value="1" /> - </allowed-values> - <default-value int-value="0" /> - </setting> -</cec-settings> diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 7dc9a0b2a364..cbe6e69cbef3 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -304,7 +304,7 @@ public class InputManagerService extends IInputManager.Stub int displayId, InputApplicationHandle application); private static native void nativeSetFocusedDisplay(long ptr, int displayId); private static native boolean nativeTransferTouchFocus(long ptr, - IBinder fromChannelToken, IBinder toChannelToken); + IBinder fromChannelToken, IBinder toChannelToken, boolean isDragDrop); private static native void nativeSetPointerSpeed(long ptr, int speed); private static native void nativeSetShowTouches(long ptr, boolean enabled); private static native void nativeSetInteractive(long ptr, boolean interactive); @@ -1727,12 +1727,14 @@ public class InputManagerService extends IInputManager.Stub * @param fromChannel The channel of a window that currently has touch focus. * @param toChannel The channel of the window that should receive touch focus in * place of the first. + * @param isDragDrop True if transfer touch focus for drag and drop. * @return True if the transfer was successful. False if the window with the * specified channel did not actually have touch focus at the time of the request. */ public boolean transferTouchFocus(@NonNull InputChannel fromChannel, - @NonNull InputChannel toChannel) { - return nativeTransferTouchFocus(mPtr, fromChannel.getToken(), toChannel.getToken()); + @NonNull InputChannel toChannel, boolean isDragDrop) { + return nativeTransferTouchFocus(mPtr, fromChannel.getToken(), toChannel.getToken(), + isDragDrop); } /** @@ -1752,7 +1754,8 @@ public class InputManagerService extends IInputManager.Stub @NonNull IBinder toChannelToken) { Objects.nonNull(fromChannelToken); Objects.nonNull(toChannelToken); - return nativeTransferTouchFocus(mPtr, fromChannelToken, toChannelToken); + return nativeTransferTouchFocus(mPtr, fromChannelToken, toChannelToken, + false /* isDragDrop */); } @Override // Binder call diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index a89cb5554825..6ab4a69eaf65 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2621,9 +2621,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); // Dispatch display id for InputMethodService to update context display. - executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO( - MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken, - mMethodMap.get(mCurMethodId).getConfigChanges())); + executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( + MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken)); scheduleNotifyImeUidToAudioService(mCurMethodUid); if (mCurClient != null) { clearClientSessionLocked(mCurClient); @@ -4479,8 +4478,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } final IBinder token = (IBinder) args.arg2; ((IInputMethod) args.arg1).initializeInternal(token, msg.arg1, - new InputMethodPrivilegedOperationsImpl(this, token), - (int) args.arg3); + new InputMethodPrivilegedOperationsImpl(this, token)); } catch (RemoteException e) { } args.recycle(); @@ -5838,7 +5836,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @ShellCommandResult private int handleShellCommandTraceInputMethod(@NonNull ShellCommand shellCommand) { - int result = ImeTracing.getInstance().onShellCommand(shellCommand); + final String cmd = shellCommand.getNextArgRequired(); + final PrintWriter pw = shellCommand.getOutPrintWriter(); + switch (cmd) { + case "start": + ImeTracing.getInstance().getInstance().startTrace(pw); + break; + case "stop": + ImeTracing.getInstance().stopTrace(pw); + break; + default: + pw.println("Unknown command: " + cmd); + pw.println("Input method trace options:"); + pw.println(" start: Start tracing"); + pw.println(" stop: Stop tracing"); + return ShellCommandResult.FAILURE; + } boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled(); ArrayMap<IBinder, ClientState> clients; synchronized (mMethodMap) { @@ -5854,7 +5867,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } } - return result; + return ShellCommandResult.SUCCESS; } /** diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java index 5a90fa7a271c..7f47805ee121 100644 --- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java +++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java @@ -370,6 +370,7 @@ public class GeofenceManager extends LocationStatsEnums.USAGE_ENDED, LocationStatsEnums.API_REQUEST_GEOFENCE, registration.getIdentity().getPackageName(), + registration.getIdentity().getAttributionTag(), null, /* LocationRequest= */ null, /* hasListener= */ false, @@ -383,6 +384,7 @@ public class GeofenceManager extends LocationStatsEnums.USAGE_ENDED, LocationStatsEnums.API_REQUEST_GEOFENCE, registration.getIdentity().getPackageName(), + registration.getIdentity().getAttributionTag(), null, /* LocationRequest= */ null, /* hasListener= */ false, diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index b3119d7aa53e..8460d6797543 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -189,6 +189,7 @@ public final class GnssMeasurementsProvider extends LocationStatsEnums.USAGE_STARTED, LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER, registration.getIdentity().getPackageName(), + registration.getIdentity().getAttributionTag(), null, null, true, @@ -202,6 +203,7 @@ public final class GnssMeasurementsProvider extends LocationStatsEnums.USAGE_ENDED, LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER, registration.getIdentity().getPackageName(), + registration.getIdentity().getAttributionTag(), null, null, true, diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java index 1eb16184685d..936283deda8e 100644 --- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java @@ -83,6 +83,7 @@ public class GnssStatusProvider extends LocationStatsEnums.USAGE_STARTED, LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK, registration.getIdentity().getPackageName(), + registration.getIdentity().getAttributionTag(), null, null, true, @@ -96,6 +97,7 @@ public class GnssStatusProvider extends LocationStatsEnums.USAGE_ENDED, LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK, registration.getIdentity().getPackageName(), + registration.getIdentity().getAttributionTag(), null, null, true, diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index f275663a1309..1eef0de3a05d 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -27,6 +27,7 @@ import android.location.GnssMeasurementsEvent; import android.location.GnssNavigationMessage; import android.location.GnssStatus; import android.location.Location; +import android.os.Binder; import android.os.SystemClock; import android.util.Log; @@ -921,6 +922,7 @@ public class GnssNative { @NativeEntryPoint void reportGnssServiceDied() { + // Not necessary to clear (and restore) binder identity since it runs on another thread. Log.e(TAG, "gnss hal died - restarting shortly..."); // move to another thread just in case there is some awkward gnss thread dependency with @@ -940,96 +942,111 @@ public class GnssNative { @NativeEntryPoint void reportLocation(boolean hasLatLong, Location location) { - if (hasLatLong && !mHasFirstFix) { - mHasFirstFix = true; - - // notify status listeners - int ttff = (int) (SystemClock.elapsedRealtime() - mStartRealtimeMs); - for (int i = 0; i < mStatusCallbacks.length; i++) { - mStatusCallbacks[i].onReportFirstFix(ttff); + Binder.withCleanCallingIdentity(() -> { + if (hasLatLong && !mHasFirstFix) { + mHasFirstFix = true; + + // notify status listeners + int ttff = (int) (SystemClock.elapsedRealtime() - mStartRealtimeMs); + for (int i = 0; i < mStatusCallbacks.length; i++) { + mStatusCallbacks[i].onReportFirstFix(ttff); + } } - } - if (location.hasSpeed()) { - boolean exceeded = location.getSpeed() > ITAR_SPEED_LIMIT_METERS_PER_SECOND; - if (!mItarSpeedLimitExceeded && exceeded) { - Log.w(TAG, "speed nearing ITAR threshold - blocking further GNSS output"); - } else if (mItarSpeedLimitExceeded && !exceeded) { - Log.w(TAG, "speed leaving ITAR threshold - allowing further GNSS output"); + if (location.hasSpeed()) { + boolean exceeded = location.getSpeed() > ITAR_SPEED_LIMIT_METERS_PER_SECOND; + if (!mItarSpeedLimitExceeded && exceeded) { + Log.w(TAG, "speed nearing ITAR threshold - blocking further GNSS output"); + } else if (mItarSpeedLimitExceeded && !exceeded) { + Log.w(TAG, "speed leaving ITAR threshold - allowing further GNSS output"); + } + mItarSpeedLimitExceeded = exceeded; } - mItarSpeedLimitExceeded = exceeded; - } - if (mItarSpeedLimitExceeded) { - return; - } + if (mItarSpeedLimitExceeded) { + return; + } - for (int i = 0; i < mLocationCallbacks.length; i++) { - mLocationCallbacks[i].onReportLocation(hasLatLong, location); - } + for (int i = 0; i < mLocationCallbacks.length; i++) { + mLocationCallbacks[i].onReportLocation(hasLatLong, location); + } + }); } @NativeEntryPoint void reportStatus(@StatusCallbacks.GnssStatusValue int gnssStatus) { - for (int i = 0; i < mStatusCallbacks.length; i++) { - mStatusCallbacks[i].onReportStatus(gnssStatus); - } + Binder.withCleanCallingIdentity(() -> { + for (int i = 0; i < mStatusCallbacks.length; i++) { + mStatusCallbacks[i].onReportStatus(gnssStatus); + } + }); } @NativeEntryPoint void reportSvStatus(int svCount, int[] svidWithFlags, float[] cn0DbHzs, float[] elevations, float[] azimuths, float[] carrierFrequencies, float[] basebandCn0DbHzs) { - GnssStatus gnssStatus = GnssStatus.wrap(svCount, svidWithFlags, cn0DbHzs, elevations, - azimuths, carrierFrequencies, basebandCn0DbHzs); - for (int i = 0; i < mSvStatusCallbacks.length; i++) { - mSvStatusCallbacks[i].onReportSvStatus(gnssStatus); - } + Binder.withCleanCallingIdentity(() -> { + GnssStatus gnssStatus = GnssStatus.wrap(svCount, svidWithFlags, cn0DbHzs, elevations, + azimuths, carrierFrequencies, basebandCn0DbHzs); + for (int i = 0; i < mSvStatusCallbacks.length; i++) { + mSvStatusCallbacks[i].onReportSvStatus(gnssStatus); + } + }); } @NativeEntryPoint void reportAGpsStatus(int agpsType, int agpsStatus, byte[] suplIpAddr) { - mAGpsCallbacks.onReportAGpsStatus(agpsType, agpsStatus, suplIpAddr); + Binder.withCleanCallingIdentity( + () -> mAGpsCallbacks.onReportAGpsStatus(agpsType, agpsStatus, suplIpAddr)); } @NativeEntryPoint void reportNmea(long timestamp) { - if (mItarSpeedLimitExceeded) { - return; - } + Binder.withCleanCallingIdentity(() -> { + if (mItarSpeedLimitExceeded) { + return; + } - for (int i = 0; i < mNmeaCallbacks.length; i++) { - mNmeaCallbacks[i].onReportNmea(timestamp); - } + for (int i = 0; i < mNmeaCallbacks.length; i++) { + mNmeaCallbacks[i].onReportNmea(timestamp); + } + }); } @NativeEntryPoint void reportMeasurementData(GnssMeasurementsEvent event) { - if (mItarSpeedLimitExceeded) { - return; - } + Binder.withCleanCallingIdentity(() -> { + if (mItarSpeedLimitExceeded) { + return; + } - for (int i = 0; i < mMeasurementCallbacks.length; i++) { - mMeasurementCallbacks[i].onReportMeasurements(event); - } + for (int i = 0; i < mMeasurementCallbacks.length; i++) { + mMeasurementCallbacks[i].onReportMeasurements(event); + } + }); } @NativeEntryPoint void reportAntennaInfo(List<GnssAntennaInfo> antennaInfos) { - for (int i = 0; i < mAntennaInfoCallbacks.length; i++) { - mAntennaInfoCallbacks[i].onReportAntennaInfo(antennaInfos); - } + Binder.withCleanCallingIdentity(() -> { + for (int i = 0; i < mAntennaInfoCallbacks.length; i++) { + mAntennaInfoCallbacks[i].onReportAntennaInfo(antennaInfos); + } + }); } @NativeEntryPoint void reportNavigationMessage(GnssNavigationMessage event) { - if (mItarSpeedLimitExceeded) { - return; - } + Binder.withCleanCallingIdentity(() -> { + if (mItarSpeedLimitExceeded) { + return; + } - for (int i = 0; i < mNavigationMessageCallbacks.length; i++) { - mNavigationMessageCallbacks[i].onReportNavigationMessage(event); - } + for (int i = 0; i < mNavigationMessageCallbacks.length; i++) { + mNavigationMessageCallbacks[i].onReportNavigationMessage(event); + } + }); } @NativeEntryPoint @@ -1061,15 +1078,17 @@ public class GnssNative { private void onCapabilitiesChanged(GnssCapabilities oldCapabilities, GnssCapabilities newCapabilities) { - if (newCapabilities.equals(oldCapabilities)) { - return; - } + Binder.withCleanCallingIdentity(() -> { + if (newCapabilities.equals(oldCapabilities)) { + return; + } - Log.i(TAG, "gnss capabilities changed to " + newCapabilities); + Log.i(TAG, "gnss capabilities changed to " + newCapabilities); - for (int i = 0; i < mBaseCallbacks.length; i++) { - mBaseCallbacks[i].onCapabilitiesChanged(oldCapabilities, newCapabilities); - } + for (int i = 0; i < mBaseCallbacks.length; i++) { + mBaseCallbacks[i].onCapabilitiesChanged(oldCapabilities, newCapabilities); + } + }); } @NativeEntryPoint @@ -1089,88 +1108,103 @@ public class GnssNative { @NativeEntryPoint void reportLocationBatch(Location[] locations) { - for (int i = 0; i < mLocationCallbacks.length; i++) { - mLocationCallbacks[i].onReportLocations(locations); - } + Binder.withCleanCallingIdentity(() -> { + for (int i = 0; i < mLocationCallbacks.length; i++) { + mLocationCallbacks[i].onReportLocations(locations); + } + }); } @NativeEntryPoint void psdsDownloadRequest(int psdsType) { - mPsdsCallbacks.onRequestPsdsDownload(psdsType); + Binder.withCleanCallingIdentity(() -> mPsdsCallbacks.onRequestPsdsDownload(psdsType)); } @NativeEntryPoint void reportGeofenceTransition(int geofenceId, Location location, int transition, long transitionTimestamp) { - mGeofenceCallbacks.onReportGeofenceTransition(geofenceId, location, transition, - transitionTimestamp); + Binder.withCleanCallingIdentity( + () -> mGeofenceCallbacks.onReportGeofenceTransition(geofenceId, location, + transition, transitionTimestamp)); } @NativeEntryPoint void reportGeofenceStatus(int status, Location location) { - mGeofenceCallbacks.onReportGeofenceStatus(status, location); + Binder.withCleanCallingIdentity( + () -> mGeofenceCallbacks.onReportGeofenceStatus(status, location)); } @NativeEntryPoint void reportGeofenceAddStatus(int geofenceId, @GeofenceCallbacks.GeofenceStatus int status) { - mGeofenceCallbacks.onReportGeofenceAddStatus(geofenceId, status); + Binder.withCleanCallingIdentity( + () -> mGeofenceCallbacks.onReportGeofenceAddStatus(geofenceId, status)); } @NativeEntryPoint void reportGeofenceRemoveStatus(int geofenceId, @GeofenceCallbacks.GeofenceStatus int status) { - mGeofenceCallbacks.onReportGeofenceRemoveStatus(geofenceId, status); + Binder.withCleanCallingIdentity( + () -> mGeofenceCallbacks.onReportGeofenceRemoveStatus(geofenceId, status)); } @NativeEntryPoint void reportGeofencePauseStatus(int geofenceId, @GeofenceCallbacks.GeofenceStatus int status) { - mGeofenceCallbacks.onReportGeofencePauseStatus(geofenceId, status); + Binder.withCleanCallingIdentity( + () -> mGeofenceCallbacks.onReportGeofencePauseStatus(geofenceId, status)); } @NativeEntryPoint void reportGeofenceResumeStatus(int geofenceId, @GeofenceCallbacks.GeofenceStatus int status) { - mGeofenceCallbacks.onReportGeofenceResumeStatus(geofenceId, status); + Binder.withCleanCallingIdentity( + () -> mGeofenceCallbacks.onReportGeofenceResumeStatus(geofenceId, status)); } @NativeEntryPoint void reportNiNotification(int notificationId, int niType, int notifyFlags, int timeout, int defaultResponse, String requestorId, String text, int requestorIdEncoding, int textEncoding) { - mNotificationCallbacks.onReportNiNotification(notificationId, niType, notifyFlags, timeout, - defaultResponse, requestorId, text, requestorIdEncoding, textEncoding); + Binder.withCleanCallingIdentity( + () -> mNotificationCallbacks.onReportNiNotification(notificationId, niType, + notifyFlags, timeout, defaultResponse, requestorId, text, + requestorIdEncoding, textEncoding)); } @NativeEntryPoint void requestSetID(int flags) { - mAGpsCallbacks.onRequestSetID(flags); + Binder.withCleanCallingIdentity(() -> mAGpsCallbacks.onRequestSetID(flags)); } @NativeEntryPoint void requestLocation(boolean independentFromGnss, boolean isUserEmergency) { - mLocationRequestCallbacks.onRequestLocation(independentFromGnss, isUserEmergency); + Binder.withCleanCallingIdentity( + () -> mLocationRequestCallbacks.onRequestLocation(independentFromGnss, + isUserEmergency)); } @NativeEntryPoint void requestUtcTime() { - mTimeCallbacks.onRequestUtcTime(); + Binder.withCleanCallingIdentity(() -> mTimeCallbacks.onRequestUtcTime()); } @NativeEntryPoint void requestRefLocation() { - mLocationRequestCallbacks.onRequestRefLocation(); + Binder.withCleanCallingIdentity( + () -> mLocationRequestCallbacks.onRequestRefLocation()); } @NativeEntryPoint void reportNfwNotification(String proxyAppPackageName, byte protocolStack, String otherProtocolStackName, byte requestor, String requestorId, byte responseType, boolean inEmergencyMode, boolean isCachedLocation) { - mNotificationCallbacks.onReportNfwNotification(proxyAppPackageName, protocolStack, - otherProtocolStackName, requestor, requestorId, responseType, inEmergencyMode, - isCachedLocation); + Binder.withCleanCallingIdentity( + () -> mNotificationCallbacks.onReportNfwNotification(proxyAppPackageName, + protocolStack, otherProtocolStackName, requestor, requestorId, responseType, + inEmergencyMode, isCachedLocation)); } @NativeEntryPoint boolean isInEmergencySession() { - return mEmergencyHelper.isInEmergency(mConfiguration.getEsExtensionSec()); + return Binder.withCleanCallingIdentity( + () -> mEmergencyHelper.isInEmergency(mConfiguration.getEsExtensionSec())); } /** diff --git a/services/core/java/com/android/server/location/injector/LocationUsageLogger.java b/services/core/java/com/android/server/location/injector/LocationUsageLogger.java index 244a8e0daca0..af21bcbfb0ef 100644 --- a/services/core/java/com/android/server/location/injector/LocationUsageLogger.java +++ b/services/core/java/com/android/server/location/injector/LocationUsageLogger.java @@ -49,9 +49,9 @@ public class LocationUsageLogger { * Log a location API usage event. */ public void logLocationApiUsage(int usageType, int apiInUse, - String packageName, String provider, LocationRequest locationRequest, - boolean hasListener, boolean hasIntent, - Geofence geofence, boolean foreground) { + String packageName, String attributionTag, String provider, + LocationRequest locationRequest, boolean hasListener, + boolean hasIntent, Geofence geofence, boolean foreground) { try { if (hitApiUsageLogCap()) { return; @@ -84,7 +84,8 @@ public class LocationUsageLogger { isGeofenceNull ? LocationStatsEnums.RADIUS_UNKNOWN : bucketizeRadius(geofence.getRadius()), - categorizeActivityImportance(foreground)); + categorizeActivityImportance(foreground), + attributionTag); } catch (Exception e) { // Swallow exceptions to avoid crashing LMS. Log.w(TAG, "Failed to log API usage to statsd.", e); @@ -114,7 +115,8 @@ public class LocationUsageLogger { /* isListenerNull= */ true, /* isIntentNull= */ true), /* bucketizedRadius= */ 0, - LocationStatsEnums.IMPORTANCE_UNKNOWN); + LocationStatsEnums.IMPORTANCE_UNKNOWN, + /* attribution_tag */ null); } catch (Exception e) { Log.w(TAG, "Failed to log API usage to statsd.", e); } diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 2aa6f2869afb..dc8b1d001c74 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -1854,6 +1854,7 @@ public class LocationProviderManager extends LocationStatsEnums.USAGE_STARTED, LocationStatsEnums.API_REQUEST_LOCATION_UPDATES, registration.getIdentity().getPackageName(), + registration.getIdentity().getAttributionTag(), mName, registration.getRequest(), key instanceof PendingIntent, @@ -1882,6 +1883,7 @@ public class LocationProviderManager extends LocationStatsEnums.USAGE_ENDED, LocationStatsEnums.API_REQUEST_LOCATION_UPDATES, registration.getIdentity().getPackageName(), + registration.getIdentity().getAttributionTag(), mName, registration.getRequest(), key instanceof PendingIntent, diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 240464a560af..6e99cba6ea91 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -64,6 +64,8 @@ class RebootEscrowManager { @VisibleForTesting public static final String REBOOT_ESCROW_ARMED_KEY = "reboot_escrow_armed_count"; + static final String REBOOT_ESCROW_KEY_ARMED_TIMESTAMP = "reboot_escrow_key_stored_timestamp"; + /** * Number of boots until we consider the escrow data to be stale for the purposes of metrics. * <p> @@ -144,8 +146,7 @@ class RebootEscrowManager { private RebootEscrowProviderInterface createRebootEscrowProvider() { RebootEscrowProviderInterface rebootEscrowProvider; - if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, - "server_based_ror_enabled", false)) { + if (serverBasedResumeOnReboot()) { Slog.i(TAG, "Using server based resume on reboot"); rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage); } else { @@ -166,6 +167,11 @@ class RebootEscrowManager { handler.postDelayed(runnable, delayMillis); } + public boolean serverBasedResumeOnReboot() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, + "server_based_ror_enabled", false); + } + public Context getContext() { return mContext; } @@ -204,10 +210,12 @@ class RebootEscrowManager { DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS); } - public void reportMetric(boolean success) { - // TODO(b/179105110) design error code; and report the true value for other fields. - FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, 0, 1, 1, - -1, 0, -1); + public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, + int escrowDurationInSeconds, int vbmetaDigestStatus, + int durationSinceBootCompleteInSeconds) { + FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, success, + errorCode, serviceType, attemptCount, escrowDurationInSeconds, + vbmetaDigestStatus, durationSinceBootCompleteInSeconds); } public RebootEscrowEventLog getEventLog() { @@ -230,7 +238,7 @@ class RebootEscrowManager { mKeyStoreManager = injector.getKeyStoreManager(); } - private void onGetRebootEscrowKeyFailed(List<UserInfo> users) { + private void onGetRebootEscrowKeyFailed(List<UserInfo> users, int attemptCount) { Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); for (UserInfo user : users) { mStorage.removeRebootEscrow(user.id); @@ -238,7 +246,7 @@ class RebootEscrowManager { // Clear the old key in keystore. mKeyStoreManager.clearKeyStoreEncryptionKey(); - onEscrowRestoreComplete(false); + onEscrowRestoreComplete(false, attemptCount); } void loadRebootEscrowDataIfAvailable(Handler retryHandler) { @@ -276,7 +284,7 @@ class RebootEscrowManager { } Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts"); - onGetRebootEscrowKeyFailed(users); + onGetRebootEscrowKeyFailed(users, attemptNumber); } void loadRebootEscrowDataWithRetry(Handler retryHandler, int attemptNumber, @@ -299,7 +307,7 @@ class RebootEscrowManager { } if (escrowKey == null) { - onGetRebootEscrowKeyFailed(users); + onGetRebootEscrowKeyFailed(users, attemptNumber + 1); return; } @@ -313,16 +321,35 @@ class RebootEscrowManager { // Clear the old key in keystore. A new key will be generated by new RoR requests. mKeyStoreManager.clearKeyStoreEncryptionKey(); - onEscrowRestoreComplete(allUsersUnlocked); + onEscrowRestoreComplete(allUsersUnlocked, attemptNumber + 1); + } + + private void reportMetricOnRestoreComplete(boolean success, int attemptCount) { + int serviceType = mInjector.serverBasedResumeOnReboot() + ? FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__SERVER_BASED + : FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__HAL; + + long armedTimestamp = mStorage.getLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, -1, + USER_SYSTEM); + mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM); + int escrowDurationInSeconds = armedTimestamp != -1 + ? (int) (System.currentTimeMillis() - armedTimestamp) / 1000 : -1; + + // TODO(b/179105110) design error code; and report the true value for other fields. + int vbmetaDigestStatus = FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT; + + mInjector.reportMetric(success, 0 /* error code */, serviceType, attemptCount, + escrowDurationInSeconds, vbmetaDigestStatus, -1); } - private void onEscrowRestoreComplete(boolean success) { + private void onEscrowRestoreComplete(boolean success, int attemptCount) { int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, -1, USER_SYSTEM); mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); int bootCountDelta = mInjector.getBootCount() - previousBootCount; if (success || (previousBootCount != -1 && bootCountDelta <= BOOT_COUNT_TOLERANCE)) { - mInjector.reportMetric(success); + reportMetricOnRestoreComplete(success, attemptCount); } } @@ -478,6 +505,8 @@ class RebootEscrowManager { boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk); if (armedRebootEscrow) { mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM); + mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, System.currentTimeMillis(), + USER_SYSTEM); mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS); } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java index 35571f1f2728..e75aae1f99aa 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java @@ -16,7 +16,7 @@ package com.android.server.locksettings.recoverablekeystore; -import android.security.keystore.AndroidKeyStoreSecretKey; +import javax.crypto.SecretKey; /** * Used to unwrap recoverable keys before syncing them with remote storage. @@ -30,7 +30,7 @@ import android.security.keystore.AndroidKeyStoreSecretKey; public class PlatformDecryptionKey { private final int mGenerationId; - private final AndroidKeyStoreSecretKey mKey; + private final SecretKey mKey; /** * A new instance. @@ -40,7 +40,7 @@ public class PlatformDecryptionKey { * * @hide */ - public PlatformDecryptionKey(int generationId, AndroidKeyStoreSecretKey key) { + public PlatformDecryptionKey(int generationId, SecretKey key) { mGenerationId = generationId; mKey = key; } @@ -59,7 +59,7 @@ public class PlatformDecryptionKey { * * @hide */ - public AndroidKeyStoreSecretKey getKey() { + public SecretKey getKey() { return mKey; } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java index 38f5b45ea190..ee334462f7be 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java @@ -16,7 +16,7 @@ package com.android.server.locksettings.recoverablekeystore; -import android.security.keystore.AndroidKeyStoreSecretKey; +import javax.crypto.SecretKey; /** * Private key stored in AndroidKeyStore. Used to wrap recoverable keys before writing them to disk. @@ -33,7 +33,7 @@ import android.security.keystore.AndroidKeyStoreSecretKey; public class PlatformEncryptionKey { private final int mGenerationId; - private final AndroidKeyStoreSecretKey mKey; + private final SecretKey mKey; /** * A new instance. @@ -41,7 +41,7 @@ public class PlatformEncryptionKey { * @param generationId The generation ID of the key. * @param key The secret key handle. Can be used to encrypt WITHOUT requiring screen unlock. */ - public PlatformEncryptionKey(int generationId, AndroidKeyStoreSecretKey key) { + public PlatformEncryptionKey(int generationId, SecretKey key) { mGenerationId = generationId; mKey = key; } @@ -56,7 +56,7 @@ public class PlatformEncryptionKey { /** * Returns the actual key, which can only be used to encrypt. */ - public AndroidKeyStoreSecretKey getKey() { + public SecretKey getKey() { return mKey; } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java index f448a6ef8c0b..f32af5434c43 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java @@ -21,7 +21,6 @@ import android.content.Context; import android.os.RemoteException; import android.os.UserHandle; import android.security.GateKeeper; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; @@ -237,7 +236,7 @@ public class PlatformKeyManager { if (!isKeyLoaded(userId, generationId)) { throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias); } - AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( + SecretKey key = (SecretKey) mKeyStore.getKey( alias, /*password=*/ null); return new PlatformEncryptionKey(generationId, key); } @@ -289,7 +288,7 @@ public class PlatformKeyManager { if (!isKeyLoaded(userId, generationId)) { throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias); } - AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( + SecretKey key = (SecretKey) mKeyStore.getKey( alias, /*password=*/ null); return new PlatformDecryptionKey(generationId, key); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0cc9f9e150c6..d5a9e3c0d4f8 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -7376,15 +7376,7 @@ public class NotificationManagerService extends SystemService { // so need to check the notification still valide for vibrate. synchronized (mNotificationLock) { if (mNotificationsByKey.get(record.getKey()) != null) { - // Vibrator checks the appops for the op package, not the caller, - // so we need to add the bypass dnd flag to be heard. it's ok to - // always add this flag here because we've already checked that we can - // bypass dnd - AudioAttributes.Builder aab = - new AudioAttributes.Builder(record.getAudioAttributes()) - .setFlags(FLAG_BYPASS_INTERRUPTION_POLICY); - mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getOpPkg(), - effect, "Notification (delayed)", aab.build()); + vibrate(record, effect, true); } else { Slog.e(TAG, "No vibration for canceled notification : " + record.getKey()); @@ -7392,8 +7384,7 @@ public class NotificationManagerService extends SystemService { } }).start(); } else { - mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getPackageName(), - effect, "Notification", record.getAudioAttributes()); + vibrate(record, effect, false); } return true; } finally{ @@ -7401,6 +7392,16 @@ public class NotificationManagerService extends SystemService { } } + private void vibrate(NotificationRecord record, VibrationEffect effect, boolean delayed) { + // We need to vibrate as "android" so we can breakthrough DND. VibratorManagerService + // doesn't have a concept of vibrating on an app's behalf, so add the app information + // to the reason so we can still debug from bugreports + String reason = "Notification (" + record.getSbn().getOpPkg() + " " + + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : ""); + mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME, + effect, reason, record.getAudioAttributes()); + } + private boolean isNotificationForCurrentUser(NotificationRecord record) { final int currentUser; final long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 1acbabda9e19..ca8202f5f94b 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -105,6 +105,12 @@ public class AppsFilter implements Watchable, Snappable { private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>(); /** + * A mapping from the set of App IDs that query other App IDs via library name to the + * list of packages that they can see. + */ + private final SparseSetArray<Integer> mQueryableViaUsesLibrary = new SparseSetArray<>(); + + /** * Executor for running reasonably short background tasks such as building the initial * visibility cache. */ @@ -239,6 +245,7 @@ public class AppsFilter implements Watchable, Snappable { Snapshots.copy(mImplicitlyQueryable, orig.mImplicitlyQueryable); Snapshots.copy(mQueriesViaPackage, orig.mQueriesViaPackage); Snapshots.copy(mQueriesViaComponent, orig.mQueriesViaComponent); + Snapshots.copy(mQueryableViaUsesLibrary, orig.mQueryableViaUsesLibrary); mQueriesViaComponentRequireRecompute = orig.mQueriesViaComponentRequireRecompute; mForceQueryable.addAll(orig.mForceQueryable); mForceQueryableByDevicePackageNames = orig.mForceQueryableByDevicePackageNames; @@ -508,6 +515,22 @@ public class AppsFilter implements Watchable, Snappable { return false; } + private static boolean canQueryViaUsesLibrary(AndroidPackage querying, + AndroidPackage potentialTarget) { + if (potentialTarget.getLibraryNames().isEmpty()) { + return false; + } + final List<String> libNames = potentialTarget.getLibraryNames(); + for (int i = 0, size = libNames.size(); i < size; i++) { + final String libName = libNames.get(i); + if (querying.getUsesLibraries().contains(libName) + || querying.getUsesOptionalLibraries().contains(libName)) { + return true; + } + } + return false; + } + private static boolean matchesProviders( Set<String> queriesAuthorities, AndroidPackage potentialTarget) { for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) { @@ -707,6 +730,9 @@ public class AppsFilter implements Watchable, Snappable { || canQueryAsInstaller(existingSetting, newPkg)) { mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId); } + if (canQueryViaUsesLibrary(existingPkg, newPkg)) { + mQueryableViaUsesLibrary.add(existingSetting.appId, newPkgSetting.appId); + } } // now we'll evaluate our new package's ability to see existing packages if (!mForceQueryable.contains(existingSetting.appId)) { @@ -718,6 +744,9 @@ public class AppsFilter implements Watchable, Snappable { || canQueryAsInstaller(newPkgSetting, existingPkg)) { mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId); } + if (canQueryViaUsesLibrary(newPkg, existingPkg)) { + mQueryableViaUsesLibrary.add(newPkgSetting.appId, existingSetting.appId); + } } // if either package instruments the other, mark both as visible to one another if (newPkgSetting.pkg != null && existingSetting.pkg != null @@ -1035,6 +1064,10 @@ public class AppsFilter implements Watchable, Snappable { for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) { mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.appId); } + mQueryableViaUsesLibrary.remove(setting.appId); + for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) { + mQueryableViaUsesLibrary.remove(mQueryableViaUsesLibrary.keyAt(i), setting.appId); + } mForceQueryable.remove(setting.appId); @@ -1315,6 +1348,18 @@ public class AppsFilter implements Watchable, Snappable { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueryableViaUsesLibrary"); + if (mQueryableViaUsesLibrary.contains(callingAppId, targetAppId)) { + if (DEBUG_LOGGING) { + log(callingSetting, targetPkgSetting, "queryable for library users"); + } + return false; + } + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + return true; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -1394,6 +1439,8 @@ public class AppsFilter implements Watchable, Snappable { filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId), mImplicitlyQueryable, " ", expandPackages); } + pw.println(" queryable via uses-library:"); + dumpQueriesMap(pw, filteringAppId, mQueryableViaUsesLibrary, " ", expandPackages); } private static void dumpQueriesMap(PrintWriter pw, @Nullable Integer filteringId, diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java index b34611b9cd6f..29322e2553e9 100644 --- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -180,6 +180,8 @@ public class DataLoaderManagerService extends SystemService { mId = id; mListener = listener; mDataLoader = null; + + callListener(IDataLoaderStatusListener.DATA_LOADER_BINDING); } @Override diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 58e2aa2b7602..bafe987cb546 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1011,8 +1011,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "DataLoader installation of APEX modules is not allowed."); } - if (this.params.dataLoaderParams.getComponentName().getPackageName() - == SYSTEM_DATA_LOADER_PACKAGE && mContext.checkCallingOrSelfPermission( + boolean systemDataLoader = SYSTEM_DATA_LOADER_PACKAGE.equals( + this.params.dataLoaderParams.getComponentName().getPackageName()); + if (systemDataLoader && mContext.checkCallingOrSelfPermission( Manifest.permission.USE_SYSTEM_DATA_LOADERS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("You need the " @@ -3060,7 +3061,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Also stage .dm.fsv_sig. .dm may be required to install with fs-verity signature on // supported on older devices. maybeStageFsveritySignatureLocked(dexMetadataFile, targetDexMetadataFile, - VerityUtils.isFsVeritySupported() && DexMetadataHelper.isFsVerityRequired()); + DexMetadataHelper.isFsVerityRequired()); } private void storeBytesToInstallationFile(final String localPath, final String absolutePath, @@ -3653,6 +3654,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void onStatusChanged(int dataLoaderId, int status) { switch (status) { + case IDataLoaderStatusListener.DATA_LOADER_BINDING: case IDataLoaderStatusListener.DATA_LOADER_STOPPED: case IDataLoaderStatusListener.DATA_LOADER_DESTROYED: return; @@ -3763,8 +3765,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { healthCheckParams.unhealthyTimeoutMs = INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS; healthCheckParams.unhealthyMonitoringMs = INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS; - final boolean systemDataLoader = - params.getComponentName().getPackageName() == SYSTEM_DATA_LOADER_PACKAGE; + final boolean systemDataLoader = SYSTEM_DATA_LOADER_PACKAGE.equals( + params.getComponentName().getPackageName()); final IStorageHealthListener healthListener = new IStorageHealthListener.Stub() { @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ff87f1c4b4ca..dfe72b26f72a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -410,6 +410,7 @@ import com.android.server.utils.Watched; import com.android.server.utils.WatchedArrayMap; import com.android.server.utils.WatchedLongSparseArray; import com.android.server.utils.WatchedSparseBooleanArray; +import com.android.server.utils.WatchedSparseIntArray; import com.android.server.utils.Watcher; import com.android.server.wm.ActivityTaskManagerInternal; @@ -892,7 +893,7 @@ public class PackageManagerService extends IPackageManager.Stub // that created the isolated process. @Watched @GuardedBy("mLock") - final SparseIntArray mIsolatedOwners = new SparseIntArray(); + final WatchedSparseIntArray mIsolatedOwners = new WatchedSparseIntArray(); /** * Tracks new system packages [received in an OTA] that we expect to @@ -1795,7 +1796,7 @@ public class PackageManagerService extends IPackageManager.Stub public static final int SNAPPED = 2; public final Settings settings; - public final SparseIntArray isolatedOwners; + public final WatchedSparseIntArray isolatedOwners; public final WatchedArrayMap<String, AndroidPackage> packages; public final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibs; public final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> staticLibs; @@ -1814,7 +1815,7 @@ public class PackageManagerService extends IPackageManager.Stub Snapshot(int type) { if (type == Snapshot.SNAPPED) { settings = mSettings.snapshot(); - isolatedOwners = mIsolatedOwners.clone(); + isolatedOwners = mIsolatedOwners.snapshot(); packages = mPackages.snapshot(); sharedLibs = mSharedLibraries.snapshot(); staticLibs = mStaticLibsByDeclaringPackage.snapshot(); @@ -2016,7 +2017,7 @@ public class PackageManagerService extends IPackageManager.Stub // Cached attributes. The names in this class are the same as the // names in PackageManagerService; see that class for documentation. private final Settings mSettings; - private final SparseIntArray mIsolatedOwners; + private final WatchedSparseIntArray mIsolatedOwners; private final WatchedArrayMap<String, AndroidPackage> mPackages; private final WatchedArrayMap<ComponentName, ParsedInstrumentation> mInstrumentation; @@ -3551,7 +3552,7 @@ public class PackageManagerService extends IPackageManager.Stub public String getInstantAppPackageName(int callingUid) { // If the caller is an isolated app use the owner's uid for the lookup. if (Process.isIsolated(callingUid)) { - callingUid = mIsolatedOwners.get(callingUid); + callingUid = getIsolatedOwner(callingUid); } final int appId = UserHandle.getAppId(callingUid); final Object obj = mSettings.getSettingLPr(appId); @@ -3563,6 +3564,19 @@ public class PackageManagerService extends IPackageManager.Stub return null; } + /** + * Finds the owner for the provided isolated UID. Throws IllegalStateException if no such + * isolated UID is found. + */ + private int getIsolatedOwner(int isolatedUid) { + final int ownerUid = mIsolatedOwners.get(isolatedUid, -1); + if (ownerUid == -1) { + throw new IllegalStateException( + "No owner UID found for isolated UID " + isolatedUid); + } + return ownerUid; + } + public String resolveExternalPackageNameLPr(AndroidPackage pkg) { if (pkg.getStaticSharedLibName() != null) { return pkg.getManifestPackageName(); @@ -3929,7 +3943,7 @@ public class PackageManagerService extends IPackageManager.Stub public boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId, int callingUid) { if (Process.isIsolated(callingUid)) { - callingUid = mIsolatedOwners.get(callingUid); + callingUid = getIsolatedOwner(callingUid); } final PackageSetting ps = mSettings.getPackageLPr(packageName); final boolean returnAllowed = @@ -4083,7 +4097,7 @@ public class PackageManagerService extends IPackageManager.Stub @Nullable ComponentName component, @ComponentType int componentType, int userId) { // if we're in an isolated process, get the real calling UID if (Process.isIsolated(callingUid)) { - callingUid = mIsolatedOwners.get(callingUid); + callingUid = getIsolatedOwner(callingUid); } final String instantAppPkgName = getInstantAppPackageName(callingUid); final boolean callerIsInstantApp = instantAppPkgName != null; @@ -6164,6 +6178,7 @@ public class PackageManagerService extends IPackageManager.Stub mAppsFilter.registerObserver(mWatcher); mInstantAppRegistry.registerObserver(mWatcher); mSettings.registerObserver(mWatcher); + mIsolatedOwners.registerObserver(mWatcher); // If neither "build" attribute is true then this may be a mockito test, and verification // can fail as a false positive. Watchable.verifyWatchedAttributes(this, mWatcher, !(mIsEngBuild || mIsUserDebugBuild)); @@ -27803,13 +27818,23 @@ public class PackageManagerService extends IPackageManager.Stub } private static String getDefaultTimeouts() { - return DeviceConfig.getString(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, - PROPERTY_INCFS_DEFAULT_TIMEOUTS, ""); + final long token = Binder.clearCallingIdentity(); + try { + return DeviceConfig.getString(NAMESPACE_PACKAGE_MANAGER_SERVICE, + PROPERTY_INCFS_DEFAULT_TIMEOUTS, ""); + } finally { + Binder.restoreCallingIdentity(token); + } } private static String getKnownDigestersList() { - return DeviceConfig.getString(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, - PROPERTY_KNOWN_DIGESTERS_LIST, ""); + final long token = Binder.clearCallingIdentity(); + try { + return DeviceConfig.getString(NAMESPACE_PACKAGE_MANAGER_SERVICE, + PROPERTY_KNOWN_DIGESTERS_LIST, ""); + } finally { + Binder.restoreCallingIdentity(token); + } } /** diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index d1cf55de7254..38cba4ca9e20 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1889,231 +1889,248 @@ public class ShortcutService extends IShortcutService.Stub { @Override public void setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId, @NonNull AndroidFuture callback) { - verifyCaller(packageName, userId); + try { + verifyCaller(packageName, userId); - final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); - verifyShortcutInfoPackages(packageName, newShortcuts); - final int size = newShortcuts.size(); + final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); + final int size = newShortcuts.size(); - final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( - injectBinderCallingPid(), injectBinderCallingUid()); + final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( + injectBinderCallingPid(), injectBinderCallingUid()); - List<ShortcutInfo> changedShortcuts = null; - List<ShortcutInfo> removedShortcuts = null; + List<ShortcutInfo> changedShortcuts = null; + List<ShortcutInfo> removedShortcuts = null; - synchronized (mLock) { - throwIfUserLockedL(userId); + synchronized (mLock) { + throwIfUserLockedL(userId); - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); - ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); - ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); + ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); + ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); - fillInDefaultActivity(newShortcuts); + fillInDefaultActivity(newShortcuts); - ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET); + ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET); - // Throttling. - if (!ps.tryApiCall(unlimited)) { - callback.complete(false); - } + // Throttling. + if (!ps.tryApiCall(unlimited)) { + callback.complete(false); + return; + } - // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). - ps.clearAllImplicitRanks(); - assignImplicitRanks(newShortcuts); + // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). + ps.clearAllImplicitRanks(); + assignImplicitRanks(newShortcuts); - for (int i = 0; i < size; i++) { - fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); - } + for (int i = 0; i < size; i++) { + fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); + } - ArrayList<ShortcutInfo> cachedOrPinned = new ArrayList<>(); - ps.findAll(cachedOrPinned, (ShortcutInfo si) -> si.isVisibleToPublisher() - && si.isDynamic() && (si.isCached() || si.isPinned()), - ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); + ArrayList<ShortcutInfo> cachedOrPinned = new ArrayList<>(); + ps.findAll(cachedOrPinned, (ShortcutInfo si) -> si.isVisibleToPublisher() + && si.isDynamic() && (si.isCached() || si.isPinned()), + ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); - // First, remove all un-pinned and non-cached; dynamic shortcuts - removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); + // First, remove all un-pinned and non-cached; dynamic shortcuts + removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); - // Then, add/update all. We need to make sure to take over "pinned" flag. - for (int i = 0; i < size; i++) { - final ShortcutInfo newShortcut = newShortcuts.get(i); - ps.addOrReplaceDynamicShortcut(newShortcut); - } + // Then, add/update all. We need to make sure to take over "pinned" flag. + for (int i = 0; i < size; i++) { + final ShortcutInfo newShortcut = newShortcuts.get(i); + ps.addOrReplaceDynamicShortcut(newShortcut); + } - // Lastly, adjust the ranks. - ps.adjustRanks(); + // Lastly, adjust the ranks. + ps.adjustRanks(); - changedShortcuts = prepareChangedShortcuts( - cachedOrPinned, newShortcuts, removedShortcuts, ps); - } + changedShortcuts = prepareChangedShortcuts( + cachedOrPinned, newShortcuts, removedShortcuts, ps); + } - packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); + packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); - verifyStates(); + verifyStates(); - callback.complete(true); + callback.complete(true); + } catch (Exception e) { + callback.completeExceptionally(e); + } } @Override public void updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId, AndroidFuture callback) { - verifyCaller(packageName, userId); + try { + verifyCaller(packageName, userId); - final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); - verifyShortcutInfoPackages(packageName, newShortcuts); - final int size = newShortcuts.size(); + final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); + final int size = newShortcuts.size(); - final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( - injectBinderCallingPid(), injectBinderCallingUid()); + final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( + injectBinderCallingPid(), injectBinderCallingUid()); - final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1); + final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1); - synchronized (mLock) { - throwIfUserLockedL(userId); + synchronized (mLock) { + throwIfUserLockedL(userId); - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); - ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); - ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); + ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); + ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); - // For update, don't fill in the default activity. Having null activity means - // "don't update the activity" here. + // For update, don't fill in the default activity. Having null activity means + // "don't update the activity" here. - ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE); + ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE); - // Throttling. - if (!ps.tryApiCall(unlimited)) { - callback.complete(false); - return; - } + // Throttling. + if (!ps.tryApiCall(unlimited)) { + callback.complete(false); + return; + } - // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). - ps.clearAllImplicitRanks(); - assignImplicitRanks(newShortcuts); + // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). + ps.clearAllImplicitRanks(); + assignImplicitRanks(newShortcuts); - for (int i = 0; i < size; i++) { - final ShortcutInfo source = newShortcuts.get(i); - fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); + for (int i = 0; i < size; i++) { + final ShortcutInfo source = newShortcuts.get(i); + fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); - ps.mutateShortcut(source.getId(), null, target -> { - // Invisible shortcuts can't be updated. - if (target == null || !target.isVisibleToPublisher()) { - return; - } + ps.mutateShortcut(source.getId(), null, target -> { + // Invisible shortcuts can't be updated. + if (target == null || !target.isVisibleToPublisher()) { + return; + } - if (target.isEnabled() != source.isEnabled()) { - Slog.w(TAG, - "ShortcutInfo.enabled cannot be changed with updateShortcuts()"); - } + if (target.isEnabled() != source.isEnabled()) { + Slog.w(TAG, "ShortcutInfo.enabled cannot be changed with" + + " updateShortcuts()"); + } - if (target.isLongLived() != source.isLongLived()) { - Slog.w(TAG, - "ShortcutInfo.longLived cannot be changed with updateShortcuts()"); - } + if (target.isLongLived() != source.isLongLived()) { + Slog.w(TAG, + "ShortcutInfo.longLived cannot be changed with" + + " updateShortcuts()"); + } - // When updating the rank, we need to insert between existing ranks, so set - // this setRankChanged, and also copy the implicit rank fo adjustRanks(). - if (source.hasRank()) { - target.setRankChanged(); - target.setImplicitRank(source.getImplicitRank()); - } + // When updating the rank, we need to insert between existing ranks, so set + // this setRankChanged, and also copy the implicit rank fo adjustRanks(). + if (source.hasRank()) { + target.setRankChanged(); + target.setImplicitRank(source.getImplicitRank()); + } - final boolean replacingIcon = (source.getIcon() != null); - if (replacingIcon) { - removeIconLocked(target); - } + final boolean replacingIcon = (source.getIcon() != null); + if (replacingIcon) { + removeIconLocked(target); + } - // Note copyNonNullFieldsFrom() does the "updatable with?" check too. - target.copyNonNullFieldsFrom(source); - target.setTimestamp(injectCurrentTimeMillis()); + // Note copyNonNullFieldsFrom() does the "updatable with?" check too. + target.copyNonNullFieldsFrom(source); + target.setTimestamp(injectCurrentTimeMillis()); - if (replacingIcon) { - saveIconAndFixUpShortcutLocked(target); - } + if (replacingIcon) { + saveIconAndFixUpShortcutLocked(target); + } - // When we're updating any resource related fields, re-extract the res names and - // the values. - if (replacingIcon || source.hasStringResources()) { - fixUpShortcutResourceNamesAndValues(target); - } + // When we're updating any resource related fields, re-extract the res + // names and the values. + if (replacingIcon || source.hasStringResources()) { + fixUpShortcutResourceNamesAndValues(target); + } - changedShortcuts.add(target); - }); - } + changedShortcuts.add(target); + }); + } - // Lastly, adjust the ranks. - ps.adjustRanks(); - } - packageShortcutsChanged(packageName, userId, - changedShortcuts.isEmpty() ? null : changedShortcuts, null); + // Lastly, adjust the ranks. + ps.adjustRanks(); + } + packageShortcutsChanged(packageName, userId, + changedShortcuts.isEmpty() ? null : changedShortcuts, null); - verifyStates(); + verifyStates(); - callback.complete(true); + callback.complete(true); + } catch (Exception e) { + callback.completeExceptionally(e); + } } @Override public void addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId, AndroidFuture callback) { - verifyCaller(packageName, userId); + try { + verifyCaller(packageName, userId); - final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); - verifyShortcutInfoPackages(packageName, newShortcuts); - final int size = newShortcuts.size(); + final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); + verifyShortcutInfoPackages(packageName, newShortcuts); + final int size = newShortcuts.size(); - final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( - injectBinderCallingPid(), injectBinderCallingUid()); + final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( + injectBinderCallingPid(), injectBinderCallingUid()); - List<ShortcutInfo> changedShortcuts = null; + List<ShortcutInfo> changedShortcuts = null; - synchronized (mLock) { - throwIfUserLockedL(userId); + synchronized (mLock) { + throwIfUserLockedL(userId); - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); + final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, + userId); - ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); - ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); + ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); + ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); - fillInDefaultActivity(newShortcuts); + fillInDefaultActivity(newShortcuts); - ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD); + ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD); - // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). - ps.clearAllImplicitRanks(); - assignImplicitRanks(newShortcuts); + // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). + ps.clearAllImplicitRanks(); + assignImplicitRanks(newShortcuts); - // Throttling. - if (!ps.tryApiCall(unlimited)) { - callback.complete(false); - return; - } - for (int i = 0; i < size; i++) { - final ShortcutInfo newShortcut = newShortcuts.get(i); + // Throttling. + if (!ps.tryApiCall(unlimited)) { + callback.complete(false); + return; + } + for (int i = 0; i < size; i++) { + final ShortcutInfo newShortcut = newShortcuts.get(i); - // Validate the shortcut. - fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); + // Validate the shortcut. + fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); - // When ranks are changing, we need to insert between ranks, so set the - // "rank changed" flag. - newShortcut.setRankChanged(); + // When ranks are changing, we need to insert between ranks, so set the + // "rank changed" flag. + newShortcut.setRankChanged(); - // Add it. - ps.addOrReplaceDynamicShortcut(newShortcut); + // Add it. + ps.addOrReplaceDynamicShortcut(newShortcut); - if (changedShortcuts == null) { - changedShortcuts = new ArrayList<>(1); + if (changedShortcuts == null) { + changedShortcuts = new ArrayList<>(1); + } + changedShortcuts.add(newShortcut); } - changedShortcuts.add(newShortcut); - } - // Lastly, adjust the ranks. - ps.adjustRanks(); - } - packageShortcutsChanged(packageName, userId, changedShortcuts, null); + // Lastly, adjust the ranks. + ps.adjustRanks(); + } + packageShortcutsChanged(packageName, userId, changedShortcuts, null); - verifyStates(); + verifyStates(); - callback.complete(true); + callback.complete(true); + } catch (Exception e) { + callback.completeExceptionally(e); + } } @Override @@ -2180,31 +2197,40 @@ public class ShortcutService extends IShortcutService.Stub { @Override public void requestPinShortcut(String packageName, ShortcutInfo shortcut, IntentSender resultIntent, int userId, AndroidFuture callback) { - Objects.requireNonNull(shortcut); - Objects.requireNonNull(callback); - Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); - callback.complete(requestPinItem(packageName, userId, shortcut, null, null, resultIntent)); + try { + Objects.requireNonNull(shortcut); + Objects.requireNonNull(callback); + Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); + callback.complete( + requestPinItem(packageName, userId, shortcut, null, null, resultIntent)); + } catch (Exception e) { + callback.completeExceptionally(e); + } } @Override public void createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId, AndroidFuture callback) throws RemoteException { - Objects.requireNonNull(shortcut); - Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); - verifyCaller(packageName, userId); - verifyShortcutInfoPackage(packageName, shortcut); + try { + Objects.requireNonNull(shortcut); + Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); + verifyCaller(packageName, userId); + verifyShortcutInfoPackage(packageName, shortcut); - final Intent ret; - synchronized (mLock) { - throwIfUserLockedL(userId); + final Intent ret; + synchronized (mLock) { + throwIfUserLockedL(userId); - // Send request to the launcher, if supported. - ret = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId); - } + // Send request to the launcher, if supported. + ret = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId); + } - verifyStates(); - callback.complete(ret); + verifyStates(); + callback.complete(ret); + } catch (Exception e) { + callback.completeExceptionally(e); + } } /** @@ -2464,47 +2490,57 @@ public class ShortcutService extends IShortcutService.Stub { public void getShortcuts(String packageName, @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId, AndroidFuture<ParceledListSlice<ShortcutInfo>> callback) { - verifyCaller(packageName, userId); - - synchronized (mLock) { - throwIfUserLockedL(userId); - - final boolean matchDynamic = (matchFlags & ShortcutManager.FLAG_MATCH_DYNAMIC) != 0; - final boolean matchPinned = (matchFlags & ShortcutManager.FLAG_MATCH_PINNED) != 0; - final boolean matchManifest = (matchFlags & ShortcutManager.FLAG_MATCH_MANIFEST) != 0; - final boolean matchCached = (matchFlags & ShortcutManager.FLAG_MATCH_CACHED) != 0; + try { + verifyCaller(packageName, userId); - final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0) - | (matchPinned ? ShortcutInfo.FLAG_PINNED : 0) - | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0) - | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0); + synchronized (mLock) { + throwIfUserLockedL(userId); - callback.complete(getShortcutsWithQueryLocked( - packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - (ShortcutInfo si) -> - si.isVisibleToPublisher() && (si.getFlags() & shortcutFlags) != 0)); + final boolean matchDynamic = (matchFlags & ShortcutManager.FLAG_MATCH_DYNAMIC) != 0; + final boolean matchPinned = (matchFlags & ShortcutManager.FLAG_MATCH_PINNED) != 0; + final boolean matchManifest = + (matchFlags & ShortcutManager.FLAG_MATCH_MANIFEST) != 0; + final boolean matchCached = (matchFlags & ShortcutManager.FLAG_MATCH_CACHED) != 0; + + final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0) + | (matchPinned ? ShortcutInfo.FLAG_PINNED : 0) + | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0) + | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0); + + callback.complete(getShortcutsWithQueryLocked( + packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, + (ShortcutInfo si) -> + si.isVisibleToPublisher() && (si.getFlags() & shortcutFlags) != 0)); + } + } catch (Exception e) { + callback.completeExceptionally(e); } } @Override public void getShareTargets(String packageName, IntentFilter filter, @UserIdInt int userId, AndroidFuture<ParceledListSlice> callback) { - Preconditions.checkStringNotEmpty(packageName, "packageName"); - Objects.requireNonNull(filter, "intentFilter"); + try { + Preconditions.checkStringNotEmpty(packageName, "packageName"); + Objects.requireNonNull(filter, "intentFilter"); - verifyCaller(packageName, userId); - enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, - "getShareTargets"); + verifyCaller(packageName, userId); + enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, + "getShareTargets"); - synchronized (mLock) { - throwIfUserLockedL(userId); + synchronized (mLock) { + throwIfUserLockedL(userId); - final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>(); + final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>(); - final ShortcutUser user = getUserShortcutsLocked(userId); - user.forAllPackages(p -> shortcutInfoList.addAll(p.getMatchingShareTargets(filter))); + final ShortcutUser user = getUserShortcutsLocked(userId); + user.forAllPackages( + p -> shortcutInfoList.addAll(p.getMatchingShareTargets(filter))); - callback.complete(new ParceledListSlice<>(shortcutInfoList)); + callback.complete(new ParceledListSlice<>(shortcutInfoList)); + } + } catch (Exception e) { + callback.completeExceptionally(e); } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index 0c2b4c547dae..b4bcde726173 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -80,14 +80,14 @@ public interface DomainVerificationManagerInternal { * been preserved for migration purposes, but is otherwise ignored. Corresponds to * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS}. */ - int APPROVAL_LEVEL_LEGACY_ALWAYS = 1; + int APPROVAL_LEVEL_LEGACY_ALWAYS = 2; /** * The app has been chosen by the user through * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)}, * indicating an explicit choice to use this app to open an unverified domain. */ - int APPROVAL_LEVEL_SELECTION = 2; + int APPROVAL_LEVEL_SELECTION = 3; /** * The app is approved through the digital asset link statement being hosted at the domain @@ -95,7 +95,7 @@ public interface DomainVerificationManagerInternal { * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)} by * the domain verification agent on device. */ - int APPROVAL_LEVEL_VERIFIED = 3; + int APPROVAL_LEVEL_VERIFIED = 4; /** * The app has been installed as an instant app, which grants it total authority on the domains @@ -105,7 +105,7 @@ public interface DomainVerificationManagerInternal { * The user is still able to disable instant app link handling through * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, boolean)}. */ - int APPROVAL_LEVEL_INSTANT_APP = 4; + int APPROVAL_LEVEL_INSTANT_APP = 5; /** * Defines the possible values for {@link #approvalLevelForDomain(PackageSetting, Intent, int)} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 047e3b362b7a..ffea6a743a42 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -345,6 +345,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { */ private final Object mLock = new Object(); + /** List of {@link ScreenOnListener}s which do not belong to the default display. */ + private final SparseArray<ScreenOnListener> mScreenOnListeners = new SparseArray<>(); + Context mContext; IWindowManager mWindowManager; WindowManagerFuncs mWindowManagerFuncs; @@ -434,8 +437,25 @@ public class PhoneWindowManager implements WindowManagerPolicy { volatile boolean mBeganFromNonInteractive; volatile boolean mEndCallKeyHandled; volatile boolean mCameraGestureTriggeredDuringGoingToSleep; - volatile boolean mGoingToSleep; - volatile boolean mRequestedOrGoingToSleep; + + /** + * {@code true} if the device is entering a low-power state; {@code false otherwise}. + * + * <p>This differs from {@link #mRequestedOrSleepingDefaultDisplay} which tracks the power state + * of the {@link #mDefaultDisplay default display} versus the power state of the entire device. + */ + volatile boolean mDeviceGoingToSleep; + + /** + * {@code true} if the {@link #mDefaultDisplay default display} is entering or was requested to + * enter a low-power state; {@code false otherwise}. + * + * <p>This differs from {@link #mDeviceGoingToSleep} which tracks the power state of the entire + * device versus the power state of the {@link #mDefaultDisplay default display}. + */ + // TODO(b/178103325): Track sleep/requested sleep for every display. + volatile boolean mRequestedOrSleepingDefaultDisplay; + volatile boolean mRecentsVisible; volatile boolean mNavBarVirtualKeyHapticFeedbackEnabled = true; volatile boolean mPictureInPictureVisible; @@ -917,13 +937,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { case SHORT_PRESS_POWER_NOTHING: break; case SHORT_PRESS_POWER_GO_TO_SLEEP: - goToSleepFromPowerButton(eventTime, 0); + sleepDefaultDisplayFromPowerButton(eventTime, 0); break; case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP: - goToSleepFromPowerButton(eventTime, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); + sleepDefaultDisplayFromPowerButton(eventTime, + PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); break; case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME: - if (goToSleepFromPowerButton(eventTime, + if (sleepDefaultDisplayFromPowerButton(eventTime, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE)) { launchHomeFromHotKey(DEFAULT_DISPLAY); } @@ -951,11 +972,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } /** - * Sends the device to sleep as a result of a power button press. + * Sends the default display to sleep as a result of a power button press. * - * @return True if the was device was sent to sleep, false if sleep was suppressed. + * @return {@code true} if the device was sent to sleep, {@code false} if the device did not + * sleep. */ - private boolean goToSleepFromPowerButton(long eventTime, int flags) { + private boolean sleepDefaultDisplayFromPowerButton(long eventTime, int flags) { // Before we actually go to sleep, we check the last wakeup reason. // If the device very recently woke up from a gesture (like user lifting their device) // then ignore the sleep instruction. This is because users have developed @@ -975,12 +997,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, flags); + sleepDefaultDisplay(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, flags); return true; } - private void goToSleep(long eventTime, int reason, int flags) { - mRequestedOrGoingToSleep = true; + private void sleepDefaultDisplay(long eventTime, int reason, int flags) { + mRequestedOrSleepingDefaultDisplay = true; mPowerManager.goToSleep(eventTime, reason, flags); } @@ -1017,7 +1039,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { Settings.Global.THEATER_MODE_ON, 1); if (mGoToSleepOnButtonPressTheaterMode && interactive) { - goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0); + sleepDefaultDisplay(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, + 0); } } break; @@ -1126,7 +1149,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case SHORT_PRESS_SLEEP_GO_TO_SLEEP: case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME: Slog.i(TAG, "sleepRelease() calling goToSleep(GO_TO_SLEEP_REASON_SLEEP_BUTTON)"); - goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0); + sleepDefaultDisplay(eventTime, PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0); break; } } @@ -3511,7 +3534,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if ((mEndcallBehavior & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) { - goToSleep(event.getEventTime(), + sleepDefaultDisplay(event.getEventTime(), PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0); isWakeKey = false; } @@ -3538,10 +3561,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Any activity on the power button stops the accessibility shortcut result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately + final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState()); + final boolean interactiveAndOn = interactive && isDefaultDisplayOn; if (down) { - interceptPowerKeyDown(event, interactive); + interceptPowerKeyDown(event, interactiveAndOn); } else { - interceptPowerKeyUp(event, interactive, canceled); + interceptPowerKeyUp(event, interactiveAndOn, canceled); } break; } @@ -3746,7 +3771,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final MutableBoolean outLaunched = new MutableBoolean(false); final boolean gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive, outLaunched); - if (outLaunched.value && mRequestedOrGoingToSleep) { + if (outLaunched.value && mRequestedOrSleepingDefaultDisplay) { mCameraGestureTriggeredDuringGoingToSleep = true; } return gesturedServiceIntercepted; @@ -4088,8 +4113,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { pmSleepReason)) + ")"); } - mGoingToSleep = true; - mRequestedOrGoingToSleep = true; + mDeviceGoingToSleep = true; + mRequestedOrSleepingDefaultDisplay = true; if (mKeyguardDelegate != null) { mKeyguardDelegate.onStartedGoingToSleep(pmSleepReason); @@ -4108,8 +4133,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { } MetricsLogger.histogram(mContext, "screen_timeout", mLockScreenTimeout / 1000); - mGoingToSleep = false; - mRequestedOrGoingToSleep = false; + mDeviceGoingToSleep = false; + mRequestedOrSleepingDefaultDisplay = false; mDefaultDisplayPolicy.setAwake(false); // We must get this work done here because the power manager will drop @@ -4253,21 +4278,25 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Called on the DisplayManager's DisplayPowerController thread. @Override public void screenTurnedOff(int displayId) { - if (displayId != DEFAULT_DISPLAY) { - return; - } - - if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turned off..."); + if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off..."); - updateScreenOffSleepToken(true); - mDefaultDisplayPolicy.screenTurnedOff(); - synchronized (mLock) { - if (mKeyguardDelegate != null) { - mKeyguardDelegate.onScreenTurnedOff(); + if (displayId == DEFAULT_DISPLAY) { + updateScreenOffSleepToken(true); + mRequestedOrSleepingDefaultDisplay = false; + mDefaultDisplayPolicy.screenTurnedOff(); + synchronized (mLock) { + if (mKeyguardDelegate != null) { + mKeyguardDelegate.onScreenTurnedOff(); + } + } + mDefaultDisplayRotation.updateOrientationListener(); + reportScreenStateToVrManager(false); + if (mCameraGestureTriggeredDuringGoingToSleep) { + wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromPowerKey, + PowerManager.WAKE_REASON_CAMERA_LAUNCH, + "com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK"); } } - mDefaultDisplayRotation.updateOrientationListener(); - reportScreenStateToVrManager(false); } private long getKeyguardDrawnTimeout() { @@ -4280,27 +4309,28 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Called on the DisplayManager's DisplayPowerController thread. @Override public void screenTurningOn(int displayId, final ScreenOnListener screenOnListener) { - if (displayId != DEFAULT_DISPLAY) { - return; - } - - if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turning on..."); + if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turning on..."); - Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */); - updateScreenOffSleepToken(false); - mDefaultDisplayPolicy.screenTurnedOn(screenOnListener); + if (displayId == DEFAULT_DISPLAY) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", + 0 /* cookie */); + updateScreenOffSleepToken(false); + mDefaultDisplayPolicy.screenTurnedOn(screenOnListener); - synchronized (mLock) { - if (mKeyguardDelegate != null && mKeyguardDelegate.hasKeyguard()) { - mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT); - mHandler.sendEmptyMessageDelayed(MSG_KEYGUARD_DRAWN_TIMEOUT, - getKeyguardDrawnTimeout()); - mKeyguardDelegate.onScreenTurningOn(mKeyguardDrawnCallback); - } else { - if (DEBUG_WAKEUP) Slog.d(TAG, - "null mKeyguardDelegate: setting mKeyguardDrawComplete."); - mHandler.sendEmptyMessage(MSG_KEYGUARD_DRAWN_COMPLETE); + synchronized (mLock) { + if (mKeyguardDelegate != null && mKeyguardDelegate.hasKeyguard()) { + mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT); + mHandler.sendEmptyMessageDelayed(MSG_KEYGUARD_DRAWN_TIMEOUT, + getKeyguardDrawnTimeout()); + mKeyguardDelegate.onScreenTurningOn(mKeyguardDrawnCallback); + } else { + if (DEBUG_WAKEUP) Slog.d(TAG, + "null mKeyguardDelegate: setting mKeyguardDrawComplete."); + mHandler.sendEmptyMessage(MSG_KEYGUARD_DRAWN_COMPLETE); + } } + } else { + mScreenOnListeners.put(displayId, screenOnListener); } } @@ -4321,11 +4351,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void screenTurningOff(int displayId, ScreenOffListener screenOffListener) { + mWindowManagerFuncs.screenTurningOff(displayId, screenOffListener); if (displayId != DEFAULT_DISPLAY) { return; } - mWindowManagerFuncs.screenTurningOff(screenOffListener); + mRequestedOrSleepingDefaultDisplay = true; synchronized (mLock) { if (mKeyguardDelegate != null) { mKeyguardDelegate.onScreenTurningOff(); @@ -4380,6 +4411,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { listener.onScreenOn(); } + for (int i = mScreenOnListeners.size() - 1; i >= 0; i--) { + final ScreenOnListener screenOnListener = mScreenOnListeners.valueAt(i); + if (screenOnListener != null) { + screenOnListener.onScreenOn(); + } + } + mScreenOnListeners.clear(); + if (enableScreen) { try { mWindowManager.enableScreenIfNeeded(); @@ -4410,7 +4449,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public boolean okToAnimate() { - return mDefaultDisplayPolicy.isAwake() && !mGoingToSleep; + return mDefaultDisplayPolicy.isAwake() && !mDeviceGoingToSleep; } /** {@inheritDoc} */ @@ -4777,7 +4816,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerFuncs.lockDeviceNow(); break; case LID_BEHAVIOR_SLEEP: - goToSleep(SystemClock.uptimeMillis(), + sleepDefaultDisplay(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); break; diff --git a/services/core/java/com/android/server/policy/SplashScreenSurface.java b/services/core/java/com/android/server/policy/SplashScreenSurface.java index b9202c334fec..72933a0ad309 100644 --- a/services/core/java/com/android/server/policy/SplashScreenSurface.java +++ b/services/core/java/com/android/server/policy/SplashScreenSurface.java @@ -45,7 +45,7 @@ class SplashScreenSurface implements StartingSurface { } @Override - public void remove() { + public void remove(boolean animate) { if (DEBUG_SPLASH_SCREEN) Slog.v(TAG, "Removing splash screen window for " + mAppToken + ": " + this + " Callers=" + Debug.getCallers(4)); diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index b5a9acacec83..0735977be72a 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -238,8 +238,9 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * Removes the starting window surface. Do not hold the window manager lock when calling * this method! + * @param animate Whether need to play the default exit animation for starting window. */ - void remove(); + void remove(boolean animate); } /** @@ -303,9 +304,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * Notifies the window manager that screen is being turned off. * + * @param displayId the ID of the display which is turning off * @param listener callback to call when display can be turned off */ - void screenTurningOff(ScreenOffListener listener); + void screenTurningOff(int displayId, ScreenOffListener listener); /** * Convert the lid state to a human readable format. diff --git a/services/core/java/com/android/server/power/FaceDownDetector.java b/services/core/java/com/android/server/power/FaceDownDetector.java index fe9663aaabe5..676181dcfb67 100644 --- a/services/core/java/com/android/server/power/FaceDownDetector.java +++ b/services/core/java/com/android/server/power/FaceDownDetector.java @@ -291,8 +291,10 @@ public class FaceDownDetector implements SensorEventListener { * The user interacted with the screen while face down, indicated the phone is in use. * We log this event and temporarily make this detector inactive. */ - public void userActivity() { - mHandler.post(mUserActivityRunnable); + public void userActivity(int event) { + if (event != PowerManager.USER_ACTIVITY_EVENT_FACE_DOWN) { + mHandler.post(mUserActivityRunnable); + } } private void exitFaceDown(int resultType, long millisSinceFlip) { diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index f49e2f1631b9..7555a7f2920b 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -549,6 +549,7 @@ public class Notifier { if (!mUserActivityPending) { mUserActivityPending = true; Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY); + msg.arg1 = event; msg.setAsynchronous(true); mHandler.sendMessage(msg); } @@ -647,7 +648,7 @@ public class Notifier { mSuspendBlocker.release(); } - private void sendUserActivity() { + private void sendUserActivity(int event) { synchronized (mLock) { if (!mUserActivityPending) { return; @@ -657,7 +658,7 @@ public class Notifier { TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); tm.notifyUserActivity(); mPolicy.userActivity(); - mFaceDownDetector.userActivity(); + mFaceDownDetector.userActivity(event); } void postEnhancedDischargePredictionBroadcast(long delayMs) { @@ -833,7 +834,7 @@ public class Notifier { public void handleMessage(Message msg) { switch (msg.what) { case MSG_USER_ACTIVITY: - sendUserActivity(); + sendUserActivity(msg.arg1); break; case MSG_BROADCAST: sendNextBroadcast(); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 29adde37ab3b..d2a4cd604c01 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -180,8 +180,6 @@ 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: phone flipped to face down - private static final int DIRTY_FACE_DOWN = 1 << 15; // Dirty bit: display group power state has changed private static final int DIRTY_DISPLAY_GROUP_POWER_UPDATED = 1 << 16; @@ -1069,8 +1067,9 @@ public final class PowerManagerService extends SystemService final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout, -1L); millisUntilNormalTimeout = mLastUserActivityTime + screenOffTimeout - mClock.uptimeMillis(); - mDirty |= DIRTY_FACE_DOWN; - updatePowerStateLocked(); + userActivityInternal(mClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_FACE_DOWN, /* flags= */0, + Process.SYSTEM_UID); } } if (isFaceDown) { diff --git a/services/core/java/com/android/server/power/PreRebootLogger.java b/services/core/java/com/android/server/power/PreRebootLogger.java index 2e4b054b829c..c9e81ed7a796 100644 --- a/services/core/java/com/android/server/power/PreRebootLogger.java +++ b/services/core/java/com/android/server/power/PreRebootLogger.java @@ -19,7 +19,6 @@ package com.android.server.power; import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.content.Context; -import android.os.Binder; import android.os.Environment; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -147,7 +146,6 @@ final class PreRebootLogger { return; } - final long token = Binder.clearCallingIdentity(); try { final File dumpFile = new File(dumpDir, serviceName); final ParcelFileDescriptor fd = ParcelFileDescriptor.open(dumpFile, @@ -156,8 +154,6 @@ final class PreRebootLogger { binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class)); } catch (FileNotFoundException | RemoteException e) { Slog.e(TAG, String.format("Failed to dump %s service before reboot", serviceName), e); - } finally { - Binder.restoreCallingIdentity(token); } } } diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 3a08ddc6c405..fc62f5be801c 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -24,6 +24,7 @@ import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.PackageRollbackInfo.RestoreInfo; import android.content.rollback.RollbackInfo; +import android.os.SystemProperties; import android.os.UserHandle; import android.system.ErrnoException; import android.system.Os; @@ -227,6 +228,15 @@ class RollbackStore { packageSessionIds, extensionVersions); } + private static boolean isLinkPossible(File oldFile, File newFile) { + try { + return Os.stat(oldFile.getAbsolutePath()).st_dev + == Os.stat(newFile.getAbsolutePath()).st_dev; + } catch (ErrnoException ignore) { + return false; + } + } + /** * Creates a backup copy of an apk or apex for a package. * For packages containing splits, this method should be called for each @@ -239,16 +249,29 @@ class RollbackStore { targetDir.mkdirs(); File targetFile = new File(targetDir, sourceFile.getName()); - try { - // Create a hard link to avoid copy - // TODO(b/168562373) - // Linking between non-encrypted and encrypted is not supported and we have - // encrypted /data/rollback and non-encrypted /data/apex/active. For now this works - // because we happen to store encrypted files under /data/apex/active which is no - // longer the case when compressed apex rolls out. We have to handle this case in - // order not to fall back to copy. - Os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath()); - } catch (ErrnoException ignore) { + boolean fallbackToCopy = !isLinkPossible(sourceFile, targetFile); + if (!fallbackToCopy) { + try { + // Create a hard link to avoid copy + // TODO(b/168562373) + // Linking between non-encrypted and encrypted is not supported and we have + // encrypted /data/rollback and non-encrypted /data/apex/active. For now this works + // because we happen to store encrypted files under /data/apex/active which is no + // longer the case when compressed apex rolls out. We have to handle this case in + // order not to fall back to copy. + Os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath()); + } catch (ErrnoException e) { + boolean isRollbackTest = + SystemProperties.getBoolean("persist.rollback.is_test", false); + if (isRollbackTest) { + throw new IOException(e); + } else { + fallbackToCopy = true; + } + } + } + + if (fallbackToCopy) { // Fall back to copy if hardlink can't be created Files.copy(sourceFile.toPath(), targetFile.toPath()); } diff --git a/services/core/java/com/android/server/rotationresolver/RemoteRotationResolverService.java b/services/core/java/com/android/server/rotationresolver/RemoteRotationResolverService.java index cfb4c27820fa..189f47f7f1da 100644 --- a/services/core/java/com/android/server/rotationresolver/RemoteRotationResolverService.java +++ b/services/core/java/com/android/server/rotationresolver/RemoteRotationResolverService.java @@ -77,9 +77,7 @@ class RemoteRotationResolverService extends ServiceConnector.Impl<IRotationResol @GuardedBy("mLock") public void resolveRotationLocked(RotationRequest request) { - final RotationResolutionRequest remoteRequest = new RotationResolutionRequest( - request.mProposedRotation, request.mCurrentRotation, request.mPackageName, - request.mTimeoutMillis); + final RotationResolutionRequest remoteRequest = request.mRemoteRequest; post(service -> service.resolveRotation(request.mIRotationResolverCallback, remoteRequest)); // schedule a timeout. @@ -91,7 +89,7 @@ class RemoteRotationResolverService extends ServiceConnector.Impl<IRotationResol request.cancelInternal(); } } - }, request.mTimeoutMillis); + }, request.mRemoteRequest.getTimeoutMillis()); } @VisibleForTesting @@ -109,28 +107,18 @@ class RemoteRotationResolverService extends ServiceConnector.Impl<IRotationResol @GuardedBy("mLock") boolean mIsFulfilled; - private final long mTimeoutMillis; - @VisibleForTesting - final int mProposedRotation; - - private final int mCurrentRotation; - private final String mPackageName; + final RotationResolutionRequest mRemoteRequest; boolean mIsDispatched; private final Object mLock = new Object(); private final long mRequestStartTimeMillis; RotationRequest( - @NonNull RotationResolverInternal.RotationResolverCallbackInternal - callbackInternal, int proposedRotation, int currentRotation, - String packageName, long timeoutMillis, - @NonNull CancellationSignal cancellationSignal) { - mTimeoutMillis = timeoutMillis; + @NonNull RotationResolverInternal.RotationResolverCallbackInternal callbackInternal, + RotationResolutionRequest request, @NonNull CancellationSignal cancellationSignal) { mCallbackInternal = callbackInternal; - mProposedRotation = proposedRotation; - mCurrentRotation = currentRotation; - mPackageName = packageName; + mRemoteRequest = request; mIRotationResolverCallback = new RotationResolverCallback(this); mCancellationSignalInternal = cancellationSignal; mRequestStartTimeMillis = SystemClock.elapsedRealtime(); @@ -185,8 +173,8 @@ class RemoteRotationResolverService extends ServiceConnector.Impl<IRotationResol request.mCallbackInternal.onSuccess(rotation); final long timeToCalculate = SystemClock.elapsedRealtime() - request.mRequestStartTimeMillis; - logRotationStats(request.mProposedRotation, request.mCurrentRotation, rotation, - timeToCalculate); + logRotationStats(request.mRemoteRequest.getProposedRotation(), + request.mRemoteRequest.getCurrentRotation(), rotation, timeToCalculate); Slog.d(TAG, "onSuccess:" + rotation); Slog.d(TAG, "timeToCalculate:" + timeToCalculate); } @@ -204,8 +192,9 @@ class RemoteRotationResolverService extends ServiceConnector.Impl<IRotationResol request.mCallbackInternal.onFailure(error); final long timeToCalculate = SystemClock.elapsedRealtime() - request.mRequestStartTimeMillis; - logRotationStats(request.mProposedRotation, request.mCurrentRotation, - RESOLUTION_FAILURE, timeToCalculate); + logRotationStats(request.mRemoteRequest.getProposedRotation(), + request.mRemoteRequest.getCurrentRotation(), RESOLUTION_FAILURE, + timeToCalculate); Slog.d(TAG, "onFailure:" + error); Slog.d(TAG, "timeToCalculate:" + timeToCalculate); } diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java index 13f8d61f74f5..1dbe3e485938 100644 --- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java +++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java @@ -34,11 +34,11 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.CancellationSignal; import android.rotationresolver.RotationResolverInternal; +import android.service.rotationresolver.RotationResolutionRequest; import android.service.rotationresolver.RotationResolverService; import android.text.TextUtils; import android.util.IndentingPrintWriter; import android.util.Slog; -import android.view.Surface; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -95,14 +95,14 @@ final class RotationResolverManagerPerUserService extends @VisibleForTesting void resolveRotationLocked( @NonNull RotationResolverInternal.RotationResolverCallbackInternal callbackInternal, - @Surface.Rotation int proposedRotation, @Surface.Rotation int currentRotation, - String packageName, long timeoutMillis, + @NonNull RotationResolutionRequest request, @NonNull CancellationSignal cancellationSignalInternal) { if (!isServiceAvailableLocked()) { Slog.w(TAG, "Service is not available at this moment."); callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED); - logRotationStats(proposedRotation, currentRotation, RESOLUTION_UNAVAILABLE); + logRotationStats(request.getProposedRotation(), request.getCurrentRotation(), + RESOLUTION_UNAVAILABLE); return; } @@ -114,8 +114,7 @@ final class RotationResolverManagerPerUserService extends } mCurrentRequest = new RemoteRotationResolverService.RotationRequest(callbackInternal, - proposedRotation, currentRotation, packageName, timeoutMillis, - cancellationSignalInternal); + request, cancellationSignalInternal); cancellationSignalInternal.setOnCancelListener(() -> { synchronized (mLock) { diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java index e57d4ce9ec31..a7f3cdb8bcd2 100644 --- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java +++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java @@ -35,6 +35,7 @@ import android.os.ShellCallback; import android.os.UserHandle; import android.provider.DeviceConfig; import android.rotationresolver.RotationResolverInternal; +import android.service.rotationresolver.RotationResolutionRequest; import android.text.TextUtils; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -158,8 +159,9 @@ public class RotationResolverManagerService extends if (mIsServiceEnabled) { final RotationResolverManagerPerUserService service = getServiceForUserLocked( UserHandle.getCallingUserId()); - service.resolveRotationLocked(callbackInternal, proposedRotation, - currentRotation, /* packageName */ "", timeout, + final RotationResolutionRequest request = new RotationResolutionRequest("", + currentRotation, proposedRotation, true, timeout); + service.resolveRotationLocked(callbackInternal, request, cancellationSignalInternal); } else { Slog.w(TAG, "Rotation Resolver service is disabled."); diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java b/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java index e5088c023533..a0e04ee7a20d 100644 --- a/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java +++ b/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.os.CancellationSignal; import android.os.ShellCommand; import android.rotationresolver.RotationResolverInternal.RotationResolverCallbackInternal; +import android.service.rotationresolver.RotationResolutionRequest; import android.text.TextUtils; import android.view.Surface; @@ -107,8 +108,10 @@ final class RotationResolverShellCommand extends ShellCommand { } private int runResolveRotation() { - mService.resolveRotationLocked(sTestableRotationCallbackInternal, Surface.ROTATION_0, - Surface.ROTATION_0, "", 2000L, new CancellationSignal()); + final RotationResolutionRequest request = new RotationResolutionRequest("", + Surface.ROTATION_0, Surface.ROTATION_0, true, 2000L); + mService.resolveRotationLocked(sTestableRotationCallbackInternal, request, + new CancellationSignal()); return 0; } diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java index dbe73546d748..52c1467bd5d0 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java @@ -16,6 +16,8 @@ package com.android.server.speech; +import static android.Manifest.permission.MANAGE_SPEECH_RECOGNITION; + import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.ComponentName; @@ -24,6 +26,7 @@ import android.os.IBinder; import android.os.UserHandle; import android.speech.IRecognitionServiceManager; import android.speech.IRecognitionServiceManagerCallback; +import android.util.Slog; import com.android.internal.R; import com.android.server.infra.AbstractMasterSystemService; @@ -42,6 +45,8 @@ public final class SpeechRecognitionManagerService extends SpeechRecognitionManagerServiceImpl> { private static final String TAG = SpeechRecognitionManagerService.class.getSimpleName(); + private static final int MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS = 60_000; + public SpeechRecognitionManagerService(@NonNull Context context) { super(context, // TODO(b/176578753): think if we want to favor the particular service here. @@ -58,6 +63,16 @@ public final class SpeechRecognitionManagerService extends } @Override + protected void enforceCallingPermissionForManagement() { + getContext().enforceCallingPermission(MANAGE_SPEECH_RECOGNITION, TAG); + } + + @Override + protected int getMaximumTemporaryServiceDurationMs() { + return MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS; + } + + @Override protected SpeechRecognitionManagerServiceImpl newServiceLocked( @UserIdInt int resolvedUserId, boolean disabled) { return new SpeechRecognitionManagerServiceImpl(this, mLock, resolvedUserId, disabled); @@ -77,5 +92,21 @@ public final class SpeechRecognitionManagerService extends service.createSessionLocked(componentName, clientToken, onDevice, callback); } } + + @Override + public void setTemporaryComponent(ComponentName componentName) { + int userId = UserHandle.getCallingUserId(); + if (componentName == null) { + resetTemporaryService(userId); + Slog.i(TAG, "Reset temporary service for user " + userId); + return; + } + setTemporaryService( + userId, + componentName.flattenToString(), + MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS); + Slog.i(TAG, "SpeechRecognition temporarily set to " + componentName + " for " + + MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS + "ms"); + } } } diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index 2656a3d32555..769e049c8d0e 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -100,6 +100,9 @@ final class SpeechRecognitionManagerServiceImpl extends } if (serviceComponent == null) { + if (mMaster.debug) { + Slog.i(TAG, "Service component is undefined, responding with error."); + } tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT); return; } @@ -213,6 +216,10 @@ final class SpeechRecognitionManagerServiceImpl extends @Nullable private ComponentName getOnDeviceComponentNameLocked() { final String serviceName = getComponentNameLocked(); + if (mMaster.debug) { + Slog.i(TAG, "Resolved component name: " + serviceName); + } + if (serviceName == null) { if (mMaster.verbose) { Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name."); @@ -241,6 +248,11 @@ final class SpeechRecognitionManagerServiceImpl extends service.getServiceComponentName().equals(serviceComponent)) .findFirst(); if (existingService.isPresent()) { + + if (mMaster.debug) { + Slog.i(TAG, "Reused existing connection to " + serviceComponent); + } + return existingService.get(); } } @@ -253,6 +265,10 @@ final class SpeechRecognitionManagerServiceImpl extends mRemoteServicesByUid.computeIfAbsent(callingUid, key -> new HashSet<>()); valuesByCaller.add(service); + if (mMaster.debug) { + Slog.i(TAG, "Creating a new connection to " + serviceComponent); + } + return service; } } diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS index fc7fd220b26a..174ad3ad2e25 100644 --- a/services/core/java/com/android/server/stats/OWNERS +++ b/services/core/java/com/android/server/stats/OWNERS @@ -1,7 +1,10 @@ jeffreyhuang@google.com joeo@google.com +jtnguyen@google.com muhammadq@google.com +rslawik@google.com ruchirr@google.com +sharaienko@google.com singhtejinder@google.com tsaichristine@google.com yaochen@google.com diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 7ed7a592a972..8023fd42edfa 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -148,11 +148,13 @@ import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeRead import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; +import com.android.internal.os.KernelSingleProcessCpuThreadReader.ProcessCpuUsage; import com.android.internal.os.KernelWakelockReader; import com.android.internal.os.KernelWakelockStats; import com.android.internal.os.LooperStats; import com.android.internal.os.PowerProfile; import com.android.internal.os.ProcessCpuTracker; +import com.android.internal.os.SelectedProcessCpuThreadReader; import com.android.internal.os.StoragedUidIoStatsReader; import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; import com.android.internal.util.CollectionUtils; @@ -351,6 +353,8 @@ public class StatsPullAtomService extends SystemService { @GuardedBy("mDataBytesTransferLock") private final ArrayList<SubInfo> mHistoricalSubs = new ArrayList<>(); + private SelectedProcessCpuThreadReader mSurfaceFlingerProcessCpuThreadReader; + // Puller locks private final Object mDataBytesTransferLock = new Object(); private final Object mBluetoothBytesTransferLock = new Object(); @@ -753,6 +757,9 @@ public class StatsPullAtomService extends SystemService { } } } + + mSurfaceFlingerProcessCpuThreadReader = + new SelectedProcessCpuThreadReader("/system/bin/surfaceflinger"); } void registerEventListeners() { @@ -1479,7 +1486,7 @@ public class StatsPullAtomService extends SystemService { } for (int freqIndex = 0; freqIndex < timesMs.length; ++freqIndex) { int cluster = freqsClusters[freqIndex]; - long freq = freqs[freqIndex]; + int freq = (int) freqs[freqIndex]; long timeMs = timesMs[freqIndex]; pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, cluster, freq, timeMs)); } @@ -1678,6 +1685,18 @@ public class StatsPullAtomService extends SystemService { FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER_BINDER, times.binderThreadCpuTimesUs); + ProcessCpuUsage surfaceFlingerTimes = mSurfaceFlingerProcessCpuThreadReader.readAbsolute(); + if (surfaceFlingerTimes != null && surfaceFlingerTimes.threadCpuTimesMillis != null) { + long[] surfaceFlingerTimesUs = + new long[surfaceFlingerTimes.threadCpuTimesMillis.length]; + for (int i = 0; i < surfaceFlingerTimesUs.length; ++i) { + surfaceFlingerTimesUs[i] = surfaceFlingerTimes.threadCpuTimesMillis[i] * 1_000; + } + addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData, + FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SURFACE_FLINGER, + surfaceFlingerTimesUs); + } + return StatsManager.PULL_SUCCESS; } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 7523671fb3a7..970420a284d6 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -160,4 +160,10 @@ public interface StatusBarManagerInternal { * Handles a logging command from the WM shell command. */ void handleWindowManagerLoggingCommand(String[] args, ParcelFileDescriptor outFd); + + /** + * @see com.android.internal.statusbar.IStatusBar#setNavigationBarLumaSamplingEnabled(int, + * boolean) + */ + void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 546e420c1d59..302a23fb262c 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -588,6 +588,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } catch (RemoteException ex) { } } } + + @Override + public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) { + if (mBar != null) { + try { + mBar.setNavigationBarLumaSamplingEnabled(displayId, enable); + } catch (RemoteException ex) { } + } + } }; private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() { diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java index 3ae9d641e81c..ee78a4e4d1cf 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java @@ -28,8 +28,6 @@ import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneConfiguration; import android.os.UserHandle; -import com.android.internal.util.Preconditions; - import java.util.Objects; /** @@ -39,7 +37,7 @@ import java.util.Objects; */ public final class ConfigurationInternal { - private final boolean mAutoDetectionSupported; + private final boolean mTelephonyDetectionSupported; private final boolean mGeoDetectionSupported; private final boolean mAutoDetectionEnabled; private final @UserIdInt int mUserId; @@ -48,7 +46,7 @@ public final class ConfigurationInternal { private final boolean mGeoDetectionEnabled; private ConfigurationInternal(Builder builder) { - mAutoDetectionSupported = builder.mAutoDetectionSupported; + mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported; mGeoDetectionSupported = builder.mGeoDetectionSupported; mAutoDetectionEnabled = builder.mAutoDetectionEnabled; @@ -56,14 +54,16 @@ public final class ConfigurationInternal { mUserConfigAllowed = builder.mUserConfigAllowed; mLocationEnabled = builder.mLocationEnabled; mGeoDetectionEnabled = builder.mGeoDetectionEnabled; - // if mGeoDetectionSupported then mAutoDetectionSupported, i.e. mGeoDetectionSupported - // cannot be true if mAutoDetectionSupported == false - Preconditions.checkState(mAutoDetectionSupported || !mGeoDetectionSupported); } /** Returns true if the device supports any form of auto time zone detection. */ public boolean isAutoDetectionSupported() { - return mAutoDetectionSupported; + return mTelephonyDetectionSupported || mGeoDetectionSupported; + } + + /** Returns true if the device supports telephony time zone detection. */ + public boolean isTelephonyDetectionSupported() { + return mTelephonyDetectionSupported; } /** Returns true if the device supports geolocation time zone detection. */ @@ -78,9 +78,10 @@ public final class ConfigurationInternal { /** * Returns true if auto time zone detection behavior is actually enabled, which can be distinct - * from the raw setting value. */ + * from the raw setting value. + */ public boolean getAutoDetectionEnabledBehavior() { - return mAutoDetectionSupported && mAutoDetectionEnabled; + return isAutoDetectionSupported() && mAutoDetectionEnabled; } /** Returns the ID of the user this configuration is associated with. */ @@ -212,7 +213,7 @@ public final class ConfigurationInternal { ConfigurationInternal that = (ConfigurationInternal) o; return mUserId == that.mUserId && mUserConfigAllowed == that.mUserConfigAllowed - && mAutoDetectionSupported == that.mAutoDetectionSupported + && mTelephonyDetectionSupported == that.mTelephonyDetectionSupported && mGeoDetectionSupported == that.mGeoDetectionSupported && mAutoDetectionEnabled == that.mAutoDetectionEnabled && mLocationEnabled == that.mLocationEnabled @@ -221,7 +222,7 @@ public final class ConfigurationInternal { @Override public int hashCode() { - return Objects.hash(mUserId, mUserConfigAllowed, mAutoDetectionSupported, + return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported, mGeoDetectionSupported, mAutoDetectionEnabled, mLocationEnabled, mGeoDetectionEnabled); } @@ -231,7 +232,7 @@ public final class ConfigurationInternal { return "ConfigurationInternal{" + "mUserId=" + mUserId + ", mUserConfigAllowed=" + mUserConfigAllowed - + ", mAutoDetectionSupported=" + mAutoDetectionSupported + + ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported + ", mGeoDetectionSupported=" + mGeoDetectionSupported + ", mAutoDetectionEnabled=" + mAutoDetectionEnabled + ", mLocationEnabled=" + mLocationEnabled @@ -247,7 +248,7 @@ public final class ConfigurationInternal { private final @UserIdInt int mUserId; private boolean mUserConfigAllowed; - private boolean mAutoDetectionSupported; + private boolean mTelephonyDetectionSupported; private boolean mGeoDetectionSupported; private boolean mAutoDetectionEnabled; private boolean mLocationEnabled; @@ -266,7 +267,7 @@ public final class ConfigurationInternal { public Builder(ConfigurationInternal toCopy) { this.mUserId = toCopy.mUserId; this.mUserConfigAllowed = toCopy.mUserConfigAllowed; - this.mAutoDetectionSupported = toCopy.mAutoDetectionSupported; + this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported; this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported; this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled; this.mLocationEnabled = toCopy.mLocationEnabled; @@ -282,10 +283,10 @@ public final class ConfigurationInternal { } /** - * Sets whether any form of automatic time zone detection is supported on this device. + * Sets whether telephony time zone detection is supported on this device. */ - public Builder setAutoDetectionFeatureSupported(boolean supported) { - mAutoDetectionSupported = supported; + public Builder setTelephonyDetectionFeatureSupported(boolean supported) { + mTelephonyDetectionSupported = supported; return this; } diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java index e3caae9482d9..0e5f3bfbb4b1 100644 --- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java @@ -128,8 +128,8 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @Override public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { return new ConfigurationInternal.Builder(userId) - .setAutoDetectionFeatureSupported( - mServiceConfigAccessor.isAutoDetectionFeatureSupported()) + .setTelephonyDetectionFeatureSupported( + mServiceConfigAccessor.isTelephonyTimeZoneDetectionFeatureSupported()) .setGeoDetectionFeatureSupported( mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) .setAutoDetectionEnabled(isAutoDetectionEnabled()) diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java new file mode 100644 index 000000000000..c8c828f10ad3 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java @@ -0,0 +1,344 @@ +/* + * 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.timezonedetector; + +import static libcore.io.IoUtils.closeQuietly; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.timezonedetector.ManualTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; +import android.util.proto.ProtoOutputStream; + +import java.io.ByteArrayOutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * A class that provides time zone detector state information for metrics. + * + * <p> + * Regarding time zone ID ordinals: + * <p> + * We don't want to leak user location information by reporting time zone IDs. Instead, time zone + * IDs are consistently identified within a given instance of this class by a numeric ID. This + * allows comparison of IDs without revealing what those IDs are. + */ +public final class MetricsTimeZoneDetectorState { + + @IntDef(prefix = "DETECTION_MODE_", + value = { DETECTION_MODE_MANUAL, DETECTION_MODE_GEO, DETECTION_MODE_TELEPHONY}) + @interface DetectionMode {}; + + @DetectionMode + public static final int DETECTION_MODE_MANUAL = 0; + @DetectionMode + public static final int DETECTION_MODE_GEO = 1; + @DetectionMode + public static final int DETECTION_MODE_TELEPHONY = 2; + + @NonNull + private final ConfigurationInternal mConfigurationInternal; + @NonNull + private final int mDeviceTimeZoneIdOrdinal; + @Nullable + private final MetricsTimeZoneSuggestion mLatestManualSuggestion; + @Nullable + private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion; + @Nullable + private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion; + + private MetricsTimeZoneDetectorState( + @NonNull ConfigurationInternal configurationInternal, + int deviceTimeZoneIdOrdinal, + @Nullable MetricsTimeZoneSuggestion latestManualSuggestion, + @Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion, + @Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion) { + mConfigurationInternal = Objects.requireNonNull(configurationInternal); + mDeviceTimeZoneIdOrdinal = deviceTimeZoneIdOrdinal; + mLatestManualSuggestion = latestManualSuggestion; + mLatestTelephonySuggestion = latestTelephonySuggestion; + mLatestGeolocationSuggestion = latestGeolocationSuggestion; + } + + /** + * Creates {@link MetricsTimeZoneDetectorState} from the supplied parameters, using the {@link + * OrdinalGenerator} to generate time zone ID ordinals. + */ + public static MetricsTimeZoneDetectorState create( + @NonNull OrdinalGenerator<String> tzIdOrdinalGenerator, + @NonNull ConfigurationInternal configurationInternal, + @NonNull String deviceTimeZoneId, + @Nullable ManualTimeZoneSuggestion latestManualSuggestion, + @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion, + @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) { + + // TODO(b/172934905) Add logic to canonicalize the time zone IDs to Android's preferred IDs + // so that the ordinals will match even when the ID is not identical, just equivalent. + int deviceTimeZoneIdOrdinal = + tzIdOrdinalGenerator.ordinal(Objects.requireNonNull(deviceTimeZoneId)); + MetricsTimeZoneSuggestion latestObfuscatedManualSuggestion = + createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestManualSuggestion); + MetricsTimeZoneSuggestion latestObfuscatedTelephonySuggestion = + createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestTelephonySuggestion); + MetricsTimeZoneSuggestion latestObfuscatedGeolocationSuggestion = + createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestGeolocationSuggestion); + + return new MetricsTimeZoneDetectorState( + configurationInternal, deviceTimeZoneIdOrdinal, latestObfuscatedManualSuggestion, + latestObfuscatedTelephonySuggestion, latestObfuscatedGeolocationSuggestion); + } + + /** Returns true if the device supports telephony time zone detection. */ + public boolean isTelephonyDetectionSupported() { + return mConfigurationInternal.isTelephonyDetectionSupported(); + } + + /** Returns true if the device supports geolocation time zone detection. */ + public boolean isGeoDetectionSupported() { + return mConfigurationInternal.isGeoDetectionSupported(); + } + + /** Returns true if user's location can be used generally. */ + public boolean isUserLocationEnabled() { + return mConfigurationInternal.isLocationEnabled(); + } + + /** Returns the value of the geolocation time zone detection enabled setting. */ + public boolean getGeoDetectionEnabledSetting() { + return mConfigurationInternal.getGeoDetectionEnabledSetting(); + } + + /** Returns the value of the auto time zone detection enabled setting. */ + public boolean getAutoDetectionEnabledSetting() { + return mConfigurationInternal.getAutoDetectionEnabledSetting(); + } + + /** + * Returns the detection mode the device is currently using, which can be influenced by various + * things besides the user's setting. + */ + @DetectionMode + public int getDetectionMode() { + if (!mConfigurationInternal.getAutoDetectionEnabledBehavior()) { + return DETECTION_MODE_MANUAL; + } else if (mConfigurationInternal.getGeoDetectionEnabledBehavior()) { + return DETECTION_MODE_GEO; + } else { + return DETECTION_MODE_TELEPHONY; + } + } + + /** + * Returns the ordinal for the device's currently set time zone ID. + * See {@link MetricsTimeZoneDetectorState} for information about ordinals. + */ + @NonNull + public int getDeviceTimeZoneIdOrdinal() { + return mDeviceTimeZoneIdOrdinal; + } + + /** + * Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last manual + * suggestion received. + */ + @Nullable + public byte[] getLatestManualSuggestionProtoBytes() { + return suggestionProtoBytes(mLatestManualSuggestion); + } + + /** + * Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last, best + * telephony suggestion received. + */ + @Nullable + public byte[] getLatestTelephonySuggestionProtoBytes() { + return suggestionProtoBytes(mLatestTelephonySuggestion); + } + + /** + * Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last geolocation + * suggestion received. + */ + @Nullable + public byte[] getLatestGeolocationSuggestionProtoBytes() { + return suggestionProtoBytes(mLatestGeolocationSuggestion); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MetricsTimeZoneDetectorState that = (MetricsTimeZoneDetectorState) o; + return mDeviceTimeZoneIdOrdinal == that.mDeviceTimeZoneIdOrdinal + && mConfigurationInternal.equals(that.mConfigurationInternal) + && Objects.equals(mLatestManualSuggestion, that.mLatestManualSuggestion) + && Objects.equals(mLatestTelephonySuggestion, that.mLatestTelephonySuggestion) + && Objects.equals(mLatestGeolocationSuggestion, that.mLatestGeolocationSuggestion); + } + + @Override + public int hashCode() { + return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal, + mLatestManualSuggestion, mLatestTelephonySuggestion, mLatestGeolocationSuggestion); + } + + @Override + public String toString() { + return "MetricsTimeZoneDetectorState{" + + "mConfigurationInternal=" + mConfigurationInternal + + ", mDeviceTimeZoneIdOrdinal=" + mDeviceTimeZoneIdOrdinal + + ", mLatestManualSuggestion=" + mLatestManualSuggestion + + ", mLatestTelephonySuggestion=" + mLatestTelephonySuggestion + + ", mLatestGeolocationSuggestion=" + mLatestGeolocationSuggestion + + '}'; + } + + private static byte[] suggestionProtoBytes( + @Nullable MetricsTimeZoneSuggestion suggestion) { + if (suggestion == null) { + return null; + } + return suggestion.toBytes(); + } + + @Nullable + private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( + @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator, + @NonNull ManualTimeZoneSuggestion manualSuggestion) { + if (manualSuggestion == null) { + return null; + } + + int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(manualSuggestion.getZoneId()); + return MetricsTimeZoneSuggestion.createCertain( + new int[] { zoneIdOrdinal }); + } + + @Nullable + private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( + @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator, + @NonNull TelephonyTimeZoneSuggestion telephonySuggestion) { + if (telephonySuggestion == null) { + return null; + } + if (telephonySuggestion.getZoneId() == null) { + return MetricsTimeZoneSuggestion.createUncertain(); + } + int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(telephonySuggestion.getZoneId()); + return MetricsTimeZoneSuggestion.createCertain(new int[] { zoneIdOrdinal }); + } + + @Nullable + private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( + @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator, + @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion) { + if (geolocationSuggestion == null) { + return null; + } + + List<String> zoneIds = geolocationSuggestion.getZoneIds(); + if (zoneIds == null) { + return MetricsTimeZoneSuggestion.createUncertain(); + } + return MetricsTimeZoneSuggestion.createCertain(zoneIdOrdinalGenerator.ordinals(zoneIds)); + } + + /** + * A Java class that closely matches the android.app.time.MetricsTimeZoneSuggestion + * proto definition. + */ + private static final class MetricsTimeZoneSuggestion { + @Nullable + private final int[] mZoneIdOrdinals; + + MetricsTimeZoneSuggestion(@Nullable int[] zoneIdOrdinals) { + mZoneIdOrdinals = zoneIdOrdinals; + } + + @NonNull + static MetricsTimeZoneSuggestion createUncertain() { + return new MetricsTimeZoneSuggestion(null); + } + + public static MetricsTimeZoneSuggestion createCertain( + @NonNull int[] zoneIdOrdinals) { + return new MetricsTimeZoneSuggestion(zoneIdOrdinals); + } + + boolean isCertain() { + return mZoneIdOrdinals != null; + } + + @Nullable + int[] getZoneIdOrdinals() { + return mZoneIdOrdinals; + } + + byte[] toBytes() { + // We don't get access to the atoms.proto definition for nested proto fields, so we use + // an identically specified proto. + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ProtoOutputStream protoOutputStream = new ProtoOutputStream(byteArrayOutputStream); + int typeProtoValue = isCertain() + ? android.app.time.MetricsTimeZoneSuggestion.CERTAIN + : android.app.time.MetricsTimeZoneSuggestion.UNCERTAIN; + protoOutputStream.write(android.app.time.MetricsTimeZoneSuggestion.TYPE, + typeProtoValue); + if (isCertain()) { + for (int zoneIdOrdinal : getZoneIdOrdinals()) { + protoOutputStream.write( + android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_ORDINALS, + zoneIdOrdinal); + } + } + protoOutputStream.flush(); + closeQuietly(byteArrayOutputStream); + return byteArrayOutputStream.toByteArray(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MetricsTimeZoneSuggestion that = (MetricsTimeZoneSuggestion) o; + return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals); + } + + @Override + public int hashCode() { + return Arrays.hashCode(mZoneIdOrdinals); + } + + @Override + public String toString() { + return "MetricsTimeZoneSuggestion{" + + "mZoneIdOrdinals=" + Arrays.toString(mZoneIdOrdinals) + + '}'; + } + } +} diff --git a/services/core/java/com/android/server/timezonedetector/OrdinalGenerator.java b/services/core/java/com/android/server/timezonedetector/OrdinalGenerator.java new file mode 100644 index 000000000000..a448773c40d5 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/OrdinalGenerator.java @@ -0,0 +1,49 @@ +/* + * 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.timezonedetector; + +import android.util.ArraySet; + +import java.util.List; + +/** + * A helper class that turns a set of objects into ordinal values, i.e. each object is offered + * up via {@link #ordinal(Object)} or similar method, and a number will be returned. If the + * object has been seen before by the instance then the same number will be returned. Intended + * for situations where it is useful to know if values from some finite set are the same or + * different, but the value is either large or may reveal PII. This class relies on {@link + * Object#equals(Object)} and {@link Object#hashCode()}. + */ +class OrdinalGenerator<T> { + private final ArraySet<T> mKnownIds = new ArraySet<>(); + + int ordinal(T object) { + int ordinal = mKnownIds.indexOf(object); + if (ordinal < 0) { + ordinal = mKnownIds.size(); + mKnownIds.add(object); + } + return ordinal; + } + + int[] ordinals(List<T> objects) { + int[] ordinals = new int[objects.size()]; + for (int i = 0; i < ordinals.length; i++) { + ordinals[i] = ordinal(objects.get(i)); + } + return ordinals; + } +} diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java index 86c32f8d7b45..2452c8d3ddc9 100644 --- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java @@ -115,10 +115,15 @@ public final class ServiceConfigAccessor { /** Returns {@code true} if any form of automatic time zone detection is supported. */ public boolean isAutoDetectionFeatureSupported() { - return deviceHasTelephonyNetwork() || isGeoTimeZoneDetectionFeatureSupported(); + return isTelephonyTimeZoneDetectionFeatureSupported() + || isGeoTimeZoneDetectionFeatureSupported(); } - private boolean deviceHasTelephonyNetwork() { + /** + * Returns {@code true} if the telephony-based time zone detection feature is supported on the + * device. + */ + public boolean isTelephonyTimeZoneDetectionFeatureSupported() { // TODO b/150583524 Avoid the use of a deprecated API. return mContext.getSystemService(ConnectivityManager.class) .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java index cd220b164851..d429b8762a7c 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java @@ -50,4 +50,8 @@ public interface TimeZoneDetectorInternal extends Dumpable.Container { * available, and so on. This method may be implemented asynchronously. */ void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion); + + /** Generates a state snapshot for metrics. */ + @NonNull + MetricsTimeZoneDetectorState generateMetricsState(); } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java index 2d5dacdd6acc..4e78f5aa444c 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java @@ -86,8 +86,14 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) { Objects.requireNonNull(timeZoneSuggestion); - // All strategy calls must take place on the mHandler thread. + // This call can take place on the mHandler thread because there is no return value. mHandler.post( () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion)); } + + @Override + @NonNull + public MetricsTimeZoneDetectorState generateMetricsState() { + return mTimeZoneDetectorStrategy.generateMetricsState(); + } } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index 8266f121822e..e3f31b6aa326 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -66,9 +66,10 @@ import android.util.IndentingPrintWriter; * <p>Threading: * * <p>Suggestion calls with a void return type may be handed off to a separate thread and handled - * asynchronously. Synchronous calls like {@link #getCurrentUserConfigurationInternal()}, and debug - * calls like {@link #dump(IndentingPrintWriter, String[])}, may be called on a different thread - * concurrently with other operations. + * asynchronously. Synchronous calls like {@link #getCurrentUserConfigurationInternal()}, + * {@link #generateMetricsState()} and debug calls like {@link + * #dump(IndentingPrintWriter, String[])}, may be called on a different thread concurrently with + * other operations. * * @hide */ @@ -123,4 +124,8 @@ public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container { * suggestion. */ void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion); + + /** Generates a state snapshot for metrics. */ + @NonNull + MetricsTimeZoneDetectorState generateMetricsState(); } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index d163a0e22320..5d34dd7daffb 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -195,6 +195,13 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat private ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion = new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); + /** + * The latest manual suggestion received. + */ + @GuardedBy("this") + private ReferenceWithHistory<ManualTimeZoneSuggestion> mLatestManualSuggestion = + new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); + @GuardedBy("this") private final List<Dumpable> mDumpables = new ArrayList<>(); @@ -286,6 +293,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat if (currentUserConfig.getGeoDetectionEnabledBehavior()) { // Only store a geolocation suggestion if geolocation detection is currently enabled. + // See also clearGeolocationSuggestionIfNeeded(). mLatestGeoLocationSuggestion.set(suggestion); // Now perform auto time zone detection. The new suggestion may be used to modify the @@ -324,6 +332,12 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat return false; } + // Record the manual suggestion for debugging / metrics (but only if manual detection is + // currently enabled). + // Note: This is not used to set the device back to a previous manual suggestion if the user + // later disables automatic time zone detection. + mLatestManualSuggestion.set(suggestion); + setDeviceTimeZoneIfRequired(timeZoneId, cause); return true; } @@ -357,6 +371,28 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } } + @Override + @NonNull + public synchronized MetricsTimeZoneDetectorState generateMetricsState() { + int currentUserId = mEnvironment.getCurrentUserId(); + // Just capture one telephony suggestion: the one that would be used right now if telephony + // detection is in use. + QualifiedTelephonyTimeZoneSuggestion bestQualifiedTelephonySuggestion = + findBestTelephonySuggestion(); + TelephonyTimeZoneSuggestion telephonySuggestion = + bestQualifiedTelephonySuggestion == null + ? null : bestQualifiedTelephonySuggestion.suggestion; + // A new generator is created each time: we don't want / require consistency. + OrdinalGenerator<String> tzIdOrdinalGenerator = new OrdinalGenerator<>(); + return MetricsTimeZoneDetectorState.create( + tzIdOrdinalGenerator, + getConfigurationInternal(currentUserId), + mEnvironment.getDeviceTimeZone(), + getLatestManualSuggestion(), + telephonySuggestion, + getLatestGeolocationSuggestion()); + } + private static int scoreTelephonySuggestion(@NonNull TelephonyTimeZoneSuggestion suggestion) { int score; if (suggestion.getZoneId() == null) { @@ -619,6 +655,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat mTimeZoneChangesLog.dump(ipw); ipw.decreaseIndent(); // level 2 + ipw.println("Manual suggestion history:"); + ipw.increaseIndent(); // level 2 + mLatestManualSuggestion.dump(ipw); + ipw.decreaseIndent(); // level 2 + ipw.println("Geolocation suggestion history:"); ipw.increaseIndent(); // level 2 mLatestGeoLocationSuggestion.dump(ipw); @@ -639,6 +680,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * A method used to inspect strategy state during tests. Not intended for general use. */ @VisibleForTesting + public synchronized ManualTimeZoneSuggestion getLatestManualSuggestion() { + return mLatestManualSuggestion.get(); + } + + /** + * A method used to inspect strategy state during tests. Not intended for general use. + */ + @VisibleForTesting public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion( int slotIndex) { return mTelephonySuggestionsBySlotIndex.get(slotIndex); diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java index c0c9e6d58622..4fa920e5b7d2 100644 --- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java @@ -39,15 +39,15 @@ import java.util.Objects; */ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider { - private static final String TAG = LocationTimeZoneManagerService.TAG; - @NonNull private final LocationTimeZoneProviderProxy mProxy; BinderLocationTimeZoneProvider( + @NonNull ProviderMetricsLogger providerMetricsLogger, @NonNull ThreadingDomain threadingDomain, @NonNull String providerName, @NonNull LocationTimeZoneProviderProxy proxy) { - super(threadingDomain, providerName); + super(providerMetricsLogger, threadingDomain, providerName, + new ZoneInfoDbTimeZoneIdValidator()); mProxy = Objects.requireNonNull(proxy); } diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java index 0d1692a8781d..ca4a6408cfbb 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java @@ -45,6 +45,7 @@ import com.android.server.FgThread; import com.android.server.SystemService; import com.android.server.timezonedetector.ServiceConfigAccessor; import com.android.server.timezonedetector.TimeZoneDetectorInternal; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -297,7 +298,9 @@ public class LocationTimeZoneManagerService extends Binder { R.string.config_primaryLocationTimeZoneProviderPackageName ); } - return new BinderLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME, proxy); + ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(0); + return new BinderLocationTimeZoneProvider( + providerMetricsLogger, mThreadingDomain, PRIMARY_PROVIDER_NAME, proxy); } @NonNull @@ -317,7 +320,9 @@ public class LocationTimeZoneManagerService extends Binder { R.string.config_secondaryLocationTimeZoneProviderPackageName ); } - return new BinderLocationTimeZoneProvider(mThreadingDomain, SECONDARY_PROVIDER_NAME, proxy); + ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(1); + return new BinderLocationTimeZoneProvider( + providerMetricsLogger, mThreadingDomain, SECONDARY_PROVIDER_NAME, proxy); } /** Used for bug triage and in tests to simulate provider events. */ @@ -520,6 +525,12 @@ public class LocationTimeZoneManagerService extends Binder { } } + static void infoLog(String msg) { + if (Log.isLoggable(TAG, Log.INFO)) { + Slog.i(TAG, msg); + } + } + static void warnLog(String msg) { warnLog(msg, null); } diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java index ef2f357b8c3e..cc815dc61886 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java @@ -20,6 +20,7 @@ import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESU import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY; import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog; +import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog; import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; @@ -85,6 +86,26 @@ abstract class LocationTimeZoneProvider implements Dumpable { } /** + * Used by {@link LocationTimeZoneProvider} to check if time zone IDs are understood + * by the platform. + */ + interface TimeZoneIdValidator { + + /** + * Returns whether {@code timeZoneId} is supported by the platform or not. + */ + boolean isValid(@NonNull String timeZoneId); + } + + /** + * Listener interface used to log provider events for metrics. + */ + interface ProviderMetricsLogger { + /** Logs that a provider changed state. */ + void onProviderStateChanged(@ProviderStateEnum int stateEnum); + } + + /** * Information about the provider's current state. */ static class ProviderState { @@ -336,6 +357,7 @@ abstract class LocationTimeZoneProvider implements Dumpable { } } + @NonNull private final ProviderMetricsLogger mProviderMetricsLogger; @NonNull final ThreadingDomain mThreadingDomain; @NonNull final Object mSharedLock; @NonNull final String mProviderName; @@ -364,13 +386,19 @@ abstract class LocationTimeZoneProvider implements Dumpable { // Non-null and effectively final after initialize() is called. ProviderListener mProviderListener; + @NonNull private TimeZoneIdValidator mTimeZoneIdValidator; + /** Creates the instance. */ - LocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain, - @NonNull String providerName) { + LocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger, + @NonNull ThreadingDomain threadingDomain, + @NonNull String providerName, + @NonNull TimeZoneIdValidator timeZoneIdValidator) { mThreadingDomain = Objects.requireNonNull(threadingDomain); + mProviderMetricsLogger = Objects.requireNonNull(providerMetricsLogger); mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue(); mSharedLock = threadingDomain.getLockObject(); mProviderName = Objects.requireNonNull(providerName); + mTimeZoneIdValidator = Objects.requireNonNull(timeZoneIdValidator); } /** @@ -468,6 +496,7 @@ abstract class LocationTimeZoneProvider implements Dumpable { mCurrentState.set(newState); onSetCurrentState(newState); if (!Objects.equals(newState, oldState)) { + mProviderMetricsLogger.onProviderStateChanged(newState.stateEnum); if (mStateChangeRecording) { mRecordedStates.add(newState); } @@ -610,6 +639,25 @@ abstract class LocationTimeZoneProvider implements Dumpable { mThreadingDomain.assertCurrentThread(); Objects.requireNonNull(timeZoneProviderEvent); + // If the provider has made a suggestion with unknown time zone IDs it cannot be used to set + // the device's time zone. This logic prevents bad time zone IDs entering the time zone + // detection logic from third party code. + // + // An event containing an unknown time zone ID could occur if the provider is using a + // different TZDB version than the device. Provider developers are expected to take steps to + // avoid version skew problem, e.g. by ensuring atomic updates with the platform time zone + // rules, or providing IDs based on the device's TZDB version, so this is not considered a + // common case. + // + // Treating a suggestion containing unknown time zone IDs as "uncertain" in the primary + // enables immediate failover to a secondary provider, one that might provide valid IDs for + // the same location, which should provide better behavior than just ignoring the event. + if (hasInvalidTimeZones(timeZoneProviderEvent)) { + infoLog("event=" + timeZoneProviderEvent + " has unsupported time zones. " + + "Replacing it with uncertain event."); + timeZoneProviderEvent = TimeZoneProviderEvent.createUncertainEvent(); + } + synchronized (mSharedLock) { debugLog("handleTimeZoneProviderEvent: mProviderName=" + mProviderName + ", timeZoneProviderEvent=" + timeZoneProviderEvent); @@ -707,6 +755,20 @@ abstract class LocationTimeZoneProvider implements Dumpable { } } + private boolean hasInvalidTimeZones(@NonNull TimeZoneProviderEvent event) { + if (event.getSuggestion() == null) { + return false; + } + + for (String timeZone : event.getSuggestion().getTimeZoneIds()) { + if (!mTimeZoneIdValidator.isValid(timeZone)) { + return true; + } + } + + return false; + } + @GuardedBy("mSharedLock") private void assertIsStarted() { ProviderState currentState = mCurrentState.get(); diff --git a/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java b/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java new file mode 100644 index 000000000000..dfff6f2dd5ae --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.timezonedetector.location; + +import android.annotation.IntRange; + +import com.android.internal.util.FrameworkStatsLog; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; + +/** + * The real implementation of {@link ProviderMetricsLogger} which logs using + * {@link FrameworkStatsLog}. + */ +public class RealProviderMetricsLogger implements ProviderMetricsLogger { + + @IntRange(from = 0, to = 1) + private final int mProviderIndex; + + public RealProviderMetricsLogger(@IntRange(from = 0, to = 1) int providerIndex) { + mProviderIndex = providerIndex; + } + + @Override + public void onProviderStateChanged(@ProviderStateEnum int stateEnum) { + // TODO(b/172934905): Implement once the atom has landed. + } +} diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java new file mode 100644 index 000000000000..cab5ad25c54e --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.timezonedetector.location; + +import android.annotation.NonNull; + +import com.android.i18n.timezone.ZoneInfoDb; + +class ZoneInfoDbTimeZoneIdValidator implements + LocationTimeZoneProvider.TimeZoneIdValidator { + + @Override + public boolean isValid(@NonNull String timeZoneId) { + return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId); + } +} diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index eaba083c551c..e3dc70b41c0d 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -54,50 +54,6 @@ final class VibratorController { void onComplete(int vibratorId, long vibrationId); } - /** - * Initializes the native part of this controller, creating a global reference to given - * {@link OnVibrationCompleteListener} and returns a newly allocated native pointer. This - * wrapper is responsible for deleting this pointer by calling the method pointed - * by {@link #vibratorGetFinalizer()}. - * - * <p><b>Note:</b> Make sure the given implementation of {@link OnVibrationCompleteListener} - * do not hold any strong reference to the instance responsible for deleting the returned - * pointer, to avoid creating a cyclic GC root reference. - */ - static native long vibratorInit(int vibratorId, OnVibrationCompleteListener listener); - - /** - * Returns pointer to native function responsible for cleaning up the native pointer allocated - * and returned by {@link #vibratorInit(int, OnVibrationCompleteListener)}. - */ - static native long vibratorGetFinalizer(); - - static native boolean vibratorIsAvailable(long nativePtr); - - static native void vibratorOn(long nativePtr, long milliseconds, long vibrationId); - - static native void vibratorOff(long nativePtr); - - static native void vibratorSetAmplitude(long nativePtr, int amplitude); - - static native int[] vibratorGetSupportedEffects(long nativePtr); - - static native int[] vibratorGetSupportedPrimitives(long nativePtr); - - static native long vibratorPerformEffect( - long nativePtr, long effect, long strength, long vibrationId); - - static native long vibratorPerformComposedEffect( - long nativePtr, VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId); - - static native void vibratorSetExternalControl(long nativePtr, boolean enabled); - - static native long vibratorGetCapabilities(long nativePtr); - - static native void vibratorAlwaysOnEnable(long nativePtr, long id, long effect, long strength); - - static native void vibratorAlwaysOnDisable(long nativePtr, long id); - VibratorController(int vibratorId, OnVibrationCompleteListener listener) { this(vibratorId, listener, new NativeWrapper()); } @@ -109,7 +65,8 @@ final class VibratorController { mNativeWrapper.init(vibratorId, listener); mVibratorInfo = new VibratorInfo(vibratorId, nativeWrapper.getCapabilities(), - nativeWrapper.getSupportedEffects(), nativeWrapper.getSupportedPrimitives()); + nativeWrapper.getSupportedEffects(), nativeWrapper.getSupportedPrimitives(), + nativeWrapper.getResonantFrequency(), nativeWrapper.getQFactor()); } /** Register state listener for this vibrator. */ @@ -336,13 +293,47 @@ final class VibratorController { /** Wrapper around the static-native methods of {@link VibratorController} for tests. */ @VisibleForTesting public static class NativeWrapper { + /** + * Initializes the native part of this controller, creating a global reference to given + * {@link OnVibrationCompleteListener} and returns a newly allocated native pointer. This + * wrapper is responsible for deleting this pointer by calling the method pointed + * by {@link #getNativeFinalizer()}. + * + * <p><b>Note:</b> Make sure the given implementation of {@link OnVibrationCompleteListener} + * do not hold any strong reference to the instance responsible for deleting the returned + * pointer, to avoid creating a cyclic GC root reference. + */ + private static native long nativeInit(int vibratorId, OnVibrationCompleteListener listener); + + /** + * Returns pointer to native function responsible for cleaning up the native pointer + * allocated and returned by {@link #nativeInit(int, OnVibrationCompleteListener)}. + */ + private static native long getNativeFinalizer(); + private static native boolean isAvailable(long nativePtr); + private static native void on(long nativePtr, long milliseconds, long vibrationId); + private static native void off(long nativePtr); + private static native void setAmplitude(long nativePtr, int amplitude); + private static native int[] getSupportedEffects(long nativePtr); + private static native int[] getSupportedPrimitives(long nativePtr); + private static native long performEffect( + long nativePtr, long effect, long strength, long vibrationId); + private static native long performComposedEffect(long nativePtr, + VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId); + private static native void setExternalControl(long nativePtr, boolean enabled); + private static native long getCapabilities(long nativePtr); + private static native void alwaysOnEnable(long nativePtr, long id, long effect, + long strength); + private static native void alwaysOnDisable(long nativePtr, long id); + private static native float getResonantFrequency(long nativePtr); + private static native float getQFactor(long nativePtr); private long mNativePtr = 0; /** Initializes native controller and allocation registry to destroy native instances. */ public void init(int vibratorId, OnVibrationCompleteListener listener) { - mNativePtr = VibratorController.vibratorInit(vibratorId, listener); - long finalizerPtr = VibratorController.vibratorGetFinalizer(); + mNativePtr = nativeInit(vibratorId, listener); + long finalizerPtr = getNativeFinalizer(); if (finalizerPtr != 0) { NativeAllocationRegistry registry = @@ -354,65 +345,73 @@ final class VibratorController { /** Check if the vibrator is currently available. */ public boolean isAvailable() { - return VibratorController.vibratorIsAvailable(mNativePtr); + return isAvailable(mNativePtr); } /** Turns vibrator on for given time. */ public void on(long milliseconds, long vibrationId) { - VibratorController.vibratorOn(mNativePtr, milliseconds, vibrationId); + on(mNativePtr, milliseconds, vibrationId); } /** Turns vibrator off. */ public void off() { - VibratorController.vibratorOff(mNativePtr); + off(mNativePtr); } /** Sets the amplitude for the vibrator to run. */ public void setAmplitude(int amplitude) { - VibratorController.vibratorSetAmplitude(mNativePtr, amplitude); + setAmplitude(mNativePtr, amplitude); } /** Returns all predefined effects supported by the device vibrator. */ public int[] getSupportedEffects() { - return VibratorController.vibratorGetSupportedEffects(mNativePtr); + return getSupportedEffects(mNativePtr); } /** Returns all compose primitives supported by the device vibrator. */ public int[] getSupportedPrimitives() { - return VibratorController.vibratorGetSupportedPrimitives(mNativePtr); + return getSupportedPrimitives(mNativePtr); } /** Turns vibrator on to perform one of the supported effects. */ public long perform(long effect, long strength, long vibrationId) { - return VibratorController.vibratorPerformEffect( - mNativePtr, effect, strength, vibrationId); + return performEffect(mNativePtr, effect, strength, vibrationId); } /** Turns vibrator on to perform one of the supported composed effects. */ public long compose( VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) { - return VibratorController.vibratorPerformComposedEffect(mNativePtr, effect, - vibrationId); + return performComposedEffect(mNativePtr, effect, vibrationId); } /** Enabled the device vibrator to be controlled by another service. */ public void setExternalControl(boolean enabled) { - VibratorController.vibratorSetExternalControl(mNativePtr, enabled); + setExternalControl(mNativePtr, enabled); } /** Returns all capabilities of the device vibrator. */ public long getCapabilities() { - return VibratorController.vibratorGetCapabilities(mNativePtr); + return getCapabilities(mNativePtr); } /** Enable always-on vibration with given id and effect. */ public void alwaysOnEnable(long id, long effect, long strength) { - VibratorController.vibratorAlwaysOnEnable(mNativePtr, id, effect, strength); + alwaysOnEnable(mNativePtr, id, effect, strength); } /** Disable always-on vibration for given id. */ public void alwaysOnDisable(long id) { - VibratorController.vibratorAlwaysOnDisable(mNativePtr, id); + alwaysOnDisable(mNativePtr, id); + } + + /** Gets the vibrator's resonant frequency (F0) */ + public float getResonantFrequency() { + return getResonantFrequency(mNativePtr); + } + + /** Gets the vibrator's Q factor */ + public float getQFactor() { + return getQFactor(mNativePtr); } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index aeeabe21460c..db3d7ad0c398 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -137,6 +137,7 @@ import static com.android.server.wm.ActivityRecordProto.CLIENT_VISIBLE; import static com.android.server.wm.ActivityRecordProto.DEFER_HIDING_CLIENT; import static com.android.server.wm.ActivityRecordProto.FILLS_PARENT; import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK; +import static com.android.server.wm.ActivityRecordProto.IN_SIZE_COMPAT_MODE; import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING; import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START; import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN; @@ -2114,7 +2115,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } if (abort) { - surface.remove(); + surface.remove(false /* prepareAnimation */); } } else { ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Surface returned was null: %s", @@ -2128,7 +2129,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, TaskSnapshot snapshot) { - if (newTask || !processRunning || (taskSwitch && !activityCreated)) { + if ((newTask || !processRunning || (taskSwitch && !activityCreated)) + && !isActivityTypeHome()) { return STARTING_WINDOW_TYPE_SPLASH_SCREEN; } else if (taskSwitch && allowTaskSnapshot) { if (isSnapshotCompatible(snapshot)) { @@ -2179,7 +2181,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A + ActivityRecord.this + " state " + mTransferringSplashScreenState); if (isTransferringSplashScreen()) { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; - // TODO show default exit splash screen animation removeStartingWindow(); } } @@ -2196,6 +2197,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } private boolean transferSplashScreenIfNeeded() { + if (!mWmService.mStartingSurfaceController.DEBUG_ENABLE_SHELL_DRAWER) { + return false; + } if (!mHandleExitSplashScreen || mStartingSurface == null || mStartingWindow == null || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) { return false; @@ -2265,10 +2269,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // no matter what, remove the starting window. mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; - removeStartingWindow(); + removeStartingWindowAnimation(false /* prepareAnimation */); } void removeStartingWindow() { + removeStartingWindowAnimation(true /* prepareAnimation */); + } + + void removeStartingWindowAnimation(boolean prepareAnimation) { if (transferSplashScreenIfNeeded()) { return; } @@ -2313,7 +2321,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mWmService.mAnimationHandler.post(() -> { ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Removing startingView=%s", surface); try { - surface.remove(); + surface.remove(prepareAnimation); } catch (Exception e) { Slog.w(TAG_WM, "Exception when removing starting window", e); } @@ -6190,7 +6198,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Remove orphaned starting window. if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, "Found orphaned starting window " + this); mStartingWindowState = STARTING_WINDOW_REMOVED; - removeStartingWindow(); + removeStartingWindowAnimation(false /* prepareAnimation */); } if (isState(INITIALIZING) && !shouldBeVisible( true /* behindFullscreenActivity */, true /* ignoringKeyguard */)) { @@ -8255,6 +8263,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A proto.write(PROC_ID, app.getPid()); } proto.write(PIP_AUTO_ENTER_ENABLED, pictureInPictureArgs.isAutoEnterEnabled()); + proto.write(IN_SIZE_COMPAT_MODE, inSizeCompatMode()); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index d4eedf153c24..52d110c95e36 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -557,7 +557,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { boolean mSupportsPictureInPicture; boolean mSupportsMultiDisplay; boolean mForceResizableActivities; - boolean mSupportsNonResizableMultiWindow; + volatile boolean mSupportsNonResizableMultiWindow; final List<ActivityTaskManagerInternal.ScreenObserver> mScreenObservers = new ArrayList<>(); @@ -3432,6 +3432,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public boolean supportsNonResizableMultiWindow() { + return mSupportsNonResizableMultiWindow; + } + + @Override public boolean updateConfiguration(Configuration values) { mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()"); diff --git a/services/core/java/com/android/server/wm/BlurController.java b/services/core/java/com/android/server/wm/BlurController.java index 13295e8aca02..128d452c3018 100644 --- a/services/core/java/com/android/server/wm/BlurController.java +++ b/services/core/java/com/android/server/wm/BlurController.java @@ -22,12 +22,17 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.view.ICrossWindowBlurEnabledListener; +import com.android.internal.annotations.GuardedBy; + final class BlurController { private final RemoteCallbackList<ICrossWindowBlurEnabledListener> mBlurEnabledListeners = new RemoteCallbackList<>(); private final Object mLock = new Object(); + @GuardedBy("mLock") boolean mBlurEnabled; + @GuardedBy("mLock") + boolean mBlurForceDisabled; BlurController() { mBlurEnabled = CROSS_WINDOW_BLUR_SUPPORTED; @@ -46,19 +51,24 @@ final class BlurController { mBlurEnabledListeners.unregister(listener); } - private void updateBlurEnabled() { - // TODO: add other factors disabling blurs - final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED; + void setForceCrossWindowBlurDisabled(boolean disable) { synchronized (mLock) { - if (mBlurEnabled == newEnabled) { - return; - } - mBlurEnabled = newEnabled; - notifyBlurEnabledChanged(newEnabled); + mBlurForceDisabled = disable; + updateBlurEnabledLocked(); + } + + } + + private void updateBlurEnabledLocked() { + final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED && !mBlurForceDisabled; + if (mBlurEnabled == newEnabled) { + return; } + mBlurEnabled = newEnabled; + notifyBlurEnabledChangedLocked(newEnabled); } - private void notifyBlurEnabledChanged(boolean enabled) { + private void notifyBlurEnabledChangedLocked(boolean enabled) { int i = mBlurEnabledListeners.beginBroadcast(); while (i > 0) { i--; @@ -71,6 +81,4 @@ final class BlurController { } mBlurEnabledListeners.finishBroadcast(); } - - } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 1d45c6e1a371..426e63181ff1 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -131,7 +131,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE; import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT; import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD; -import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS; @@ -1811,11 +1810,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp w.mReportOrientationChanged = true; }, true /* traverseTopToBottom */); - if (rotateSeamlessly) { - mWmService.mH.sendNewMessageDelayed(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT, - this, SEAMLESS_ROTATION_TIMEOUT_DURATION); - } - for (int i = mWmService.mRotationWatchers.size() - 1; i >= 0; i--) { final WindowManagerService.RotationWatcher rotationWatcher = mWmService.mRotationWatchers.get(i); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 32152ec85493..01f0359fa548 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1410,9 +1410,9 @@ public class DisplayPolicy { boolean localClient) { final InsetsState state = mDisplayContent.getInsetsPolicy().getInsetsForWindowMetrics(attrs); - final boolean inSizeCompatMode = WindowState.inSizeCompatMode(attrs, windowToken); - outInsetsState.set(state, inSizeCompatMode || localClient); - if (inSizeCompatMode) { + final boolean hasCompatScale = WindowState.hasCompatScale(attrs, windowToken); + outInsetsState.set(state, hasCompatScale || localClient); + if (hasCompatScale) { final float compatScale = windowToken != null ? windowToken.getSizeCompatScale() : mDisplayContent.mCompatibleScreenScale; diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 63cb38a59349..5df1355f3460 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -567,7 +567,7 @@ public class DisplayRotation { } mDisplayContent.forAllWindows(w -> { if (w.mSeamlesslyRotated) { - w.finishSeamlessRotation(false /* timeout */); + w.cancelSeamlessRotation(); w.mSeamlesslyRotated = false; } }, true /* traverseTopToBottom */); @@ -670,24 +670,6 @@ public class DisplayRotation { } } - void onSeamlessRotationTimeout() { - final boolean[] isLayoutNeeded = { false }; - - mDisplayContent.forAllWindows(w -> { - if (!w.mSeamlesslyRotated) { - return; - } - isLayoutNeeded[0] = true; - w.setDisplayLayoutNeeded(); - w.finishSeamlessRotation(true /* timeout */); - markForSeamlessRotation(w, false /* seamlesslyRotated */); - }, true /* traverseTopToBottom */); - - if (isLayoutNeeded[0]) { - mService.mWindowPlacerLocked.performSurfacePlacement(); - } - } - /** * Returns the animation to run for a rotation transition based on the top fullscreen windows * {@link android.view.WindowManager.LayoutParams#rotationAnimation} and whether it is currently diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index 627af9149fe5..1120a074aa8c 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -182,8 +182,6 @@ class DragDropController { if (SHOW_LIGHT_TRANSACTIONS) { Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag"); } - - mDragState.notifyLocationLocked(touchX, touchY); } finally { if (surface != null) { surface.release(); diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 0b3c065e0e73..08d5e800a808 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -515,50 +515,6 @@ class DragState { mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply(); ProtoLog.i(WM_SHOW_TRANSACTIONS, "DRAG %s: pos=(%d,%d)", mSurfaceControl, (int) (x - mThumbOffsetX), (int) (y - mThumbOffsetY)); - - notifyLocationLocked(x, y); - } - - void notifyLocationLocked(float x, float y) { - // Tell the affected window - WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y); - if (touchedWin != null && !isWindowNotified(touchedWin)) { - // The drag point is over a window which was not notified about a drag start. - // Pretend it's over empty space. - touchedWin = null; - } - - try { - final int myPid = Process.myPid(); - - // have we dragged over a new window? - if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "sending DRAG_EXITED to " + mTargetWindow); - } - // force DRAG_EXITED_EVENT if appropriate - DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED, - 0, 0, 0, 0, null, null, null, null, null, false); - mTargetWindow.mClient.dispatchDragEvent(evt); - if (myPid != mTargetWindow.mSession.mPid) { - evt.recycle(); - } - } - if (touchedWin != null) { - if (false && DEBUG_DRAG) { - Slog.d(TAG_WM, "sending DRAG_LOCATION to " + touchedWin); - } - DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION, - x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, null, null, false); - touchedWin.mClient.dispatchDragEvent(evt); - if (myPid != touchedWin.mSession.mPid) { - evt.recycle(); - } - } - } catch (RemoteException e) { - Slog.w(TAG_WM, "can't send drag notification to windows"); - } - mTargetWindow = touchedWin; } /** diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java index a1e3ac71a3b6..aa7317022794 100644 --- a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java +++ b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java @@ -37,10 +37,15 @@ public class FixedRotationAnimationController extends FadeAnimationController { super(displayContent); final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); mStatusBar = displayPolicy.getStatusBar(); - // Do not animate movable navigation bar (e.g. non-gesture mode). + + final RecentsAnimationController controller = + displayContent.mWmService.getRecentsAnimationController(); + final boolean navBarControlledByRecents = + controller != null && controller.isNavigationBarAttachedToApp(); + // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation bar + // is currently controlled by recents animation. mNavigationBar = !displayPolicy.navigationBarCanMove() - ? displayPolicy.getNavigationBar() - : null; + && !navBarControlledByRecents ? displayPolicy.getNavigationBar() : null; } /** Applies show animation on the previously hidden window tokens. */ diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index c6c7fe083b16..35e54912b33e 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; @@ -59,6 +58,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 @@ -84,6 +84,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; @@ -93,7 +103,6 @@ class InsetsSourceProvider { private boolean mServerVisible; private boolean mSeamlessRotating; - private long mFinishSeamlessRotateFrameNumber = -1; private final boolean mControllable; @@ -150,7 +159,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); @@ -249,31 +257,16 @@ class InsetsSourceProvider { if (mControl != null) { final Point position = getWindowFrameSurfacePosition(); if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) { - if (!mWin.getWindowFrames().didFrameSizeChange()) { - updateLeashPosition(-1 /* frameNumber */); - } else if (mWin.mInRelayout) { - updateLeashPosition(mWin.getFrameNumber()); + if (mWin.getWindowFrames().didFrameSizeChange()) { + mWin.applyWithNextDraw(mSetLeashPositionConsumer); } else { - mWin.mPendingPositionChanged = this; + mSetLeashPositionConsumer.accept(mWin.getPendingTransaction()); } mStateController.notifyControlChanged(mControlTarget); } } } - 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(); @@ -281,14 +274,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) */ @@ -342,15 +327,6 @@ class InsetsSourceProvider { mIsLeashReadyForDispatching = false; final SurfaceControl leash = mAdapter.mCapturedLeash; - final long frameNumber = mFinishSeamlessRotateFrameNumber; - mFinishSeamlessRotateFrameNumber = -1; - if (mWin.mHasSurface && leash != null) { - // We just finished the seamless rotation. We don't want to change the position or the - // window crop of the surface controls (including the leash) until the client finishes - // drawing the new frame of the new orientation. Although we cannot defer the reparent - // operation, it is fine, because reparent won't cause any visual effect. - deferTransactionUntil(t, leash, frameNumber); - } mControlTarget = target; updateVisibility(); mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition); @@ -359,19 +335,14 @@ class InsetsSourceProvider { } void startSeamlessRotation() { - if (!mSeamlessRotating) { - mSeamlessRotating = true; - - // This will revoke the leash and clear the control target. - mWin.cancelAnimation(); - } + if (!mSeamlessRotating) { + mSeamlessRotating = true; + mWin.cancelAnimation(); + } } - void finishSeamlessRotation(boolean timeout) { - if (mSeamlessRotating) { - mSeamlessRotating = false; - mFinishSeamlessRotateFrameNumber = timeout ? -1 : mWin.getFrameNumber(); - } + void finishSeamlessRotation() { + mSeamlessRotating = false; } boolean updateClientVisibility(InsetsControlTarget caller) { @@ -529,7 +500,6 @@ class InsetsSourceProvider { proto.write(CLIENT_VISIBLE, mClientVisible); proto.write(SERVER_VISIBLE, mServerVisible); proto.write(SEAMLESS_ROTATING, mSeamlessRotating); - proto.write(FINISH_SEAMLESS_ROTATE_FRAME_NUMBER, mFinishSeamlessRotateFrameNumber); proto.write(CONTROLLABLE, mControllable); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index e02cce4b946a..914e45641b45 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -17,7 +17,9 @@ package com.android.server.wm; 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.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; @@ -104,7 +106,8 @@ public class RecentsAnimationController implements DeathRecipient { public @interface ReorderMode {} private final WindowManagerService mService; - private final StatusBarManagerInternal mStatusBar; + @VisibleForTesting + final StatusBarManagerInternal mStatusBar; private IRecentsAnimationRunner mRunner; private final RecentsAnimationCallbacks mCallbacks; private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>(); @@ -149,6 +152,7 @@ public class RecentsAnimationController implements DeathRecipient { @VisibleForTesting boolean mShouldAttachNavBarToAppDuringTransition; + private boolean mNavigationBarAttachedToApp; /** * Animates the screenshot of task that used to be controlled by RecentsAnimation. @@ -369,7 +373,17 @@ public class RecentsAnimationController implements DeathRecipient { } @Override - public void detachNavigationBarFromApp() {} + public void detachNavigationBarFromApp(boolean moveHomeToTop) { + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.getWindowManagerLock()) { + restoreNavigationBarFromApp(moveHomeToTop); + mService.mWindowPlacerLocked.requestTraversal(); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } }; /** @@ -440,9 +454,7 @@ public class RecentsAnimationController implements DeathRecipient { return; } - if (mShouldAttachNavBarToAppDuringTransition) { - attachNavBarToApp(); - } + attachNavigationBarToApp(); // Adjust the wallpaper visibility for the showing target activity ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, @@ -577,32 +589,52 @@ public class RecentsAnimationController implements DeathRecipient { } } + boolean isNavigationBarAttachedToApp() { + return mNavigationBarAttachedToApp; + } + @VisibleForTesting - WindowToken getNavigationBarWindowToken() { - WindowState navBar = mDisplayContent.getDisplayPolicy().getNavigationBar(); - if (navBar != null) { - return navBar.mToken; - } - return null; + WindowState getNavigationBarWindow() { + return mDisplayContent.getDisplayPolicy().getNavigationBar(); } - private void attachNavBarToApp() { + private void attachNavigationBarToApp() { + if (!mShouldAttachNavBarToAppDuringTransition + // Skip the case where the nav bar is controlled by fixed rotation. + || mDisplayContent.getFixedRotationAnimationController() != null) { + return; + } ActivityRecord topActivity = null; + boolean shouldTranslateNavBar = false; + final boolean isDisplayLandscape = + mDisplayContent.getConfiguration().orientation == ORIENTATION_LANDSCAPE; for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { final TaskAnimationAdapter adapter = mPendingAnimations.get(i); final Task task = adapter.mTask; - if (!task.isHomeOrRecentsRootTask()) { - topActivity = task.getTopVisibleActivity(); - break; + final boolean isSplitScreenSecondary = + task.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + if (task.isHomeOrRecentsRootTask() + // TODO(b/178449492): Will need to update for the new split screen mode once + // it's ready. + // Skip if the task is the secondary split screen and in landscape. + || (isSplitScreenSecondary && isDisplayLandscape)) { + continue; } + shouldTranslateNavBar = isSplitScreenSecondary; + topActivity = task.getTopVisibleActivity(); + break; } - final WindowToken navToken = getNavigationBarWindowToken(); - if (topActivity == null || navToken == null) { + + final WindowState navWindow = getNavigationBarWindow(); + if (topActivity == null || navWindow == null || navWindow.mToken == null) { return; } - - final SurfaceControl.Transaction t = navToken.getPendingTransaction(); - final SurfaceControl navSurfaceControl = navToken.getSurfaceControl(); + mNavigationBarAttachedToApp = true; + final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction(); + final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl(); + if (shouldTranslateNavBar) { + navWindow.setSurfaceTranslationY(-topActivity.getBounds().top); + } t.reparent(navSurfaceControl, topActivity.getSurfaceControl()); t.show(navSurfaceControl); @@ -613,17 +645,33 @@ public class RecentsAnimationController implements DeathRecipient { // Place the nav bar on top of anything else in the top activity. t.setLayer(navSurfaceControl, Integer.MAX_VALUE); } + if (mStatusBar != null) { + mStatusBar.setNavigationBarLumaSamplingEnabled(mDisplayId, false); + } } - private void restoreNavBarFromApp(boolean animate) { - // Reparent the SurfaceControl of nav bar token back. - final WindowToken navToken = getNavigationBarWindowToken(); - final SurfaceControl.Transaction t = mDisplayContent.getPendingTransaction(); - if (navToken != null) { - final WindowContainer parent = navToken.getParent(); - t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); + private void restoreNavigationBarFromApp(boolean animate) { + if (!mNavigationBarAttachedToApp) { + return; + } + if (mStatusBar != null) { + mStatusBar.setNavigationBarLumaSamplingEnabled(mDisplayId, true); } + final WindowState navWindow = getNavigationBarWindow(); + if (navWindow == null) { + return; + } + navWindow.setSurfaceTranslationY(0); + + if (navWindow.mToken == null) { + return; + } + final SurfaceControl.Transaction t = mDisplayContent.getPendingTransaction(); + final WindowContainer parent = navWindow.mToken.getParent(); + // Reparent the SurfaceControl of nav bar token back. + t.reparent(navWindow.mToken.getSurfaceControl(), parent.getSurfaceControl()); + if (animate) { // Run fade-in animation to show navigation bar back to bottom of the display. final NavBarFadeAnimationController controller = @@ -852,9 +900,7 @@ public class RecentsAnimationController implements DeathRecipient { removeWallpaperAnimation(wallpaperAdapter); } - if (mShouldAttachNavBarToAppDuringTransition) { - restoreNavBarFromApp(reorderMode == REORDER_MOVE_TO_TOP); - } + restoreNavigationBarFromApp(reorderMode == REORDER_MOVE_TO_TOP); // Clear any pending failsafe runnables mService.mH.removeCallbacks(mFailsafeRunnable); diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 42cb96f65738..6fc585e473a9 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -104,11 +104,11 @@ class RemoteAnimationController implements DeathRecipient { */ void goodToGo(@WindowManager.TransitionOldType int transit) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo()"); - if (mPendingAnimations.isEmpty() || mCanceled) { + if (mCanceled) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, - "goodToGo(): Animation finished already, canceled=%s mPendingAnimations=%d", - mCanceled, mPendingAnimations.size()); + "goodToGo(): Animation canceled already"); onAnimationFinished(); + invokeAnimationCancelled(); return; } @@ -120,8 +120,11 @@ class RemoteAnimationController implements DeathRecipient { // Create the app targets final RemoteAnimationTarget[] appTargets = createAppAnimations(); if (appTargets.length == 0) { - ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo(): No apps to animate"); + ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, + "goodToGo(): No apps to animate, mPendingAnimations=%d", + mPendingAnimations.size()); onAnimationFinished(); + invokeAnimationCancelled(); return; } diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java index 3d305e4d852d..1e8b8a5bb576 100644 --- a/services/core/java/com/android/server/wm/SeamlessRotator.java +++ b/services/core/java/com/android/server/wm/SeamlessRotator.java @@ -35,9 +35,6 @@ import java.io.StringWriter; * Helper class for seamless rotation. * * Works by transforming the {@link WindowState} back into the old display rotation. - * - * Uses {@link Transaction#deferTransactionUntil(SurfaceControl, IBinder, long)} instead of - * latching on the buffer size to allow for seamless 180 degree rotations. */ public class SeamlessRotator { @@ -103,22 +100,7 @@ public class SeamlessRotator { * Removing the transform and the result of the {@link WindowState} layout are both tied to the * {@link WindowState} next frame, such that they apply at the same time the client draws the * window in the new orientation. - * - * In the case of a rotation timeout, we want to remove the transform immediately and not defer - * it. */ - public void finish(WindowState win, boolean timeout) { - final Transaction t = win.getPendingTransaction(); - finish(t, win); - if (win.mWinAnimator.mSurfaceController != null && !timeout) { - t.deferTransactionUntil(win.mSurfaceControl, - win.getClientViewRootSurface(), win.getFrameNumber()); - t.deferTransactionUntil(win.mWinAnimator.mSurfaceController.mSurfaceControl, - win.getClientViewRootSurface(), win.getFrameNumber()); - } - } - - /** Removes the transform and restore to the original last position. */ void finish(Transaction t, WindowContainer win) { mTransform.reset(); t.setMatrix(win.mSurfaceControl, mTransform, mFloat9); diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index ef4a40f4837c..6c4613526e88 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -40,9 +40,8 @@ public class StartingSurfaceController { private static final String TAG = TAG_WITH_CLASS_NAME ? StartingSurfaceController.class.getSimpleName() : TAG_WM; /** Set to {@code true} to enable shell starting surface drawer. */ - private static final boolean DEBUG_ENABLE_SHELL_DRAWER = - SystemProperties.getBoolean("persist.debug.shell_starting_surface", false); - + static final boolean DEBUG_ENABLE_SHELL_DRAWER = + SystemProperties.getBoolean("persist.debug.shell_starting_surface", true); private final WindowManagerService mService; public StartingSurfaceController(WindowManagerService wm) { @@ -139,8 +138,9 @@ public class StartingSurfaceController { } @Override - public void remove() { - mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask); + public void remove(boolean animate) { + mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, + animate); } } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d992a4591a22..a4b4726fe070 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4135,6 +4135,8 @@ class Task extends WindowContainer<WindowContainer> { final StartingWindowInfo info = new StartingWindowInfo(); info.taskInfo = getTaskInfo(); + info.isKeyguardOccluded = + mAtmService.mKeyguardController.isDisplayOccluded(DEFAULT_DISPLAY); final ActivityRecord topActivity = getTopMostActivity(); if (topActivity != null) { info.startingWindowTypeParameter = @@ -5245,17 +5247,29 @@ class Task extends WindowContainer<WindowContainer> { if (mForceHiddenFlags == newFlags) { return false; } + final boolean wasHidden = isForceHidden(); + final boolean wasVisible = isVisible(); mForceHiddenFlags = newFlags; - if (wasHidden != isForceHidden() && isTopActivityFocusable()) { - // The change in force-hidden state will change visibility without triggering a root - // task order change, so we should reset the preferred top focusable root task to ensure - // it's not used if a new activity is started from this task. - getDisplayArea().resetPreferredTopFocusableRootTaskIfNeeded(this); + final boolean nowHidden = isForceHidden(); + if (wasHidden != nowHidden) { + final String reason = "setForceHidden"; + if (wasVisible && nowHidden) { + // Move this visible task to back when the task is forced hidden + moveToBack(reason, null); + } else if (isAlwaysOnTop()) { + // Move this always-on-top task to front when no longer hidden + moveToFront(reason); + } } return true; } + @Override + public boolean isAlwaysOnTop() { + return !isForceHidden() && super.isAlwaysOnTop(); + } + /** * Returns whether this task is currently forced to be hidden for any reason. */ @@ -7482,17 +7496,22 @@ class Task extends WindowContainer<WindowContainer> { } public void setAlwaysOnTop(boolean alwaysOnTop) { - if (isAlwaysOnTop() == alwaysOnTop) { + // {@link #isAwaysonTop} overrides the original behavior which also evaluates if this + // task is force hidden, so super.isAlwaysOnTop() is used here to see whether the + // alwaysOnTop attributes should be updated. + if (super.isAlwaysOnTop() == alwaysOnTop) { return; } super.setAlwaysOnTop(alwaysOnTop); - final TaskDisplayArea taskDisplayArea = getDisplayArea(); // positionChildAtTop() must be called even when always on top gets turned off because we // need to make sure that the root task is moved from among always on top windows to // below other always on top windows. Since the position the root task should be inserted // into is calculated properly in {@link DisplayContent#getTopInsertPosition()} in both // cases, we can just request that the root task is put at top here. - taskDisplayArea.positionChildAt(POSITION_TOP, this, false /* includingParents */); + // Don't bother moving task to top if this task is force hidden and invisible to user. + if (!isForceHidden()) { + getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */); + } } void dismissPip() { diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index ed92fd08bef5..91aa48effe84 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -393,7 +393,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { final boolean moveToBottom = position <= 0; final int oldPosition = mChildren.indexOf(child); - if (child.getWindowConfiguration().isAlwaysOnTop() && !moveToTop) { + if (child.isAlwaysOnTop() && !moveToTop) { // This root task is always-on-top, override the default behavior. Slog.w(TAG_WM, "Ignoring move of always-on-top root task=" + this + " to bottom"); @@ -974,14 +974,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { onRootTaskOrderChanged(rootTask); } - /** Reset the mPreferredTopFocusableRootTask if it is or below the given task. */ - void resetPreferredTopFocusableRootTaskIfNeeded(Task task) { - if (mPreferredTopFocusableRootTask != null - && mPreferredTopFocusableRootTask.compareTo(task) <= 0) { - mPreferredTopFocusableRootTask = null; - } - } - /** * Moves/reparents `task` to the back of whatever container the root home task is in. This is * for when we just want to move a task to "the back" vs. a specific place. The primary use-case diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index fc6db61bdbcd..5d22f8fde057 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -32,6 +32,7 @@ import android.app.WindowConfiguration; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ParceledListSlice; +import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; @@ -131,10 +132,28 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { }); } - void removeStartingWindow(Task task) { + void removeStartingWindow(Task task, boolean prepareAnimation) { mDeferTaskOrgCallbacksConsumer.accept(() -> { + SurfaceControl firstWindowLeash = null; + Rect mainFrame = null; + // TODO enable shift up animation once we fix flicker test +// final boolean playShiftUpAnimation = !task.inMultiWindowMode(); +// if (prepareAnimation && playShiftUpAnimation) { +// final ActivityRecord topActivity = task.topActivityWithStartingWindow(); +// if (topActivity != null) { +// final WindowState mainWindow = +// topActivity.findMainWindow(false/* includeStartingApp */); +// if (mainWindow != null) { + // TODO create proper leash instead of the copied SC +// firstWindowLeash = new SurfaceControl(mainWindow.getSurfaceControl(), +// "TaskOrganizerController.removeStartingWindow"); +// mainFrame = mainWindow.getRelativeFrame(); +// } +// } +// } try { - mTaskOrganizer.removeStartingWindow(task.mTaskId); + mTaskOrganizer.removeStartingWindow(task.mTaskId, firstWindowLeash, mainFrame, + prepareAnimation); } catch (RemoteException e) { Slog.e(TAG, "Exception sending onStartTaskFinished callback", e); } @@ -249,8 +268,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mOrganizer.addStartingWindow(t, appToken, launchTheme); } - void removeStartingWindow(Task t) { - mOrganizer.removeStartingWindow(t); + void removeStartingWindow(Task t, boolean prepareAnimation) { + mOrganizer.removeStartingWindow(t, prepareAnimation); } void copySplashScreenView(Task t) { @@ -495,14 +514,14 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return true; } - void removeStartingWindow(Task task) { + void removeStartingWindow(Task task, boolean prepareAnimation) { final Task rootTask = task.getRootTask(); if (rootTask == null || rootTask.mTaskOrganizer == null) { return; } final TaskOrganizerState state = mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder()); - state.removeStartingWindow(task); + state.removeStartingWindow(task, prepareAnimation); } boolean copySplashScreenView(Task task) { @@ -865,6 +884,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mPendingTaskEvents.remove(pending); } mPendingTaskEvents.add(pending); + mService.mWindowManager.mWindowPlacerLocked.requestTraversal(); return true; } diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java index 9d35c25fc546..525420e045b5 100644 --- a/services/core/java/com/android/server/wm/TaskPositioningController.java +++ b/services/core/java/com/android/server/wm/TaskPositioningController.java @@ -188,7 +188,8 @@ class TaskPositioningController { transferFocusFromWin = displayContent.mCurrentFocus; } if (!mInputManager.transferTouchFocus( - transferFocusFromWin.mInputChannel, mTaskPositioner.mClientChannel)) { + transferFocusFromWin.mInputChannel, mTaskPositioner.mClientChannel, + false /* isDragDrop */)) { Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus"); cleanUpTaskPositioner(); return false; diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index b810de99ee10..8915eba3d509 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -37,6 +37,7 @@ import android.os.Handler; import android.util.ArraySet; import android.util.Pair; import android.util.Slog; +import android.view.Display; import android.view.InsetsState; import android.view.SurfaceControl; import android.view.ThreadedRenderer; @@ -624,7 +625,7 @@ class TaskSnapshotController { /** * Called when screen is being turned off. */ - void screenTurningOff(ScreenOffListener listener) { + void screenTurningOff(int displayId, ScreenOffListener listener) { if (shouldDisableSnapshots()) { listener.onScreenOff(); return; @@ -635,7 +636,7 @@ class TaskSnapshotController { try { synchronized (mService.mGlobalLock) { mTmpTasks.clear(); - mService.mRoot.forAllTasks(task -> { + mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> { // Since RecentsAnimation will handle task snapshot while switching apps // with the best capture timing (e.g. IME window capture), No need // additional task capture while task is controlled by RecentsAnimation. @@ -645,7 +646,7 @@ class TaskSnapshotController { }); // Allow taking snapshot of home when turning screen off to reduce the delay of // waking from secure lock to home. - final boolean allowSnapshotHome = + final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY && mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId); snapshotTasks(mTmpTasks, allowSnapshotHome); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index 07610ab6d546..79a6bd7dcd2c 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -281,13 +281,14 @@ class TaskSnapshotSurface implements StartingSurface { } @Override - public void remove() { + public void remove(boolean animate) { synchronized (mService.mGlobalLock) { final long now = SystemClock.uptimeMillis(); if (mSizeMismatch && now - mShownTime < SIZE_MISMATCH_MINIMUM_TIME_MS // Show the latest content as soon as possible for unlocking to home. && mActivityType != ACTIVITY_TYPE_HOME) { - mHandler.postAtTime(this::remove, mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS); + mHandler.postAtTime(() -> remove(false /* prepareAnimation */), + mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS); ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Defer removing snapshot surface in %dms", (now - mShownTime)); @@ -517,7 +518,7 @@ class TaskSnapshotSurface implements StartingSurface { // The orientation of the screen is changing. We better remove the snapshot ASAP as // we are going to wait on the new window in any case to unfreeze the screen, and // the starting window is not needed anymore. - sHandler.post(mOuter::remove); + sHandler.post(() -> mOuter.remove(false /* prepareAnimation */)); } if (reportDraw) { sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget(); 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/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 7450782364f4..53ebfb2c6e0e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -221,7 +221,8 @@ public abstract class WindowManagerInternal { DragState state, Display display, InputManagerService service, InputChannel source) { state.register(display); - return service.transferTouchFocus(source, state.getInputChannel()); + return service.transferTouchFocus(source, state.getInputChannel(), + true /* isDragDrop */); } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6f853c795525..b95674e511d5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -273,7 +273,6 @@ import android.window.TaskSnapshot; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.BackgroundThread; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; @@ -367,9 +366,6 @@ public class WindowManagerService extends IWindowManager.Stub /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */ static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000; - /** Amount of time (in milliseconds) to delay before declaring a seamless rotation timeout. */ - static final int SEAMLESS_ROTATION_TIMEOUT_DURATION = 2000; - /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */ static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000; @@ -529,14 +525,6 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { - // Bugreport dumps the trace 2x, 1x as proto and 1x as text. Save file to disk only 1x. - if (asProto && mWindowTracing.isEnabled()) { - mWindowTracing.stopTrace(null, false /* writeToFile */); - BackgroundThread.getHandler().post(() -> { - mWindowTracing.writeTraceToFile(); - mWindowTracing.startTrace(null); - }); - } doDump(fd, pw, new String[] {"-a"}, asProto); } @@ -2245,16 +2233,9 @@ public class WindowManagerService extends IWindowManager.Stub win.setFrameNumber(frameNumber); final DisplayContent dc = win.getDisplayContent(); - if (!dc.mWaitingForConfig) { - win.finishSeamlessRotation(false /* timeout */); - } - - if (win.mPendingPositionChanged != null) { - win.mPendingPositionChanged.updateLeashPosition(frameNumber); - win.mPendingPositionChanged = null; - } if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) { + win.prepareDrawHandlers(); result |= RELAYOUT_RES_BLAST_SYNC; } @@ -2999,8 +2980,8 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void screenTurningOff(ScreenOffListener listener) { - mTaskSnapshotController.screenTurningOff(listener); + public void screenTurningOff(int displayId, ScreenOffListener listener) { + mTaskSnapshotController.screenTurningOff(displayId, listener); } @Override @@ -5095,7 +5076,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int UPDATE_ANIMATION_SCALE = 51; public static final int WINDOW_HIDE_TIMEOUT = 52; - public static final int SEAMLESS_ROTATION_TIMEOUT = 54; public static final int RESTORE_POINTER_ICON = 55; public static final int SET_HAS_OVERLAY_UI = 58; public static final int ANIMATION_FAILSAFE = 60; @@ -5378,13 +5358,6 @@ public class WindowManagerService extends IWindowManager.Stub } break; } - case SEAMLESS_ROTATION_TIMEOUT: { - final DisplayContent displayContent = (DisplayContent) msg.obj; - synchronized (mGlobalLock) { - displayContent.getDisplayRotation().onSeamlessRotationTimeout(); - } - break; - } case SET_HAS_OVERLAY_UI: { mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1); break; @@ -5702,6 +5675,11 @@ public class WindowManagerService extends IWindowManager.Stub mBlurController.unregisterCrossWindowBlurEnabledListener(listener); } + @Override + public void setForceCrossWindowBlurDisabled(boolean disable) { + mBlurController.setForceCrossWindowBlurDisabled(disable); + } + // ------------------------------------------------------------- // Internals // ------------------------------------------------------------- diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index d9b879fdf8dc..7ebc1cc6d5c1 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -168,8 +168,8 @@ import static com.android.server.wm.WindowStateProto.FINISHED_SEAMLESS_ROTATION_ import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION; import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS; import static com.android.server.wm.WindowStateProto.GLOBAL_SCALE; +import static com.android.server.wm.WindowStateProto.HAS_COMPAT_SCALE; import static com.android.server.wm.WindowStateProto.HAS_SURFACE; -import static com.android.server.wm.WindowStateProto.IN_SIZE_COMPAT_MODE; import static com.android.server.wm.WindowStateProto.IS_ON_SCREEN; import static com.android.server.wm.WindowStateProto.IS_READY_FOR_DISPLAY; import static com.android.server.wm.WindowStateProto.IS_VISIBLE; @@ -261,6 +261,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.function.Consumer; import java.util.function.Predicate; /** A window in the window manager. */ @@ -725,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; @@ -749,6 +748,42 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private final WindowProcessController mWpcForDisplayAreaConfigChanges; /** + * We split the draw handlers in to a "pending" and "ready" list, in order to solve + * sequencing problems. Think of it this way, let's say I update a windows orientation + * (in configuration), and then I call applyWithNextDraw. What I'm hoping for is to + * apply with the draw that contains the orientation change. However, since the client + * can call finishDrawing at any time, it could be about to call a previous call to + * finishDrawing (or maybe its already called it, we just haven't handled it). Since this + * frame was already completed it had no time to include the orientation change we made. + * To solve this problem we accumulate draw handlers in mPendingDrawHandlers, and then force + * the client to call relayout. Only the frame post relayout will contain the configuration + * change since the window has to relayout), and so in relayout we drain mPendingDrawHandlers + * into mReadyDrawHandlers. Finally once we get to finishDrawing we know everything in + * mReadyDrawHandlers corresponds to state which was observed by the client and we can + * invoke the consumers. + */ + private final List<Consumer<SurfaceControl.Transaction>> mPendingDrawHandlers + = new ArrayList<>(); + private final List<Consumer<SurfaceControl.Transaction>> mReadyDrawHandlers + = new ArrayList<>(); + + private final Consumer<SurfaceControl.Transaction> mSeamlessRotationFinishedConsumer = t -> { + finishSeamlessRotation(t); + updateSurfacePosition(t); + }; + + private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> { + if (mSurfaceControl != null && mSurfaceControl.isValid()) { + t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y); + } + }; + + /** + * @see #setSurfaceTranslationY(int) + */ + private int mSurfaceTranslationY; + + /** * Returns the visibility of the given {@link InternalInsetsType type} requested by the client. * * @param type the given {@link InternalInsetsType type}. @@ -831,19 +866,27 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mPendingSeamlessRotate.unrotate(transaction, this); getDisplayContent().getDisplayRotation().markForSeamlessRotation(this, true /* seamlesslyRotated */); + applyWithNextDraw(mSeamlessRotationFinishedConsumer); } } - void finishSeamlessRotation(boolean timeout) { - if (mPendingSeamlessRotate != null) { - mPendingSeamlessRotate.finish(this, timeout); - mFinishSeamlessRotateFrameNumber = getFrameNumber(); - mPendingSeamlessRotate = null; - getDisplayContent().getDisplayRotation().markForSeamlessRotation(this, - false /* seamlesslyRotated */); - if (mControllableInsetProvider != null) { - mControllableInsetProvider.finishSeamlessRotation(timeout); - } + void cancelSeamlessRotation() { + finishSeamlessRotation(getPendingTransaction()); + } + + void finishSeamlessRotation(SurfaceControl.Transaction t) { + if (mPendingSeamlessRotate == null) { + return; + } + + mPendingSeamlessRotate.finish(t, this); + mFinishSeamlessRotateFrameNumber = getFrameNumber(); + mPendingSeamlessRotate = null; + + getDisplayContent().getDisplayRotation().markForSeamlessRotation(this, + false /* seamlesslyRotated */); + if (mControllableInsetProvider != null) { + mControllableInsetProvider.finishSeamlessRotation(); } } @@ -1050,18 +1093,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * scaling override set. * @see CompatModePackages#getCompatScale * @see android.content.res.CompatibilityInfo#supportsScreen - * @see ActivityRecord#inSizeCompatMode() + * @see ActivityRecord#hasSizeCompatBounds() */ - boolean inSizeCompatMode() { - return mOverrideScale != 1f || inSizeCompatMode(mAttrs, mActivityRecord); + boolean hasCompatScale() { + return mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord); } /** * @return {@code true} if the application runs in size compatibility mode. * @see android.content.res.CompatibilityInfo#supportsScreen - * @see ActivityRecord#inSizeCompatMode() + * @see ActivityRecord#hasSizeCompatBounds() */ - static boolean inSizeCompatMode(WindowManager.LayoutParams attrs, WindowToken windowToken) { + static boolean hasCompatScale(WindowManager.LayoutParams attrs, WindowToken windowToken) { return (attrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0 || (windowToken != null && windowToken.hasSizeCompatBounds() // Exclude starting window because it is not displayed by the application. @@ -1266,7 +1309,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP windowFrames.offsetFrames(-layoutXDiff, -layoutYDiff); windowFrames.mCompatFrame.set(windowFrames.mFrame); - if (inSizeCompatMode()) { + if (hasCompatScale()) { // Also the scaled frame that we report to the app needs to be // adjusted to be in its coordinate space. windowFrames.mCompatFrame.scale(mInvGlobalScale); @@ -1538,7 +1581,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ InsetsState getCompatInsetsState() { InsetsState state = getInsetsState(); - if (inSizeCompatMode()) { + if (hasCompatScale()) { state = new InsetsState(state, true); state.scale(mInvGlobalScale); } @@ -1676,7 +1719,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void prelayout() { - if (inSizeCompatMode()) { + if (hasCompatScale()) { if (mOverrideScale != 1f) { mGlobalScale = mToken.hasSizeCompatBounds() ? mToken.getSizeCompatScale() * mOverrideScale @@ -2090,6 +2133,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP : getTask().getWindowConfiguration().hasMovementAnimations(); if (mToken.okToAnimate() && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0 + && !mWindowFrames.didFrameSizeChange() + && !surfaceInsetsChanging() && !isDragResizing() && hasMovementAnimation && !mWinAnimator.mLastHidden @@ -3590,7 +3635,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void fillClientWindowFrames(ClientWindowFrames outFrames) { outFrames.frame.set(mWindowFrames.mCompatFrame); outFrames.displayFrame.set(mWindowFrames.mDisplayFrame); - if (mInvGlobalScale != 1.0f && inSizeCompatMode()) { + if (mInvGlobalScale != 1.0f && hasCompatScale()) { outFrames.displayFrame.scale(mInvGlobalScale); } @@ -3990,7 +4035,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP proto.write(PENDING_SEAMLESS_ROTATION, mPendingSeamlessRotate != null); proto.write(FINISHED_SEAMLESS_ROTATION_FRAME, mFinishSeamlessRotateFrameNumber); proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate); - proto.write(IN_SIZE_COMPAT_MODE, inSizeCompatMode()); + proto.write(HAS_COMPAT_SCALE, hasCompatScale()); proto.write(GLOBAL_SCALE, mGlobalScale); proto.end(token); } @@ -4092,7 +4137,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.println(prefix + "mHasSurface=" + mHasSurface + " isReadyForDisplay()=" + isReadyForDisplay() + " mWindowRemovalAllowed=" + mWindowRemovalAllowed); - if (inSizeCompatMode()) { + if (hasCompatScale()) { pw.println(prefix + "mCompatFrame=" + mWindowFrames.mCompatFrame.toShortString(sTmpSB)); } if (dumpAll) { @@ -4215,18 +4260,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float x, y; int w,h; - final boolean inSizeCompatMode = inSizeCompatMode(); + final boolean hasCompatScale = hasCompatScale(); if ((mAttrs.flags & FLAG_SCALED) != 0) { if (mAttrs.width < 0) { w = pw; - } else if (inSizeCompatMode) { + } else if (hasCompatScale) { w = (int)(mAttrs.width * mGlobalScale + .5f); } else { w = mAttrs.width; } if (mAttrs.height < 0) { h = ph; - } else if (inSizeCompatMode) { + } else if (hasCompatScale) { h = (int)(mAttrs.height * mGlobalScale + .5f); } else { h = mAttrs.height; @@ -4234,21 +4279,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } else { if (mAttrs.width == MATCH_PARENT) { w = pw; - } else if (inSizeCompatMode) { + } else if (hasCompatScale) { w = (int)(mRequestedWidth * mGlobalScale + .5f); } else { w = mRequestedWidth; } if (mAttrs.height == MATCH_PARENT) { h = ph; - } else if (inSizeCompatMode) { + } else if (hasCompatScale) { h = (int)(mRequestedHeight * mGlobalScale + .5f); } else { h = mRequestedHeight; } } - if (inSizeCompatMode) { + if (hasCompatScale) { x = mAttrs.x * mGlobalScale; y = mAttrs.y * mGlobalScale; } else { @@ -4276,7 +4321,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // We need to make sure we update the CompatFrame as it is used for // cropping decisions, etc, on systems where we lack a decor layer. windowFrames.mCompatFrame.set(windowFrames.mFrame); - if (inSizeCompatMode) { + if (hasCompatScale) { // See comparable block in computeFrameLw. windowFrames.mCompatFrame.scale(mInvGlobalScale); } @@ -4394,7 +4439,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float translateToWindowX(float x) { float winX = x - mWindowFrames.mFrame.left; - if (inSizeCompatMode()) { + if (hasCompatScale()) { winX *= mGlobalScale; } return winX; @@ -4402,7 +4447,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float translateToWindowY(float y) { float winY = y - mWindowFrames.mFrame.top; - if (inSizeCompatMode()) { + if (hasCompatScale()) { winY *= mGlobalScale; } return winY; @@ -5279,13 +5324,17 @@ 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) { + applyWithNextDraw(mSetSurfacePositionConsumer); + } else { + mSetSurfacePositionConsumer.accept(t); } } } @@ -5324,6 +5373,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Expand for surface insets. See WindowState.expandForSurfaceInsets. transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets); outPoint.offset(-mTmpPoint.x, -mTmpPoint.y); + + outPoint.y += mSurfaceTranslationY; } /** @@ -5331,7 +5382,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * scaled, the insets also need to be scaled for surface position in global coordinate. */ private void transformSurfaceInsetsPosition(Point outPos, Rect surfaceInsets) { - if (!inSizeCompatMode()) { + if (!hasCompatScale()) { outPos.x = surfaceInsets.left; outPos.y = surfaceInsets.top; return; @@ -5684,6 +5735,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms"); mActivityRecord.mRelaunchStartTime = 0; } + + executeDrawHandlers(postDrawTransaction); if (!onSyncFinishedDrawing()) { return mWinAnimator.finishDrawingLocked(postDrawTransaction); } @@ -5698,6 +5751,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void immediatelyNotifyBlastSync() { + prepareDrawHandlers(); finishDrawing(null); mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this); if (!useBLASTSync()) return; @@ -5777,4 +5831,75 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top, -attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom); } + + /** + * This method is used to control whether we return the BLAST_SYNC flag + * from relayoutWindow calls on this window (triggering the client to redirect + * it's next draw in to a transaction). If we have pending draw handlers, we are + * looking for the client to sync. + * + * See {@link WindowState#mPendingDrawHandlers} + */ + @Override + boolean useBLASTSync() { + return super.useBLASTSync() || (mPendingDrawHandlers.size() != 0); + } + + /** + * Apply the transaction with the next window redraw. A full relayout/finishDrawing + * cycle must occur before completion. This means if you call the function while + * "in relayout", the results may be undefined but at all other times the function + * should sort of transparently work like this: + * 1. Make changes to WM hierarchy (say change app configuration) + * 2. Call apply with next draw. + * 3. After finishDrawing, our consumer will be passed the Transaction + * containing the buffer, and we can merge in additional operations. + * See {@link WindowState#mPendingDrawHandlers} + */ + void applyWithNextDraw(Consumer<SurfaceControl.Transaction> consumer) { + mPendingDrawHandlers.add(consumer); + requestRedrawForSync(); + + mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this, + BLAST_TIMEOUT_DURATION); + } + + /** + * Called from relayout, to indicate the next "finishDrawing" will contain + * all changes applied by the time mPendingDrawHandlers was populated. + * + * See {@link WindowState#mPendingDrawHandlers} + */ + void prepareDrawHandlers() { + mReadyDrawHandlers.addAll(mPendingDrawHandlers); + mPendingDrawHandlers.clear(); + } + + /** + * Drain the draw handlers, called from finishDrawing() + * See {@link WindowState#mPendingDrawHandlers} + */ + boolean executeDrawHandlers(SurfaceControl.Transaction t) { + if (t == null) t = mTmpTransaction; + boolean hadHandlers = false; + for (int i = 0; i < mReadyDrawHandlers.size(); i++) { + mReadyDrawHandlers.get(i).accept(t); + hadHandlers = true; + } + mReadyDrawHandlers.clear(); + mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this); + + t.apply(); + + return hadHandlers; + } + + /** + * Adds an additional translation offset to be applied when positioning the surface. Used to + * correct offsets in specific reparenting situations, e.g. the navigation bar window attached + * on the lower split-screen app. + */ + void setSurfaceTranslationY(int translationY) { + mSurfaceTranslationY = translationY; + } } diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index e8b8bfce21a3..0bb97f560a1c 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -114,15 +114,6 @@ class WindowTracing { * @param pw Print writer */ void stopTrace(@Nullable PrintWriter pw) { - stopTrace(pw, true /* writeToFile */); - } - - /** - * Stops the trace - * @param pw Print writer - * @param writeToFile If the current buffer should be written to disk or not - */ - void stopTrace(@Nullable PrintWriter pw, boolean writeToFile) { if (IS_USER) { logAndPrintln(pw, "Error: Tracing is not supported on user builds."); return; @@ -135,12 +126,35 @@ class WindowTracing { logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush."); throw new IllegalStateException("tracing enabled while waiting for flush."); } - if (writeToFile) { - writeTraceToFileLocked(); - logAndPrintln(pw, "Trace written to " + mTraceFile + "."); + writeTraceToFileLocked(); + logAndPrintln(pw, "Trace written to " + mTraceFile + "."); + } + ProtoLogImpl.getSingleInstance().stopProtoLog(pw, true); + } + + /** + * Stops the trace and write the current buffer to disk then restart, if it's already running. + * @param pw Print writer + */ + void saveForBugreport(@Nullable PrintWriter pw) { + if (IS_USER) { + logAndPrintln(pw, "Error: Tracing is not supported on user builds."); + return; + } + synchronized (mEnabledLock) { + if (!mEnabled) { + return; } + mEnabled = mEnabledLockFree = false; + logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush."); + writeTraceToFileLocked(); + logAndPrintln(pw, "Trace written to " + mTraceFile + "."); + ProtoLogImpl.getSingleInstance().stopProtoLog(pw, true); + logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); + mBuffer.resetBuffer(); + mEnabled = mEnabledLockFree = true; + ProtoLogImpl.getSingleInstance().startProtoLog(pw); } - ProtoLogImpl.getSingleInstance().stopProtoLog(pw, writeToFile); } private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) { @@ -188,6 +202,9 @@ class WindowTracing { case "stop": stopTrace(pw); return 0; + case "save-for-bugreport": + saveForBugreport(pw); + return 0; case "status": logAndPrintln(pw, getStatus()); return 0; @@ -230,6 +247,7 @@ class WindowTracing { pw.println("Window manager trace options:"); pw.println(" start: Start logging"); pw.println(" stop: Stop logging"); + pw.println(" save-for-bugreport: Save logging data to file if it's running."); pw.println(" frame: Log trace once per frame"); pw.println(" transaction: Log each transaction"); pw.println(" size: Set the maximum log size (in KB)"); @@ -316,19 +334,6 @@ class WindowTracing { } } - /** - * Writes the trace buffer to new file for the bugreport. - * - * This method is synchronized with {@code #startTrace(PrintWriter)} and - * {@link #stopTrace(PrintWriter)}. - */ - void writeTraceToFile() { - synchronized (mEnabledLock) { - writeTraceToFileLocked(); - } - ProtoLogImpl.getSingleInstance().writeProtoLogToFile(); - } - private void logAndPrintln(@Nullable PrintWriter pw, String msg) { Log.i(TAG, msg); if (pw != null) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 10705af9ac38..be06d0395499 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1783,8 +1783,9 @@ static void nativeSetSystemUiLightsOut(JNIEnv* /* env */, jclass /* clazz */, jl im->setSystemUiLightsOut(lightsOut); } -static jboolean nativeTransferTouchFocus(JNIEnv* env, - jclass /* clazz */, jlong ptr, jobject fromChannelTokenObj, jobject toChannelTokenObj) { +static jboolean nativeTransferTouchFocus(JNIEnv* env, jclass /* clazz */, jlong ptr, + jobject fromChannelTokenObj, jobject toChannelTokenObj, + jboolean isDragDrop) { if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) { return JNI_FALSE; } @@ -1793,8 +1794,8 @@ static jboolean nativeTransferTouchFocus(JNIEnv* env, sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj); NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); - if (im->getInputManager()->getDispatcher()->transferTouchFocus( - fromChannelToken, toChannelToken)) { + if (im->getInputManager()->getDispatcher()->transferTouchFocus(fromChannelToken, toChannelToken, + isDragDrop)) { return JNI_TRUE; } else { return JNI_FALSE; @@ -2267,7 +2268,7 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*)nativeRequestPointerCapture}, {"nativeSetInputDispatchMode", "(JZZ)V", (void*)nativeSetInputDispatchMode}, {"nativeSetSystemUiLightsOut", "(JZ)V", (void*)nativeSetSystemUiLightsOut}, - {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)Z", + {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;Z)Z", (void*)nativeTransferTouchFocus}, {"nativeSetPointerSpeed", "(JI)V", (void*)nativeSetPointerSpeed}, {"nativeSetShowTouches", "(JZ)V", (void*)nativeSetShowTouches}, diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index ef2d0baff031..f60b35499013 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -32,8 +32,6 @@ #include "com_android_server_vibrator_VibratorManagerService.h" namespace V1_0 = android::hardware::vibrator::V1_0; -namespace V1_1 = android::hardware::vibrator::V1_1; -namespace V1_2 = android::hardware::vibrator::V1_2; namespace V1_3 = android::hardware::vibrator::V1_3; namespace aidl = android::hardware::vibrator; @@ -85,10 +83,11 @@ static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) class VibratorControllerWrapper { public: VibratorControllerWrapper(JNIEnv* env, int32_t vibratorId, jobject callbackListener) - : mHal(std::move(findVibrator(vibratorId))), + : mHal(findVibrator(vibratorId)), mVibratorId(vibratorId), mCallbackListener(env->NewGlobalRef(callbackListener)) { - LOG_ALWAYS_FATAL_IF(mHal == nullptr, "Unable to find reference to vibrator hal"); + LOG_ALWAYS_FATAL_IF(mHal == nullptr, + "Failed to connect to vibrator HAL, or vibratorId is invalid"); LOG_ALWAYS_FATAL_IF(mCallbackListener == nullptr, "Unable to create global reference to vibration callback handler"); } @@ -130,15 +129,15 @@ static void destroyNativeWrapper(void* ptr) { } } -static jlong vibratorInit(JNIEnv* env, jclass /* clazz */, jint vibratorId, - jobject callbackListener) { +static jlong vibratorNativeInit(JNIEnv* env, jclass /* clazz */, jint vibratorId, + jobject callbackListener) { std::unique_ptr<VibratorControllerWrapper> wrapper = std::make_unique<VibratorControllerWrapper>(env, vibratorId, callbackListener); wrapper->hal()->init(); return reinterpret_cast<jlong>(wrapper.release()); } -static jlong vibratorGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) { +static jlong vibratorGetNativeFinalizer(JNIEnv* /* env */, jclass /* clazz */) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeWrapper)); } @@ -286,25 +285,46 @@ static void vibratorAlwaysOnDisable(JNIEnv* env, jclass /* clazz */, jlong ptr, wrapper->hal()->alwaysOnDisable(static_cast<int32_t>(id)); } +static float vibratorGetResonantFrequency(JNIEnv* env, jclass /* clazz */, jlong ptr) { + VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); + if (wrapper == nullptr) { + ALOGE("vibratorGetResonantFrequency failed because native wrapper was not initialized"); + return NAN; + } + auto result = wrapper->hal()->getResonantFrequency(); + return result.isOk() ? static_cast<jfloat>(result.value()) : NAN; +} + +static float vibratorGetQFactor(JNIEnv* env, jclass /* clazz */, jlong ptr) { + VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); + if (wrapper == nullptr) { + ALOGE("vibratorGetQFactor failed because native wrapper was not initialized"); + return NAN; + } + auto result = wrapper->hal()->getQFactor(); + return result.isOk() ? static_cast<jfloat>(result.value()) : NAN; +} + static const JNINativeMethod method_table[] = { - {"vibratorInit", + {"nativeInit", "(ILcom/android/server/vibrator/VibratorController$OnVibrationCompleteListener;)J", - (void*)vibratorInit}, - {"vibratorGetFinalizer", "()J", (void*)vibratorGetFinalizer}, - {"vibratorIsAvailable", "(J)Z", (void*)vibratorIsAvailable}, - {"vibratorOn", "(JJJ)V", (void*)vibratorOn}, - {"vibratorOff", "(J)V", (void*)vibratorOff}, - {"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude}, - {"vibratorPerformEffect", "(JJJJ)J", (void*)vibratorPerformEffect}, - {"vibratorPerformComposedEffect", - "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)J", + (void*)vibratorNativeInit}, + {"getNativeFinalizer", "()J", (void*)vibratorGetNativeFinalizer}, + {"isAvailable", "(J)Z", (void*)vibratorIsAvailable}, + {"on", "(JJJ)V", (void*)vibratorOn}, + {"off", "(J)V", (void*)vibratorOff}, + {"setAmplitude", "(JI)V", (void*)vibratorSetAmplitude}, + {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect}, + {"performComposedEffect", "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)J", (void*)vibratorPerformComposedEffect}, - {"vibratorGetSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects}, - {"vibratorGetSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives}, - {"vibratorSetExternalControl", "(JZ)V", (void*)vibratorSetExternalControl}, - {"vibratorGetCapabilities", "(J)J", (void*)vibratorGetCapabilities}, - {"vibratorAlwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable}, - {"vibratorAlwaysOnDisable", "(JJ)V", (void*)vibratorAlwaysOnDisable}, + {"getSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects}, + {"getSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives}, + {"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl}, + {"getCapabilities", "(J)J", (void*)vibratorGetCapabilities}, + {"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable}, + {"alwaysOnDisable", "(JJ)V", (void*)vibratorAlwaysOnDisable}, + {"getResonantFrequency", "(J)F", (void*)vibratorGetResonantFrequency}, + {"getQFactor", "(J)F", (void*)vibratorGetQFactor}, }; int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env) { @@ -320,7 +340,8 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F"); sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I"); - return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorController", + return jniRegisterNativeMethods(env, + "com/android/server/vibrator/VibratorController$NativeWrapper", method_table, NELEM(method_table)); } diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index 6a8f6d419786..d43cf3f59170 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -45,13 +45,6 @@ xsd_config { } xsd_config { - name: "cec-config", - srcs: ["cec-config/cec-config.xsd"], - api_dir: "cec-config/schema", - package_name: "com.android.server.hdmi.cec.config", -} - -xsd_config { name: "device-state-config", srcs: ["device-state-config/device-state-config.xsd"], api_dir: "device-state-config/schema", diff --git a/services/core/xsd/cec-config/cec-config.xsd b/services/core/xsd/cec-config/cec-config.xsd deleted file mode 100644 index b59c93cce332..000000000000 --- a/services/core/xsd/cec-config/cec-config.xsd +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<xs:schema version="2.0" - elementFormDefault="qualified" - attributeFormDefault="unqualified" - xmlns:xs="http://www.w3.org/2001/XMLSchema"> - <xs:element name="cec-settings"> - <xs:complexType> - <xs:sequence> - <xs:element name="setting" type="setting" minOccurs="0" maxOccurs="unbounded"/> - </xs:sequence> - </xs:complexType> - </xs:element> - <xs:complexType name="setting"> - <xs:attribute name="name" type="xs:string"/> - <xs:attribute name="value-type" type="xs:string"/> - <xs:attribute name="user-configurable" type="xs:boolean"/> - <xs:element name="allowed-values" type="value-list" minOccurs="1" maxOccurs="1"/> - <xs:element name="default-value" type="value" minOccurs="1" maxOccurs="1"/> - </xs:complexType> - <xs:complexType name="value-list"> - <xs:sequence> - <xs:element name="value" type="value" minOccurs="1" maxOccurs="unbounded"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="value"> - <xs:attribute name="string-value" type="xs:string"/> - <xs:attribute name="int-value" type="xs:string"/> - </xs:complexType> -</xs:schema> diff --git a/services/core/xsd/cec-config/schema/current.txt b/services/core/xsd/cec-config/schema/current.txt deleted file mode 100644 index 75872d4fb8a7..000000000000 --- a/services/core/xsd/cec-config/schema/current.txt +++ /dev/null @@ -1,44 +0,0 @@ -// Signature format: 2.0 -package com.android.server.hdmi.cec.config { - - public class CecSettings { - ctor public CecSettings(); - method public java.util.List<com.android.server.hdmi.cec.config.Setting> getSetting(); - } - - public class Setting { - ctor public Setting(); - method public com.android.server.hdmi.cec.config.ValueList getAllowedValues(); - method public com.android.server.hdmi.cec.config.Value getDefaultValue(); - method public String getName(); - method public boolean getUserConfigurable(); - method public String getValueType(); - method public void setAllowedValues(com.android.server.hdmi.cec.config.ValueList); - method public void setDefaultValue(com.android.server.hdmi.cec.config.Value); - method public void setName(String); - method public void setUserConfigurable(boolean); - method public void setValueType(String); - } - - public class Value { - ctor public Value(); - method public String getIntValue(); - method public String getStringValue(); - method public void setIntValue(String); - method public void setStringValue(String); - } - - public class ValueList { - ctor public ValueList(); - method public java.util.List<com.android.server.hdmi.cec.config.Value> getValue(); - } - - public class XmlParser { - ctor public XmlParser(); - method public static com.android.server.hdmi.cec.config.CecSettings read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; - method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; - method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; - } - -} - diff --git a/services/core/xsd/cec-config/schema/last_current.txt b/services/core/xsd/cec-config/schema/last_current.txt deleted file mode 100644 index e69de29bb2d1..000000000000 --- a/services/core/xsd/cec-config/schema/last_current.txt +++ /dev/null diff --git a/services/core/xsd/cec-config/schema/last_removed.txt b/services/core/xsd/cec-config/schema/last_removed.txt deleted file mode 100644 index e69de29bb2d1..000000000000 --- a/services/core/xsd/cec-config/schema/last_removed.txt +++ /dev/null diff --git a/services/core/xsd/cec-config/schema/removed.txt b/services/core/xsd/cec-config/schema/removed.txt deleted file mode 100644 index d802177e249b..000000000000 --- a/services/core/xsd/cec-config/schema/removed.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java index 48ae8d672fb1..aed13b263a7f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java @@ -19,6 +19,8 @@ package com.android.server.devicepolicy; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; +import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG; + import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.TEXT; @@ -38,7 +40,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.IndentingPrintWriter; -import android.util.Log; import android.util.Slog; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; @@ -455,8 +456,7 @@ class ActiveAdmin { try { trustAgentInfo.options.saveToXml(out); } catch (XmlPullParserException e) { - Log.e(DevicePolicyManagerService.LOG_TAG, - "Failed to save TrustAgent options", e); + Slog.e(LOG_TAG, e, "Failed to save TrustAgent options"); } out.endTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS); } @@ -629,8 +629,7 @@ class ActiveAdmin { String tag = parser.getName(); if (TAG_POLICIES.equals(tag)) { if (shouldOverridePolicies) { - Log.d(DevicePolicyManagerService.LOG_TAG, - "Overriding device admin policies from XML."); + Slog.d(LOG_TAG, "Overriding device admin policies from XML."); info.readPoliciesFromXml(parser); } } else if (TAG_PASSWORD_QUALITY.equals(tag)) { @@ -726,16 +725,14 @@ class ActiveAdmin { if (type == TypedXmlPullParser.TEXT) { shortSupportMessage = parser.getText(); } else { - Log.w(DevicePolicyManagerService.LOG_TAG, - "Missing text when loading short support message"); + Slog.w(LOG_TAG, "Missing text when loading short support message"); } } else if (TAG_LONG_SUPPORT_MESSAGE.equals(tag)) { type = parser.next(); if (type == TypedXmlPullParser.TEXT) { longSupportMessage = parser.getText(); } else { - Log.w(DevicePolicyManagerService.LOG_TAG, - "Missing text when loading long support message"); + Slog.w(LOG_TAG, "Missing text when loading long support message"); } } else if (TAG_PARENT_ADMIN.equals(tag)) { Preconditions.checkState(!isParent); @@ -748,8 +745,7 @@ class ActiveAdmin { if (type == TypedXmlPullParser.TEXT) { organizationName = parser.getText(); } else { - Log.w(DevicePolicyManagerService.LOG_TAG, - "Missing text when loading organization name"); + Slog.w(LOG_TAG, "Missing text when loading organization name"); } } else if (TAG_IS_LOGOUT_ENABLED.equals(tag)) { isLogoutEnabled = parser.getAttributeBoolean(null, ATTR_VALUE, false); @@ -758,16 +754,14 @@ class ActiveAdmin { if (type == TypedXmlPullParser.TEXT) { startUserSessionMessage = parser.getText(); } else { - Log.w(DevicePolicyManagerService.LOG_TAG, - "Missing text when loading start session message"); + Slog.w(LOG_TAG, "Missing text when loading start session message"); } } else if (TAG_END_USER_SESSION_MESSAGE.equals(tag)) { type = parser.next(); if (type == TypedXmlPullParser.TEXT) { endUserSessionMessage = parser.getText(); } else { - Log.w(DevicePolicyManagerService.LOG_TAG, - "Missing text when loading end session message"); + Slog.w(LOG_TAG, "Missing text when loading end session message"); } } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) { mCrossProfileCalendarPackages = readPackageList(parser, tag); @@ -802,16 +796,14 @@ class ActiveAdmin { if (type == TypedXmlPullParser.TEXT) { mOrganizationId = parser.getText(); } else { - Log.w(DevicePolicyManagerService.LOG_TAG, - "Missing Organization ID."); + Slog.w(LOG_TAG, "Missing Organization ID."); } } else if (TAG_ENROLLMENT_SPECIFIC_ID.equals(tag)) { type = parser.next(); if (type == TypedXmlPullParser.TEXT) { mEnrollmentSpecificId = parser.getText(); } else { - Log.w(DevicePolicyManagerService.LOG_TAG, - "Missing Enrollment-specific ID."); + Slog.w(LOG_TAG, "Missing Enrollment-specific ID."); } } else if (TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS.equals(tag)) { mAdminCanGrantSensorsPermissions = parser.getAttributeBoolean(null, ATTR_VALUE, @@ -820,7 +812,7 @@ class ActiveAdmin { mUsbDataSignalingEnabled = parser.getAttributeBoolean(null, ATTR_VALUE, USB_DATA_SIGNALING_ENABLED_DEFAULT); } else { - Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown admin tag: " + tag); + Slog.w(LOG_TAG, "Unknown admin tag: %s", tag); XmlUtils.skipCurrentTag(parser); } } @@ -842,12 +834,10 @@ class ActiveAdmin { if (packageName != null) { result.add(packageName); } else { - Slog.w(DevicePolicyManagerService.LOG_TAG, - "Package name missing under " + outerTag); + Slog.w(LOG_TAG, "Package name missing under %s", outerTag); } } else { - Slog.w(DevicePolicyManagerService.LOG_TAG, - "Unknown tag under " + tag + ": " + outerTag); + Slog.w(LOG_TAG, "Unknown tag under %s: ", tag, outerTag); } } return result; @@ -868,8 +858,7 @@ class ActiveAdmin { if (tag.equals(tagDAM)) { result.add(parser.getAttributeValue(null, ATTR_VALUE)); } else { - Slog.e(DevicePolicyManagerService.LOG_TAG, - "Expected tag " + tag + " but found " + tagDAM); + Slog.e(LOG_TAG, "Expected tag %s but found %s", tag, tagDAM); } } } @@ -891,8 +880,7 @@ class ActiveAdmin { final TrustAgentInfo trustAgentInfo = getTrustAgentInfo(parser, tag); result.put(component, trustAgentInfo); } else { - Slog.w(DevicePolicyManagerService.LOG_TAG, - "Unknown tag under " + tag + ": " + tagDAM); + Slog.w(LOG_TAG, "Unknown tag under %s: %s", tag, tagDAM); } } return result; @@ -912,8 +900,7 @@ class ActiveAdmin { if (TAG_TRUST_AGENT_COMPONENT_OPTIONS.equals(tagDAM)) { result.options = PersistableBundle.restoreFromXml(parser); } else { - Slog.w(DevicePolicyManagerService.LOG_TAG, - "Unknown tag under " + tag + ": " + tagDAM); + Slog.w(LOG_TAG, "Unknown tag under %s: %s", tag, tagDAM); } } return result; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java index d812b8f7fadb..e0c5e328f8c7 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java @@ -16,6 +16,8 @@ package com.android.server.devicepolicy; +import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG; + import android.app.Notification; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -33,7 +35,7 @@ import android.provider.Settings; import android.security.Credentials; import android.security.KeyChain; import android.security.KeyChain.KeyChainConnection; -import android.util.Log; +import android.util.Slog; import com.android.internal.R; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; @@ -47,7 +49,6 @@ import java.security.cert.X509Certificate; import java.util.List; public class CertificateMonitor { - protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG; protected static final int MONITORING_CERT_NOTIFICATION_ID = SystemMessage.NOTE_SSL_CERT_INFO; private final DevicePolicyManagerService mService; @@ -78,16 +79,16 @@ public class CertificateMonitor { X509Certificate cert = parseCert(certBuffer); pemCert = Credentials.convertToPem(cert); } catch (CertificateException | IOException ce) { - Log.e(LOG_TAG, "Problem converting cert", ce); + Slog.e(LOG_TAG, ce, "Problem converting cert"); return null; } try (KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(userHandle)) { return keyChainConnection.getService().installCaCertificate(pemCert); } catch (RemoteException e) { - Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e); + Slog.e(LOG_TAG, e, "installCaCertsToKeyChain(): "); } catch (InterruptedException e1) { - Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1); + Slog.w(LOG_TAG, e1, "installCaCertsToKeyChain(): "); Thread.currentThread().interrupt(); } return null; @@ -99,9 +100,9 @@ public class CertificateMonitor { keyChainConnection.getService().deleteCaCertificate(aliases[i]); } } catch (RemoteException e) { - Log.e(LOG_TAG, "from CaCertUninstaller: ", e); + Slog.e(LOG_TAG, e, "from CaCertUninstaller: "); } catch (InterruptedException ie) { - Log.w(LOG_TAG, "CaCertUninstaller: ", ie); + Slog.w(LOG_TAG, ie, "CaCertUninstaller: "); Thread.currentThread().interrupt(); } } @@ -137,7 +138,8 @@ public class CertificateMonitor { }; private void updateInstalledCertificates(final UserHandle userHandle) { - if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) { + final int userId = userHandle.getIdentifier(); + if (!mInjector.getUserManager().isUserUnlocked(userId)) { return; } @@ -145,7 +147,8 @@ public class CertificateMonitor { try { installedCerts = getInstalledCaCertificates(userHandle); } catch (RemoteException | RuntimeException e) { - Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e); + Slog.e(LOG_TAG, e, "Could not retrieve certificates from KeyChain service for user %d", + userId); return; } mService.onInstalledCertificatesChanged(userHandle, installedCerts); @@ -167,7 +170,7 @@ public class CertificateMonitor { try { userContext = mInjector.createContextAsUser(userHandle); } catch (PackageManager.NameNotFoundException e) { - Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e); + Slog.e(LOG_TAG, e, "Create context as %s failed", userHandle); return null; } @@ -183,7 +186,6 @@ public class CertificateMonitor { smallIconId = R.drawable.stat_sys_certificate_info; parentUserId = mService.getProfileParentId(userHandle.getIdentifier()); } else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) { - final String ownerName = mService.getDeviceOwnerName(); contentText = resources.getString(R.string.ssl_ca_cert_noti_managed, mService.getDeviceOwnerName()); smallIconId = R.drawable.stat_sys_certificate_info; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java index 3067d4507162..00e0292d404f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java @@ -46,19 +46,11 @@ public class DeviceAdminServiceController { final Object mLock = new Object(); final Context mContext; - private final DevicePolicyManagerService mService; private final DevicePolicyManagerService.Injector mInjector; private final DevicePolicyConstants mConstants; private final Handler mHandler; // needed? - static void debug(String format, Object... args) { - if (!DEBUG) { - return; - } - Slog.d(TAG, String.format(format, args)); - } - private class DevicePolicyServiceConnection extends PersistentConnection<IDeviceAdminService> { public DevicePolicyServiceConnection(int userId, @NonNull ComponentName componentName) { @@ -88,7 +80,6 @@ public class DeviceAdminServiceController { public DeviceAdminServiceController(DevicePolicyManagerService service, DevicePolicyConstants constants) { - mService = service; mInjector = service.mInjector; mContext = mInjector.mContext; mHandler = new Handler(BackgroundThread.get().getLooper()); @@ -122,8 +113,9 @@ public class DeviceAdminServiceController { synchronized (mLock) { final ServiceInfo service = findService(packageName, userId); if (service == null) { - debug("Owner package %s on u%d has no service.", - packageName, userId); + if (DEBUG) { + Slog.d(TAG, "Owner package %s on u%d has no service.", packageName, userId); + } disconnectServiceOnUserLocked(userId, actionForLog); return; } @@ -134,14 +126,17 @@ public class DeviceAdminServiceController { // Note even when we're already connected to the same service, the binding // would have died at this point due to a package update. So we disconnect // anyway and re-connect. - debug("Disconnecting from existing service connection.", - packageName, userId); + if (DEBUG) { + Slog.d("Disconnecting from existing service connection.", packageName, + userId); + } disconnectServiceOnUserLocked(userId, actionForLog); } - debug("Owner package %s on u%d has service %s for %s", - packageName, userId, + if (DEBUG) { + Slog.d("Owner package %s on u%d has service %s for %s", packageName, userId, service.getComponentName().flattenToShortString(), actionForLog); + } final DevicePolicyServiceConnection conn = new DevicePolicyServiceConnection( @@ -172,8 +167,10 @@ public class DeviceAdminServiceController { private void disconnectServiceOnUserLocked(int userId, @NonNull String actionForLog) { final DevicePolicyServiceConnection conn = mConnections.get(userId); if (conn != null) { - debug("Stopping service for u%d if already running for %s.", - userId, actionForLog); + if (DEBUG) { + Slog.d(TAG, "Stopping service for u%d if already running for %s.", userId, + actionForLog); + } conn.unbind(); mConnections.remove(userId); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java index 464d6f5ea835..84e6da0d9851 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java @@ -88,7 +88,7 @@ public class DevicePolicyConstants { } catch (IllegalArgumentException e) { // Failed to parse the settings string, log this and move on // with defaults. - Slog.e(TAG, "Bad device policy settings: " + settings); + Slog.e(TAG, "Bad device policy settings: %s", settings); } long dasDiedServiceReconnectBackoffSec = parser.getLong( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java index c0b2ed4cc955..52cdce6ee7b3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java @@ -179,11 +179,11 @@ class DevicePolicyData { */ static boolean store(DevicePolicyData policyData, JournaledFile file, boolean isFdeDevice) { FileOutputStream stream = null; + File chooseForWrite = null; try { - File chooseForWrite = file.chooseForWrite(); + chooseForWrite = file.chooseForWrite(); if (VERBOSE_LOG) { - Slog.v(TAG, "Storing data for user " + policyData.mUserId + " on " - + chooseForWrite); + Slog.v(TAG, "Storing data for user %d on %s ", policyData.mUserId, chooseForWrite); } stream = new FileOutputStream(chooseForWrite, false); TypedXmlSerializer out = Xml.resolveSerializer(stream); @@ -195,7 +195,7 @@ class DevicePolicyData { policyData.mRestrictionsProvider.flattenToString()); } if (policyData.mUserSetupComplete) { - if (VERBOSE_LOG) Slog.v(TAG, "setting " + ATTR_SETUP_COMPLETE + " to true"); + if (VERBOSE_LOG) Slog.v(TAG, "setting %s to true", ATTR_SETUP_COMPLETE); out.attributeBoolean(null, ATTR_SETUP_COMPLETE, true); } if (policyData.mPaired) { @@ -216,8 +216,8 @@ class DevicePolicyData { if (policyData.mFactoryResetFlags != 0) { if (VERBOSE_LOG) { - Slog.v(TAG, "Storing factory reset flags for user " + policyData.mUserId + ": " - + factoryResetFlagsToString(policyData.mFactoryResetFlags)); + Slog.v(TAG, "Storing factory reset flags for user %d: %s", policyData.mUserId, + factoryResetFlagsToString(policyData.mFactoryResetFlags)); } out.attributeInt(null, ATTR_FACTORY_RESET_FLAGS, policyData.mFactoryResetFlags); } @@ -382,7 +382,7 @@ class DevicePolicyData { file.commit(); return true; } catch (XmlPullParserException | IOException e) { - Slog.w(TAG, "failed writing file", e); + Slog.w(TAG, e, "failed writing file %s", chooseForWrite); try { if (stream != null) { stream.close(); @@ -404,10 +404,8 @@ class DevicePolicyData { ComponentName ownerComponent) { FileInputStream stream = null; File file = journaledFile.chooseForRead(); - if (VERBOSE_LOG) { - Slog.v(TAG, "Loading data for user " + policy.mUserId + " from " + file); - } - + if (VERBOSE_LOG) Slog.v(TAG, "Loading data for user %d from %s", policy.mUserId, file); + boolean needsRewrite = false; try { stream = new FileInputStream(file); TypedXmlPullParser parser = Xml.resolvePullParser(stream); @@ -454,8 +452,8 @@ class DevicePolicyData { policy.mFactoryResetFlags = parser.getAttributeInt(null, ATTR_FACTORY_RESET_FLAGS, 0); if (VERBOSE_LOG) { - Slog.v(TAG, "Restored factory reset flags for user " + policy.mUserId + ": " - + factoryResetFlagsToString(policy.mFactoryResetFlags)); + Slog.v(TAG, "Restored factory reset flags for user %d: %s", policy.mUserId, + factoryResetFlagsToString(policy.mFactoryResetFlags)); } policy.mFactoryResetReason = parser.getAttributeValue(null, ATTR_FACTORY_RESET_REASON); @@ -488,7 +486,7 @@ class DevicePolicyData { policy.mAdminMap.put(ap.info.getComponent(), ap); } } catch (RuntimeException e) { - Slog.w(TAG, "Failed loading admin " + name, e); + Slog.w(TAG, e, "Failed loading admin %s", name); } } else if ("delegation".equals(tag)) { // Parse delegation info. @@ -560,7 +558,7 @@ class DevicePolicyData { policy.mAppsSuspended = parser.getAttributeBoolean(null, ATTR_VALUE, false); } else { - Slog.w(TAG, "Unknown tag: " + tag); + Slog.w(TAG, "Unknown tag: %s", tag); XmlUtils.skipCurrentTag(parser); } } @@ -568,7 +566,7 @@ class DevicePolicyData { // Don't be noisy, this is normal if we haven't defined any policies. } catch (NullPointerException | NumberFormatException | XmlPullParserException | IOException | IndexOutOfBoundsException e) { - Slog.w(TAG, "failed parsing " + file, e); + Slog.w(TAG, e, "failed parsing %s", file); } try { if (stream != null) { @@ -592,8 +590,8 @@ class DevicePolicyData { } } if (!haveOwner) { - Slog.w(TAG, "Previous password owner " + mPasswordOwner - + " no longer active; disabling"); + Slog.w(TAG, "Previous password owner %s no longer active; disabling", + mPasswordOwner); mPasswordOwner = -1; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index a3dadd835202..577f3f5a1cb4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -473,7 +473,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // to decide whether an existing policy in the {@link #DEVICE_POLICIES_XML} needs to // be upgraded. See {@link PolicyVersionUpgrader} on instructions how to add an upgrade // step. - static final int DPMS_VERSION = 1; + static final int DPMS_VERSION = 2; static { SECURE_SETTINGS_ALLOWLIST = new ArraySet<>(); @@ -1106,7 +1106,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * Used by {@code setDevicePolicySafetyChecker()} above and {@link OneTimeSafetyChecker}. */ void setDevicePolicySafetyCheckerUnchecked(DevicePolicySafetyChecker safetyChecker) { - Slog.i(LOG_TAG, String.format("Setting DevicePolicySafetyChecker as %s", safetyChecker)); + Slog.i(LOG_TAG, "Setting DevicePolicySafetyChecker as %s", safetyChecker); mSafetyChecker = safetyChecker; mInjector.setDevicePolicySafetyChecker(safetyChecker); } @@ -1588,7 +1588,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { CryptoTestHelper.runAndLogSelfTest(); } - public String[] getPersonalAppsForSuspension(int userId) { + public String[] getPersonalAppsForSuspension(@UserIdInt int userId) { return PersonalAppsSuspensionHelper.forUser(mContext, userId) .getPersonalAppsForSuspension(); } @@ -2969,8 +2969,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private class DpmsUpgradeDataProvider implements PolicyUpgraderDataProvider { @Override - public boolean isUserDeviceOwner(int userId) { - return mOwners.isDeviceOwnerUserId(userId); + public boolean isDeviceOwner(int userId, ComponentName who) { + return mOwners.isDeviceOwnerUserId(userId) + && mOwners.getDeviceOwnerComponent().equals(who); } @Override @@ -2998,14 +2999,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return component -> findAdmin(component, userId, /* throwForMissingPermission= */ false); } + + @Override + public int[] getUsersForUpgrade() { + List<UserInfo> allUsers = mUserManager.getUsers(); + return allUsers.stream().mapToInt(u -> u.id).toArray(); + } } private void performPolicyVersionUpgrade() { - List<UserInfo> allUsers = mUserManager.getUsers(); PolicyVersionUpgrader upgrader = new PolicyVersionUpgrader( new DpmsUpgradeDataProvider()); - upgrader.upgradePolicy(allUsers.stream().mapToInt(u -> u.id).toArray(), DPMS_VERSION); + upgrader.upgradePolicy(DPMS_VERSION); } private void revertTransferOwnershipIfNecessaryLocked() { @@ -9329,6 +9335,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { dumpResources(pw, mContext, "vendor_cross_profile_apps", R.array.vendor_cross_profile_apps); dumpResources(pw, mContext, "config_packagesExemptFromSuspension", R.array.config_packagesExemptFromSuspension); + dumpResources(pw, mContext, "policy_exempt_apps", R.array.policy_exempt_apps); + dumpResources(pw, mContext, "vendor_policy_exempt_apps", R.array.vendor_policy_exempt_apps); pw.decreaseIndent(); pw.println(); } @@ -10612,6 +10620,30 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public List<String> listPolicyExemptApps() { + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)); + + // 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 + // the resources on constructor. + String[] core = mContext.getResources().getStringArray(R.array.policy_exempt_apps); + String[] vendor = mContext.getResources().getStringArray(R.array.vendor_policy_exempt_apps); + + int size = core.length + vendor.length; + Set<String> apps = new ArraySet<>(size); + for (String app : core) { + apps.add(app); + } + for (String app : vendor) { + apps.add(app); + } + + return new ArrayList<>(apps); + } + + @Override public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner, boolean parent) { Objects.requireNonNull(who, "ComponentName is null"); @@ -14214,10 +14246,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); enforceCallerSystemUserHandle(); - // no effect if it's called from user build - if (!mInjector.isBuildDebuggable()) { - return; - } final int userId = UserHandle.USER_SYSTEM; boolean isUserCompleted = mInjector.settingsSecureGetIntForUser( Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java index 5484a148b0b6..8e31029769d0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java @@ -21,6 +21,7 @@ import android.os.ShellCommand; import com.android.server.devicepolicy.Owners.OwnerDto; import java.io.PrintWriter; +import java.util.Collection; import java.util.List; import java.util.Objects; @@ -30,6 +31,7 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { private static final String CMD_IS_SAFE_OPERATION_BY_REASON = "is-operation-safe-by-reason"; private static final String CMD_SET_SAFE_OPERATION = "set-operation-safe"; private static final String CMD_LIST_OWNERS = "list-owners"; + private static final String CMD_LIST_POLICY_EXEMPT_APPS = "list-policy-exempt-apps"; private final DevicePolicyManagerService mService; @@ -60,6 +62,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { return runSetSafeOperation(pw); case CMD_LIST_OWNERS: return runListOwners(pw); + case CMD_LIST_POLICY_EXEMPT_APPS: + return runListPolicyExemptApps(pw); default: return onInvalidCommand(pw, cmd); } @@ -88,6 +92,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { + " \n\n"); pw.printf(" %s\n", CMD_LIST_OWNERS); pw.printf(" Lists the device / profile owners per user \n\n"); + pw.printf(" %s\n", CMD_LIST_POLICY_EXEMPT_APPS); + pw.printf(" Lists the apps that are exempt from policies\n\n"); } private int runIsSafeOperation(PrintWriter pw) { @@ -119,18 +125,20 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { return 0; } - private int runListOwners(PrintWriter pw) { - List<OwnerDto> owners = mService.listAllOwners(); - if (owners.isEmpty()) { - pw.println("none"); + private int printAndGetSize(PrintWriter pw, Collection<?> collection, String nameOnSingular) { + if (collection.isEmpty()) { + pw.printf("no %ss\n", nameOnSingular); return 0; } - int size = owners.size(); - if (size == 1) { - pw.println("1 owner:"); - } else { - pw.printf("%d owners:\n", size); - } + int size = collection.size(); + pw.printf("%d %s%s:\n", size, nameOnSingular, (size == 1 ? "" : "s")); + return size; + } + + private int runListOwners(PrintWriter pw) { + List<OwnerDto> owners = mService.listAllOwners(); + int size = printAndGetSize(pw, owners, "owner"); + if (size == 0) return 0; for (int i = 0; i < size; i++) { OwnerDto owner = owners.get(i); @@ -150,4 +158,17 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { return 0; } + + private int runListPolicyExemptApps(PrintWriter pw) { + List<String> apps = mService.listPolicyExemptApps(); + int size = printAndGetSize(pw, apps, "policy exempt app"); + + if (size == 0) return 0; + + for (int i = 0; i < size; i++) { + String app = apps.get(i); + pw.printf(" %d: %s\n", i, app); + } + return 0; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java b/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java index 457255bd9a73..28a6987bb846 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java @@ -68,17 +68,16 @@ public final class FactoryResetter { IResultReceiver receiver = new IResultReceiver.Stub() { @Override public void send(int resultCode, Bundle resultData) throws RemoteException { - Slog.i(TAG, String.format("Factory reset confirmed by %s, proceeding", - mSafetyChecker)); + Slog.i(TAG, "Factory reset confirmed by %s, proceeding", mSafetyChecker); try { factoryResetInternalUnchecked(); } catch (IOException e) { // Shouldn't happen - Slog.wtf(TAG, "IOException calling underlying systems", e); + Slog.wtf(TAG, e, "IOException calling underlying systems"); } } }; - Slog.i(TAG, String.format("Delaying factory reset until %s confirms", mSafetyChecker)); + Slog.i(TAG, "Delaying factory reset until %s confirms", mSafetyChecker); mSafetyChecker.onFactoryReset(receiver); return false; } @@ -113,9 +112,9 @@ public final class FactoryResetter { } private void factoryResetInternalUnchecked() throws IOException { - Slog.i(TAG, String.format("factoryReset(): reason=%s, shutdown=%b, force=%b, wipeEuicc=%b, " + Slog.i(TAG, "factoryReset(): reason=%s, shutdown=%b, force=%b, wipeEuicc=%b, " + "wipeAdoptableStorage=%b, wipeFRP=%b", mReason, mShutdown, mForce, mWipeEuicc, - mWipeAdoptableStorage, mWipeFactoryResetProtection)); + mWipeAdoptableStorage, mWipeFactoryResetProtection); UserManager um = mContext.getSystemService(UserManager.class); if (!mForce && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java index 37dbfc170aff..0b9ece466df4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java @@ -18,6 +18,8 @@ package com.android.server.devicepolicy; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; +import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG; + import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -36,6 +38,7 @@ import android.provider.Telephony; import android.text.TextUtils; import android.util.ArraySet; import android.util.IndentingPrintWriter; +import android.util.Log; import android.util.Slog; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; @@ -105,7 +108,9 @@ public final class PersonalAppsSuspensionHelper { result.remove(pkg); } - Slog.i(LOG_TAG, "Packages subject to suspension: " + String.join(",", result)); + if (Log.isLoggable(LOG_TAG, Log.INFO)) { + Slog.i(LOG_TAG, "Packages subject to suspension: %s", String.join(",", result)); + } return result.toArray(new String[0]); } @@ -118,7 +123,7 @@ public final class PersonalAppsSuspensionHelper { for (final ResolveInfo resolveInfo : matchingActivities) { if (resolveInfo.activityInfo == null || TextUtils.isEmpty(resolveInfo.activityInfo.packageName)) { - Slog.wtf(LOG_TAG, "Could not find package name for launcher app" + resolveInfo); + Slog.wtf(LOG_TAG, "Could not find package name for launcher app %s", resolveInfo); continue; } final String packageName = resolveInfo.activityInfo.packageName; @@ -129,7 +134,8 @@ public final class PersonalAppsSuspensionHelper { result.add(packageName); } } catch (PackageManager.NameNotFoundException e) { - Slog.e(LOG_TAG, "Could not find application info for launcher app: " + packageName); + Slog.e(LOG_TAG, "Could not find application info for launcher app: %s", + packageName); } } return result; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyUpgraderDataProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyUpgraderDataProvider.java index b6420f8ff4bc..19a7659f4d60 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyUpgraderDataProvider.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyUpgraderDataProvider.java @@ -33,7 +33,7 @@ public interface PolicyUpgraderDataProvider { * Returns true if the provided {@code userId} is a device owner. May affect some policy * defaults. */ - boolean isUserDeviceOwner(int userId); + boolean isDeviceOwner(int userId, ComponentName who); /** * Returns true if the storage manager indicates file-based encryption is enabled. @@ -60,4 +60,9 @@ public interface PolicyUpgraderDataProvider { * user. */ Function<ComponentName, DeviceAdminInfo> getAdminInfoSupplier(int userId); + + /** + * Returns the users to upgrade. + */ + int[] getUsersForUpgrade(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java index cea08634910c..6bc7ba6499ac 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java @@ -62,7 +62,7 @@ public class PolicyVersionUpgrader { * managed profile user IDs. * @param dpmsVersion The version to upgrade to. */ - public void upgradePolicy(int[] allUsers, int dpmsVersion) { + public void upgradePolicy(int dpmsVersion) { int oldVersion = readVersion(); if (oldVersion >= dpmsVersion) { Slog.i(LOG_TAG, String.format("Current version %d, latest version %d, not upgrading.", @@ -70,6 +70,8 @@ public class PolicyVersionUpgrader { return; } + final int[] allUsers = mProvider.getUsersForUpgrade(); + //NOTE: The current version is provided in case the XML file format changes in a // non-backwards-compatible way, so that DeviceAdminData could load it with // old tags, for example. @@ -83,6 +85,27 @@ public class PolicyVersionUpgrader { currentVersion = 1; } + if (currentVersion == 1) { + Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion)); + // This upgrade step is for Device Owner scenario only: For devices upgrading to S, + // if there is a device owner, it retains the ability to control sensors-related + // permission grants. + for (int userId : allUsers) { + DevicePolicyData userData = allUsersData.get(userId); + if (userData == null) { + continue; + } + for (ActiveAdmin admin : userData.mAdminList) { + if (mProvider.isDeviceOwner(userId, admin.info.getComponent())) { + Slog.i(LOG_TAG, String.format( + "Marking Device Owner in user %d for permission grant ", userId)); + admin.mAdminCanGrantSensorsPermissions = true; + } + } + } + currentVersion = 2; + } + writePoliciesAndVersion(allUsers, allUsersData, currentVersion); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java index 543f3815454e..2959c10d5508 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java @@ -25,6 +25,8 @@ import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_ACCEP import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED; import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED; +import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG; + import android.annotation.IntDef; import android.app.Notification; import android.app.PendingIntent; @@ -58,7 +60,6 @@ import java.util.concurrent.atomic.AtomicBoolean; * Class managing bugreport collection upon device owner's request. */ public class RemoteBugreportManager { - private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG; static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport"; @@ -206,7 +207,7 @@ public class RemoteBugreportManager { return true; } catch (RemoteException re) { // should never happen - Slog.e(LOG_TAG, "Failed to make remote calls to start bugreportremote service", re); + Slog.e(LOG_TAG, re, "Failed to make remote calls to start bugreportremote service"); return false; } finally { mInjector.binderRestoreCallingIdentity(callingIdentity); @@ -220,7 +221,7 @@ public class RemoteBugreportManager { mContext.registerReceiver(mRemoteBugreportFinishedReceiver, filterFinished); } catch (IntentFilter.MalformedMimeTypeException e) { // should never happen, as setting a constant - Slog.w(LOG_TAG, "Failed to set type " + BUGREPORT_MIMETYPE, e); + Slog.w(LOG_TAG, e, "Failed to set type %s", BUGREPORT_MIMETYPE); } final IntentFilter filterConsent = new IntentFilter(); filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED); diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp index 5ffbd771764d..5140b9f6db58 100644 --- a/services/incremental/Android.bp +++ b/services/incremental/Android.bp @@ -68,6 +68,7 @@ cc_defaults { "libutils", "libvold_binder", "libc++fs", + "libziparchive_for_incfs", ], shared_libs: [ "libandroidfw", @@ -77,7 +78,6 @@ cc_defaults { "libincfs", "liblog", "libz", - "libziparchive", ], } diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 1fcc2843bd43..c38d0b3cc7db 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -74,6 +74,13 @@ struct Constants { // If DL was up and not crashing for 10mins, we consider it healthy and reset all delays. static constexpr auto healthyDataLoaderUptime = 10min; + + // For healthy DLs, we'll retry every ~5secs for ~10min + static constexpr auto bindRetryInterval = 5s; + static constexpr auto bindGracePeriod = 10min; + + static constexpr auto bindingTimeout = 1min; + // 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs) static constexpr auto minBindDelay = 10s; static constexpr auto maxBindDelay = 10000s; @@ -293,6 +300,7 @@ IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_v mTimedQueue(sm.getTimedQueue()), mProgressUpdateJobQueue(sm.getProgressUpdateJobQueue()), mFs(sm.getFs()), + mClock(sm.getClock()), mIncrementalDir(rootDir) { CHECK(mVold) << "Vold service is unavailable"; CHECK(mDataLoaderManager) << "DataLoaderManagerService is unavailable"; @@ -302,6 +310,7 @@ IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_v CHECK(mTimedQueue) << "TimedQueue is unavailable"; CHECK(mProgressUpdateJobQueue) << "mProgressUpdateJobQueue is unavailable"; CHECK(mFs) << "Fs is unavailable"; + CHECK(mClock) << "Clock is unavailable"; mJobQueue.reserve(16); mJobProcessor = std::thread([this]() { @@ -2241,17 +2250,44 @@ void IncrementalService::DataLoaderStub::setTargetStatusLocked(int status) { << status << " (current " << mCurrentStatus << ")"; } -Milliseconds IncrementalService::DataLoaderStub::updateBindDelay() { +std::optional<Milliseconds> IncrementalService::DataLoaderStub::needToBind() { std::unique_lock lock(mMutex); + + const auto now = mService.mClock->now(); + const bool healthy = (mPreviousBindDelay == 0ms); + + if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_BINDING && + now - mCurrentStatusTs <= Constants::bindingTimeout) { + LOG(INFO) << "Binding still in progress. " + << (healthy ? "The DL is healthy/freshly bound, ok to retry for a few times." + : "Already unhealthy, don't do anything."); + // Binding still in progress. + if (!healthy) { + // Already unhealthy, don't do anything. + return {}; + } + // The DL is healthy/freshly bound, ok to retry for a few times. + if (now - mPreviousBindTs <= Constants::bindGracePeriod) { + // Still within grace period. + if (now - mCurrentStatusTs >= Constants::bindRetryInterval) { + // Retry interval passed, retrying. + mCurrentStatusTs = now; + mPreviousBindDelay = 0ms; + return 0ms; + } + return {}; + } + // fallthrough, mark as unhealthy, and retry with delay + } + const auto previousBindTs = mPreviousBindTs; - const auto now = Clock::now(); mPreviousBindTs = now; const auto nonCrashingInterval = std::max(castToMs(now - previousBindTs), 100ms); if (previousBindTs.time_since_epoch() == Clock::duration::zero() || nonCrashingInterval > Constants::healthyDataLoaderUptime) { mPreviousBindDelay = 0ms; - return mPreviousBindDelay; + return 0ms; } constexpr auto minBindDelayMs = castToMs(Constants::minBindDelay); @@ -2264,12 +2300,16 @@ Milliseconds IncrementalService::DataLoaderStub::updateBindDelay() { const auto bindDelayJitterRangeMs = bindDelayMs / Constants::bindDelayJitterDivider; const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - bindDelayJitterRangeMs; mPreviousBindDelay = std::chrono::milliseconds(bindDelayMs + bindDelayJitterMs); - return mPreviousBindDelay; } bool IncrementalService::DataLoaderStub::bind() { - const auto bindDelay = updateBindDelay(); + const auto maybeBindDelay = needToBind(); + if (!maybeBindDelay) { + LOG(DEBUG) << "Skipping bind to " << mParams.packageName << " because of pending bind."; + return true; + } + const auto bindDelay = *maybeBindDelay; if (bindDelay > 1s) { LOG(INFO) << "Delaying bind to " << mParams.packageName << " by " << bindDelay.count() / 1000 << "s"; @@ -2279,7 +2319,21 @@ bool IncrementalService::DataLoaderStub::bind() { auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, bindDelay.count(), this, &result); if (!status.isOk() || !result) { - LOG(ERROR) << "Failed to bind a data loader for mount " << id(); + const bool healthy = (bindDelay == 0ms); + LOG(ERROR) << "Failed to bind a data loader for mount " << id() + << (healthy ? ", retrying." : ""); + + // Internal error, retry for healthy/new DLs. + // Let needToBind migrate it to unhealthy after too many retries. + if (healthy) { + if (mService.addTimedJob(*mService.mTimedQueue, id(), Constants::bindRetryInterval, + [this]() { fsmStep(); })) { + // Mark as binding so that we know it's not the DL's fault. + setCurrentStatus(IDataLoaderStatusListener::DATA_LOADER_BINDING); + return true; + } + } + return false; } return true; @@ -2339,7 +2393,14 @@ bool IncrementalService::DataLoaderStub::fsmStep() { // Do nothing, this is a reset state. break; case IDataLoaderStatusListener::DATA_LOADER_DESTROYED: { - return destroy(); + switch (currentStatus) { + case IDataLoaderStatusListener::DATA_LOADER_BINDING: + setCurrentStatus(IDataLoaderStatusListener::DATA_LOADER_DESTROYED); + return true; + default: + return destroy(); + } + break; } case IDataLoaderStatusListener::DATA_LOADER_STARTED: { switch (currentStatus) { @@ -2353,6 +2414,7 @@ bool IncrementalService::DataLoaderStub::fsmStep() { switch (currentStatus) { case IDataLoaderStatusListener::DATA_LOADER_DESTROYED: case IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE: + case IDataLoaderStatusListener::DATA_LOADER_BINDING: return bind(); case IDataLoaderStatusListener::DATA_LOADER_BOUND: return create(); @@ -2372,7 +2434,8 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount fromServiceSpecificError(-EINVAL, "onStatusChange came to invalid DataLoaderStub"); } if (id() != mountId) { - LOG(ERROR) << "Mount ID mismatch: expected " << id() << ", but got: " << mountId; + LOG(ERROR) << "onStatusChanged: mount ID mismatch: expected " << id() + << ", but got: " << mountId; return binder::Status::fromServiceSpecificError(-EPERM, "Mount ID mismatch."); } if (newStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) { @@ -2396,11 +2459,13 @@ void IncrementalService::DataLoaderStub::setCurrentStatus(int newStatus) { } oldStatus = mCurrentStatus; - mCurrentStatus = newStatus; targetStatus = mTargetStatus; - listener = mStatusListener; + // Change the status. + mCurrentStatus = newStatus; + mCurrentStatusTs = mService.mClock->now(); + if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE || mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) { // For unavailable, unbind from DataLoader to ensure proper re-commit. @@ -2428,7 +2493,8 @@ binder::Status IncrementalService::DataLoaderStub::reportStreamHealth(MountId mo "reportStreamHealth came to invalid DataLoaderStub"); } if (id() != mountId) { - LOG(ERROR) << "Mount ID mismatch: expected " << id() << ", but got: " << mountId; + LOG(ERROR) << "reportStreamHealth: mount ID mismatch: expected " << id() + << ", but got: " << mountId; return binder::Status::fromServiceSpecificError(-EPERM, "Mount ID mismatch."); } { @@ -2694,6 +2760,8 @@ static std::string toHexString(const RawMetadata& metadata) { void IncrementalService::DataLoaderStub::onDump(int fd) { dprintf(fd, " dataLoader: {\n"); dprintf(fd, " currentStatus: %d\n", mCurrentStatus); + dprintf(fd, " currentStatusTs: %lldmcs\n", + (long long)(elapsedMcs(mCurrentStatusTs, Clock::now()))); dprintf(fd, " targetStatus: %d\n", mTargetStatus); dprintf(fd, " targetStatusTs: %lldmcs\n", (long long)(elapsedMcs(mTargetStatusTs, Clock::now()))); diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index 14e5a7734172..4eb513808342 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -267,7 +267,10 @@ private: BootClockTsUs getOldestTsFromLastPendingReads(); Milliseconds elapsedMsSinceKernelTs(TimePoint now, BootClockTsUs kernelTsUs); - Milliseconds updateBindDelay(); + // If the stub has to bind to the DL. + // Returns {} if bind operation is already in progress. + // Or bind delay in ms. + std::optional<Milliseconds> needToBind(); void registerForPendingReads(); void unregisterFromPendingReads(); @@ -283,6 +286,7 @@ private: std::condition_variable mStatusCondition; int mCurrentStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED; + TimePoint mCurrentStatusTs = {}; int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED; TimePoint mTargetStatusTs = {}; @@ -443,6 +447,7 @@ private: const std::unique_ptr<TimedQueueWrapper> mTimedQueue; const std::unique_ptr<TimedQueueWrapper> mProgressUpdateJobQueue; const std::unique_ptr<FsWrapper> mFs; + const std::unique_ptr<ClockWrapper> mClock; const std::string mIncrementalDir; mutable std::mutex mLock; diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index d61328942e5c..80f409ff1c61 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -329,6 +329,14 @@ public: } }; +class RealClockWrapper final : public ClockWrapper { +public: + RealClockWrapper() = default; + ~RealClockWrapper() = default; + + TimePoint now() const final { return Clock::now(); } +}; + RealServiceManager::RealServiceManager(sp<IServiceManager> serviceManager, JNIEnv* env) : mServiceManager(std::move(serviceManager)), mJvm(RealJniWrapper::getJvm(env)) {} @@ -388,6 +396,10 @@ std::unique_ptr<FsWrapper> RealServiceManager::getFs() { return std::make_unique<RealFsWrapper>(); } +std::unique_ptr<ClockWrapper> RealServiceManager::getClock() { + return std::make_unique<RealClockWrapper>(); +} + static JavaVM* getJavaVm(JNIEnv* env) { CHECK(env); JavaVM* jvm = nullptr; diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index 245bb3105be5..d113f992de71 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -158,6 +158,12 @@ public: virtual void listFilesRecursive(std::string_view directoryPath, FileCallback onFile) const = 0; }; +class ClockWrapper { +public: + virtual ~ClockWrapper() = default; + virtual TimePoint now() const = 0; +}; + class ServiceManagerWrapper { public: virtual ~ServiceManagerWrapper() = default; @@ -170,6 +176,7 @@ public: virtual std::unique_ptr<TimedQueueWrapper> getTimedQueue() = 0; virtual std::unique_ptr<TimedQueueWrapper> getProgressUpdateJobQueue() = 0; virtual std::unique_ptr<FsWrapper> getFs() = 0; + virtual std::unique_ptr<ClockWrapper> getClock() = 0; }; // --- Real stuff --- @@ -187,6 +194,7 @@ public: std::unique_ptr<TimedQueueWrapper> getTimedQueue() final; std::unique_ptr<TimedQueueWrapper> getProgressUpdateJobQueue() final; std::unique_ptr<FsWrapper> getFs() final; + std::unique_ptr<ClockWrapper> getClock() final; private: template <class INTERFACE> diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index 5236983c83ff..25b34b5669b8 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -248,6 +248,27 @@ public: } return binder::Status::ok(); } + binder::Status bindToDataLoaderNotOkWithNoDelay(int32_t mountId, + const DataLoaderParamsParcel& params, + int bindDelayMs, + const sp<IDataLoaderStatusListener>& listener, + bool* _aidl_return) { + CHECK(bindDelayMs == 0) << bindDelayMs; + *_aidl_return = false; + return binder::Status::ok(); + } + binder::Status bindToDataLoaderBindingWithNoDelay(int32_t mountId, + const DataLoaderParamsParcel& params, + int bindDelayMs, + const sp<IDataLoaderStatusListener>& listener, + bool* _aidl_return) { + CHECK(bindDelayMs == 0) << bindDelayMs; + *_aidl_return = true; + if (listener) { + listener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_BINDING); + } + return binder::Status::ok(); + } binder::Status bindToDataLoaderOkWith10sDelay(int32_t mountId, const DataLoaderParamsParcel& params, int bindDelayMs, @@ -557,6 +578,21 @@ public: } }; +class MockClockWrapper : public ClockWrapper { +public: + MOCK_CONST_METHOD0(now, TimePoint()); + + void start() { ON_CALL(*this, now()).WillByDefault(Invoke(this, &MockClockWrapper::getClock)); } + template <class Delta> + void advance(Delta delta) { + mClock += delta; + } + + TimePoint getClock() const { return mClock; } + + TimePoint mClock = Clock::now(); +}; + class MockStorageHealthListener : public os::incremental::BnStorageHealthListener { public: MOCK_METHOD2(onHealthStatus, binder::Status(int32_t storageId, int32_t status)); @@ -594,7 +630,7 @@ public: std::unique_ptr<MockLooperWrapper> looper, std::unique_ptr<MockTimedQueueWrapper> timedQueue, std::unique_ptr<MockTimedQueueWrapper> progressUpdateJobQueue, - std::unique_ptr<MockFsWrapper> fs) + std::unique_ptr<MockFsWrapper> fs, std::unique_ptr<MockClockWrapper> clock) : mVold(std::move(vold)), mDataLoaderManager(std::move(dataLoaderManager)), mIncFs(std::move(incfs)), @@ -603,7 +639,8 @@ public: mLooper(std::move(looper)), mTimedQueue(std::move(timedQueue)), mProgressUpdateJobQueue(std::move(progressUpdateJobQueue)), - mFs(std::move(fs)) {} + mFs(std::move(fs)), + mClock(std::move(clock)) {} std::unique_ptr<VoldServiceWrapper> getVoldService() final { return std::move(mVold); } std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final { return std::move(mDataLoaderManager); @@ -619,6 +656,7 @@ public: return std::move(mProgressUpdateJobQueue); } std::unique_ptr<FsWrapper> getFs() final { return std::move(mFs); } + std::unique_ptr<ClockWrapper> getClock() final { return std::move(mClock); } private: std::unique_ptr<MockVoldService> mVold; @@ -630,6 +668,7 @@ private: std::unique_ptr<MockTimedQueueWrapper> mTimedQueue; std::unique_ptr<MockTimedQueueWrapper> mProgressUpdateJobQueue; std::unique_ptr<MockFsWrapper> mFs; + std::unique_ptr<MockClockWrapper> mClock; }; // --- IncrementalServiceTest --- @@ -657,6 +696,8 @@ public: mProgressUpdateJobQueue = progressUpdateJobQueue.get(); auto fs = std::make_unique<NiceMock<MockFsWrapper>>(); mFs = fs.get(); + auto clock = std::make_unique<NiceMock<MockClockWrapper>>(); + mClock = clock.get(); mIncrementalService = std::make_unique< IncrementalService>(MockServiceManager(std::move(vold), std::move(dataloaderManager), @@ -664,12 +705,13 @@ public: std::move(jni), std::move(looper), std::move(timedQueue), std::move(progressUpdateJobQueue), - std::move(fs)), + std::move(fs), std::move(clock)), mRootDir.path); mDataLoaderParcel.packageName = "com.test"; mDataLoaderParcel.arguments = "uri"; mDataLoaderManager->unbindFromDataLoaderSuccess(); mIncrementalService->onSystemReady(); + mClock->start(); setupSuccess(); } @@ -724,6 +766,7 @@ protected: NiceMock<MockTimedQueueWrapper>* mTimedQueue = nullptr; NiceMock<MockTimedQueueWrapper>* mProgressUpdateJobQueue = nullptr; NiceMock<MockFsWrapper>* mFs = nullptr; + NiceMock<MockClockWrapper>* mClock = nullptr; NiceMock<MockDataLoader>* mDataLoader = nullptr; std::unique_ptr<IncrementalService> mIncrementalService; TemporaryDir mRootDir; @@ -853,6 +896,119 @@ TEST_F(IncrementalServiceTest, testDataLoaderDestroyedAndDelayed) { mDataLoaderManager->setDataLoaderStatusDestroyed(); } +TEST_F(IncrementalServiceTest, testDataLoaderOnRestart) { + mIncFs->waitForPendingReadsSuccess(); + mIncFs->openMountSuccess(); + + constexpr auto bindRetryInterval = 5s; + + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(10); + EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6); + EXPECT_CALL(*mDataLoader, start(_)).Times(6); + EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); + EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); + EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2); + TemporaryDir tempDir; + int storageId = + mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel, + IncrementalService::CreateOptions::CreateNew); + ASSERT_GE(storageId, 0); + + // First binds to DataLoader fails... because it's restart. + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderNotOkWithNoDelay)); + + // Request DL start. + mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {}); + + // Retry callback present. + ASSERT_EQ(storageId, mTimedQueue->mId); + ASSERT_EQ(mTimedQueue->mAfter, bindRetryInterval); + auto retryCallback = mTimedQueue->mWhat; + mTimedQueue->clearJob(storageId); + + // Expecting the same bindToDataLoaderNotOkWithNoDelay call. + mClock->advance(5s); + + retryCallback(); + // Retry callback present. + ASSERT_EQ(storageId, mTimedQueue->mId); + ASSERT_EQ(mTimedQueue->mAfter, bindRetryInterval); + retryCallback = mTimedQueue->mWhat; + mTimedQueue->clearJob(storageId); + + // Returning "binding" so that we can retry. + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderBindingWithNoDelay)); + + // Expecting bindToDataLoaderBindingWithNoDelay call. + mClock->advance(5s); + + retryCallback(); + // No retry callback. + ASSERT_EQ(mTimedQueue->mAfter, 0ms); + ASSERT_EQ(mTimedQueue->mWhat, nullptr); + + // Should not change the bindToDataLoader call count + ASSERT_NE(nullptr, mLooper->mCallback); + ASSERT_NE(nullptr, mLooper->mCallbackData); + auto looperCb = mLooper->mCallback; + auto looperCbData = mLooper->mCallbackData; + looperCb(-1, -1, looperCbData); + + // Expecting the same bindToDataLoaderBindingWithNoDelay call. + mClock->advance(5s); + + // Use pending reads callback to trigger binding. + looperCb(-1, -1, looperCbData); + + // No retry callback. + ASSERT_EQ(mTimedQueue->mAfter, 0ms); + ASSERT_EQ(mTimedQueue->mWhat, nullptr); + + // Now we are out of 10m "retry" budget, let's finally bind. + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, &MockDataLoaderManager::bindToDataLoaderOk)); + mClock->advance(11min); + + // Use pending reads callback to trigger binding. + looperCb(-1, -1, looperCbData); + + // No retry callback. + ASSERT_EQ(mTimedQueue->mAfter, 0ms); + ASSERT_EQ(mTimedQueue->mWhat, nullptr); + + // And test the rest of the backoff. + // Simulated crash/other connection breakage. + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith100sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith1000sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); +} + TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) { mDataLoader->initializeCreateOkNoStatus(); EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1); diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java index 169b85eb7e06..b07fe19393b2 100644 --- a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java +++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java @@ -19,6 +19,7 @@ package com.android.server.smartspace; import static android.Manifest.permission.MANAGE_SMARTSPACE; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.content.Context.SMARTSPACE_SERVICE; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.annotation.NonNull; import android.annotation.Nullable; @@ -161,11 +162,13 @@ public class SmartspaceManagerService extends Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); } - if (!(mServiceNameResolver.isTemporary(userId) + Context ctx = getContext(); + if (!(ctx.checkCallingPermission(MANAGE_SMARTSPACE) == PERMISSION_GRANTED + || mServiceNameResolver.isTemporary(userId) || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) { - String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid(); + String msg = "Permission Denial: Cannot call " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid(); Slog.w(TAG, msg); throw new SecurityException(msg); } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java index 129d2633ae92..e13597dc6982 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java @@ -19,23 +19,37 @@ package com.android.server.am; 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.assertTrue; import static org.junit.Assert.fail; import android.app.ActivityManager; +import android.app.ActivityManager.OnUidImportanceListener; import android.app.ActivityManager.RecentTaskInfo; +import android.app.ActivityManager.RunningAppProcessInfo; import android.app.IActivityManager; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.DropBoxManager; +import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.support.test.uiautomator.UiDevice; +import android.test.suitebuilder.annotation.LargeTest; +import android.text.TextUtils; +import android.util.Log; import androidx.test.InstrumentationRegistry; import androidx.test.filters.FlakyTest; @@ -65,6 +79,12 @@ public class ActivityManagerTest { private static final long AWAIT_TIMEOUT = 2000; private static final long CHECK_INTERVAL = 100; + private static final String TEST_FGS_CLASS = + "com.android.servicestests.apps.simpleservicetestapp.SimpleFgService"; + private static final String ACTION_FGS_STATS_TEST = + "com.android.servicestests.apps.simpleservicetestapp.ACTION_FGS_STATS_TEST"; + private static final String EXTRA_MESSENGER = "extra_messenger"; + private IActivityManager mService; private IRemoteCallback mCallback; private Context mContext; @@ -204,4 +224,184 @@ public class ActivityManagerTest { public void onServiceDisconnected(ComponentName name) { } } + + /** + * Note: This test actually only works in eng build. It'll always pass + * in user and userdebug build, because the expected exception won't be + * thrown in those builds. + */ + @LargeTest + @Test + public void testFgsProcStatsTracker() throws Exception { + final PackageManager pm = mContext.getPackageManager(); + final long timeout = 5000; + int uid = pm.getPackageUid(TEST_APP, 0); + final MyUidImportanceListener uidListener1 = new MyUidImportanceListener(uid); + final MyUidImportanceListener uidListener2 = new MyUidImportanceListener(uid); + final ActivityManager am = mContext.getSystemService(ActivityManager.class); + final CountDownLatch[] latchHolder = new CountDownLatch[1]; + final H handler = new H(Looper.getMainLooper(), latchHolder); + final Messenger messenger = new Messenger(handler); + final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class); + final CountDownLatch dboxLatch = new CountDownLatch(1); + final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String tag_wtf = "system_server_wtf"; + if (tag_wtf.equals(intent.getStringExtra(DropBoxManager.EXTRA_TAG))) { + final DropBoxManager.Entry e = dbox.getNextEntry(tag_wtf, intent.getLongExtra( + DropBoxManager.EXTRA_TIME, 0) - 1); + final String text = e.getText(8192); + if (TextUtils.isEmpty(text)) { + return; + } + if (text.indexOf("can't store negative values") == -1) { + return; + } + dboxLatch.countDown(); + } + } + }; + try { + mContext.registerReceiver(receiver, + new IntentFilter(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED)); + am.addOnUidImportanceListener(uidListener1, + RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE); + am.addOnUidImportanceListener(uidListener2, RunningAppProcessInfo.IMPORTANCE_GONE); + runShellCommand("cmd deviceidle whitelist +" + TEST_APP); + toggleScreenOn(true); + + final Intent intent = new Intent(ACTION_FGS_STATS_TEST); + final ComponentName cn = ComponentName.unflattenFromString( + TEST_APP + "/" + TEST_FGS_CLASS); + final Bundle bundle = new Bundle(); + intent.setComponent(cn); + bundle.putBinder(EXTRA_MESSENGER, messenger.getBinder()); + intent.putExtras(bundle); + + latchHolder[0] = new CountDownLatch(1); + mContext.startForegroundService(intent); + assertTrue("Timed out to start fg service", uidListener1.waitFor( + RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE, timeout)); + assertTrue("Timed out to get the remote messenger", latchHolder[0].await( + timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(timeout); + latchHolder[0] = new CountDownLatch(1); + handler.sendRemoteMessage(H.MSG_STOP_FOREGROUND, 0, 0, null); + assertTrue("Timed out to wait for stop fg", latchHolder[0].await( + timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(timeout); + latchHolder[0] = new CountDownLatch(1); + handler.sendRemoteMessage(H.MSG_START_FOREGROUND, 0, 0, null); + assertTrue("Timed out to wait for start fg", latchHolder[0].await( + timeout, TimeUnit.MILLISECONDS)); + + toggleScreenOn(false); + latchHolder[0] = new CountDownLatch(1); + handler.sendRemoteMessage(H.MSG_STOP_FOREGROUND, 0, 0, null); + assertTrue("Timed out to wait for stop fg", latchHolder[0].await( + timeout, TimeUnit.MILLISECONDS)); + assertFalse("There shouldn't be negative values", dboxLatch.await( + timeout * 2, TimeUnit.MILLISECONDS)); + } finally { + toggleScreenOn(true); + runShellCommand("cmd deviceidle whitelist -" + TEST_APP); + am.removeOnUidImportanceListener(uidListener1); + am.removeOnUidImportanceListener(uidListener2); + am.forceStopPackage(TEST_APP); + mContext.unregisterReceiver(receiver); + } + } + + /** + * Make sure the screen state. + */ + private void toggleScreenOn(final boolean screenon) throws Exception { + if (screenon) { + runShellCommand("input keyevent KEYCODE_WAKEUP"); + runShellCommand("wm dismiss-keyguard"); + } else { + runShellCommand("input keyevent KEYCODE_SLEEP"); + } + // Since the screen on/off intent is ordered, they will not be sent right now. + Thread.sleep(2_000); + } + + private class H extends Handler { + static final int MSG_INIT = 0; + static final int MSG_DONE = 1; + static final int MSG_START_FOREGROUND = 2; + static final int MSG_STOP_FOREGROUND = 3; + + private Messenger mRemoteMessenger; + private CountDownLatch[] mLatchHolder; + + H(Looper looper, CountDownLatch[] latchHolder) { + super(looper); + mLatchHolder = latchHolder; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_INIT: + mRemoteMessenger = (Messenger) msg.obj; + mLatchHolder[0].countDown(); + break; + case MSG_DONE: + mLatchHolder[0].countDown(); + break; + } + } + + void sendRemoteMessage(int what, int arg1, int arg2, Object obj) { + Message msg = Message.obtain(); + msg.what = what; + msg.arg1 = arg1; + msg.arg2 = arg2; + msg.obj = obj; + try { + mRemoteMessenger.send(msg); + } catch (RemoteException e) { + } + msg.recycle(); + } + } + + private static class MyUidImportanceListener implements OnUidImportanceListener { + final CountDownLatch[] mLatchHolder = new CountDownLatch[1]; + private final int mExpectedUid; + private int mExpectedImportance; + private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE; + + MyUidImportanceListener(int uid) { + mExpectedUid = uid; + } + + @Override + public void onUidImportance(int uid, int importance) { + if (uid == mExpectedUid) { + synchronized (this) { + if (importance == mExpectedImportance && mLatchHolder[0] != null) { + mLatchHolder[0].countDown(); + } + mCurrentImportance = importance; + } + Log.i(TAG, "uid " + uid + " importance: " + importance); + } + } + + boolean waitFor(int expectedImportance, long timeout) throws Exception { + synchronized (this) { + mExpectedImportance = expectedImportance; + if (mCurrentImportance == expectedImportance) { + return true; + } + mLatchHolder[0] = new CountDownLatch(1); + } + return mLatchHolder[0].await(timeout, TimeUnit.MILLISECONDS); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index 07f67327b2bf..1c9683803857 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -40,9 +40,11 @@ import android.content.pm.UserInfo; import android.net.Uri; import android.os.RemoteException; import android.os.UserManager; +import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.server.LocalServices; import com.android.server.SystemService; import org.junit.Before; @@ -60,6 +62,7 @@ import java.util.List; * Tests for {@link com.android.server.apphibernation.AppHibernationService} */ @SmallTest +@Presubmit public final class AppHibernationServiceTest { private static final String PACKAGE_SCHEME = "package"; private static final String PACKAGE_NAME_1 = "package1"; @@ -91,6 +94,7 @@ public final class AppHibernationServiceTest { MockitoAnnotations.initMocks(this); doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); + LocalServices.removeServiceForTest(AppHibernationManagerInternal.class); mAppHibernationService = new AppHibernationService(new MockInjector(mContext)); verify(mContext).registerReceiver(mReceiverCaptor.capture(), any()); diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java index 59f3c35f2137..2237c845cde7 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java @@ -19,6 +19,7 @@ package com.android.server.apphibernation; import static org.junit.Assert.assertEquals; import android.os.FileUtils; +import android.platform.test.annotations.Presubmit; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; @@ -48,6 +49,7 @@ import java.util.concurrent.TimeoutException; @SmallTest +@Presubmit public class HibernationStateDiskStoreTest { private static final String STATES_FILE_NAME = "states"; private final MockScheduledExecutorService mMockScheduledExecutorService = diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java index f00edcc85404..fcd6b842426a 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java @@ -120,6 +120,11 @@ class CompatConfigBuilder { return this; } + CompatConfigBuilder addEnabledSinceApexChangeWithId(int sdk, long id) { + mChanges.add(new CompatChange(id, "", -1, sdk, false, false, "", false)); + return this; + } + CompatConfig build() { CompatConfig config = new CompatConfig(mBuildClassifier, mContext); config.forceNonDebuggableFinalForTest(false); diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index b6b6932c4a93..bd774056aef8 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -86,6 +86,7 @@ public class CompatConfigTest { // Assume userdebug/eng non-final build when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); when(mBuildClassifier.isFinalBuild()).thenReturn(false); + when(mBuildClassifier.platformTargetSdk()).thenReturn(30); ChangeIdStateCache.disable(); when(mPackageManager.getApplicationInfo(anyString(), anyInt())) .thenThrow(new NameNotFoundException()); @@ -567,6 +568,34 @@ public class CompatConfigTest { } @Test + public void testReadApexConfig() throws IOException { + String configXml = "<config>" + + "<compat-change id=\"1234\" name=\"MY_CHANGE1\" enableAfterTargetSdk=\"2\" />" + + "<compat-change id=\"1235\" name=\"MY_CHANGE2\" disabled=\"true\" />" + + "<compat-change id=\"1236\" name=\"MY_CHANGE3\" />" + + "<compat-change id=\"1237\" name=\"MY_CHANGE4\" enableSinceTargetSdk=\"31\" />" + + "</config>"; + + File dir = createTempDir(); + writeToFile(dir, "platform_compat_config.xml", configXml); + CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + compatConfig.forceNonDebuggableFinalForTest(false); + + compatConfig.initConfigFromLib(dir); + + assertThat(compatConfig.isChangeEnabled(1234L, + ApplicationInfoBuilder.create().withTargetSdk(1).build())).isFalse(); + assertThat(compatConfig.isChangeEnabled(1234L, + ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue(); + assertThat(compatConfig.isChangeEnabled(1235L, + ApplicationInfoBuilder.create().withTargetSdk(5).build())).isFalse(); + assertThat(compatConfig.isChangeEnabled(1236L, + ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue(); + assertThat(compatConfig.isChangeEnabled(1237L, + ApplicationInfoBuilder.create().withTargetSdk(31).build())).isTrue(); + } + + @Test public void testReadConfigMultipleFiles() throws IOException { String configXml1 = "<config>" + "<compat-change id=\"1234\" name=\"MY_CHANGE1\" enableAfterTargetSdk=\"2\" />" diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java index 0fd6445fbeeb..57fdcd340a02 100644 --- a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java @@ -22,6 +22,7 @@ import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARG import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE; import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH; import static com.android.internal.compat.OverrideAllowedState.LOGGING_ONLY_CHANGE; +import static com.android.internal.compat.OverrideAllowedState.PLATFORM_TOO_OLD; import static com.google.common.truth.Truth.assertThat; @@ -52,6 +53,7 @@ public class OverrideValidatorImplTest { private static final int TARGET_SDK = 10; private static final int TARGET_SDK_BEFORE = 9; private static final int TARGET_SDK_AFTER = 11; + private static final int PLATFORM_SDK_VERSION = 30; @Mock private PackageManager mPackageManager; @@ -61,6 +63,7 @@ public class OverrideValidatorImplTest { private AndroidBuildClassifier debuggableBuild() { AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class); when(buildClassifier.isDebuggableBuild()).thenReturn(true); + when(buildClassifier.platformTargetSdk()).thenReturn(PLATFORM_SDK_VERSION); return buildClassifier; } @@ -68,6 +71,7 @@ public class OverrideValidatorImplTest { AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class); when(buildClassifier.isDebuggableBuild()).thenReturn(false); when(buildClassifier.isFinalBuild()).thenReturn(false); + when(buildClassifier.platformTargetSdk()).thenReturn(PLATFORM_SDK_VERSION); return buildClassifier; } @@ -75,6 +79,7 @@ public class OverrideValidatorImplTest { AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class); when(buildClassifier.isDebuggableBuild()).thenReturn(false); when(buildClassifier.isFinalBuild()).thenReturn(true); + when(buildClassifier.platformTargetSdk()).thenReturn(PLATFORM_SDK_VERSION); return buildClassifier; } @@ -333,6 +338,26 @@ public class OverrideValidatorImplTest { } @Test + public void getOverrideAllowedState_targetSdkChangeGreaterThanOsVersion_rejectOverride() + throws Exception { + final AndroidBuildClassifier buildClassifier = finalBuild(); + CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) + .addEnabledSinceApexChangeWithId(PLATFORM_SDK_VERSION + 1, 1).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .debuggable() + .build()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + assertThat(stateTargetSdkLessChange).isEqualTo( + new OverrideAllowedState(PLATFORM_TOO_OLD, -1, + PLATFORM_SDK_VERSION)); + } + + @Test public void getOverrideAllowedState_finalBuildEnabledChangeDebugApp_rejectOverride() throws Exception { CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index 799b06734b54..3fc6e9918382 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -78,11 +78,12 @@ public class PlatformCompatTest { when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) .thenThrow(new PackageManager.NameNotFoundException()); mCompatConfig = new CompatConfig(mBuildClassifier, mContext); - mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); + mPlatformCompat = new PlatformCompat(mContext, mCompatConfig, mBuildClassifier); // Assume userdebug/eng non-final build mCompatConfig.forceNonDebuggableFinalForTest(false); when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); when(mBuildClassifier.isFinalBuild()).thenReturn(false); + when(mBuildClassifier.platformTargetSdk()).thenReturn(30); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); } @@ -99,7 +100,7 @@ public class PlatformCompatTest { .addLoggingOnlyChangeWithId(7L) .addOverridableChangeWithId(8L) .build(); - mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); + mPlatformCompat = new PlatformCompat(mContext, mCompatConfig, mBuildClassifier); assertThat(mPlatformCompat.listAllChanges()).asList().containsExactly( new CompatibilityChangeInfo(1L, "", -1, -1, false, false, "", false), new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, "", false), @@ -125,8 +126,9 @@ public class PlatformCompatTest { .addEnableSinceSdkChangeWithId(Build.VERSION_CODES.Q, 5L) .addEnableSinceSdkChangeWithId(Build.VERSION_CODES.R, 6L) .addLoggingOnlyChangeWithId(7L) + .addEnableSinceSdkChangeWithId(31, 8L) .build(); - mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); + mPlatformCompat = new PlatformCompat(mContext, mCompatConfig, mBuildClassifier); assertThat(mPlatformCompat.listUIChanges()).asList().containsExactly( new CompatibilityChangeInfo(1L, "", -1, -1, false, false, "", false), new CompatibilityChangeInfo(2L, "change2", -1, -1, true, false, "", false), @@ -144,7 +146,7 @@ public class PlatformCompatTest { .addEnableAfterSdkChangeWithId(Build.VERSION_CODES.O, 3L) .build(); mCompatConfig.forceNonDebuggableFinalForTest(true); - mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); + mPlatformCompat = new PlatformCompat(mContext, mCompatConfig, mBuildClassifier); // Before adding overrides. assertThat(mPlatformCompat.isChangeEnabledByPackageName(1, PACKAGE_NAME, 0)).isTrue(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 87100a63e35e..77a39d8ac762 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -107,6 +107,7 @@ import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.test.MoreAsserts; // TODO(b/171932723): replace by Truth import android.util.ArraySet; +import android.util.Log; import android.util.Pair; import androidx.test.filters.SmallTest; @@ -154,6 +155,9 @@ import java.util.concurrent.TimeUnit; @SmallTest @Presubmit public class DevicePolicyManagerTest extends DpmTestBase { + + private static final String TAG = DevicePolicyManagerTest.class.getSimpleName(); + private static final List<String> OWNER_SETUP_PERMISSIONS = Arrays.asList( permission.MANAGE_DEVICE_ADMINS, permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, permission.MANAGE_USERS, permission.INTERACT_ACROSS_USERS_FULL); @@ -3875,32 +3879,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test - public void testForceUpdateUserSetupComplete_userbuild() { - mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); - mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; - - final int userId = UserHandle.USER_SYSTEM; - // GIVEN userComplete is false in SettingsProvider - setUserSetupCompleteForUser(false, userId); - - // GIVEN userComplete is true in DPM - DevicePolicyData userData = new DevicePolicyData(userId); - userData.mUserSetupComplete = true; - dpms.mUserData.put(UserHandle.USER_SYSTEM, userData); - - // GIVEN it's user build - getServices().buildMock.isDebuggable = false; - - assertThat(dpms.hasUserSetupCompleted()).isTrue(); - - dpm.forceUpdateUserSetupComplete(); - - // THEN the state in dpms is not changed - assertThat(dpms.hasUserSetupCompleted()).isTrue(); - } - - @Test - public void testForceUpdateUserSetupComplete_userDebugbuild() { + public void testForceUpdateUserSetupComplete_forcesUpdate() { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; @@ -3913,9 +3892,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { userData.mUserSetupComplete = true; dpms.mUserData.put(UserHandle.USER_SYSTEM, userData); - // GIVEN it's userdebug build - getServices().buildMock.isDebuggable = true; - assertThat(dpms.hasUserSetupCompleted()).isTrue(); dpm.forceUpdateUserSetupComplete(); @@ -7215,6 +7191,47 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertThat(dpm.isUsbDataSignalingEnabled()).isEqualTo(enabled); } + @Test + public void testGetPolicyExemptApps_noPermission() { + assertThrows(SecurityException.class, () -> dpm.getPolicyExemptApps()); + } + + @Test + public void testGetPolicyExemptApps_empty() { + grantManageDeviceAdmins(); + mockPolicyExemptApps(); + mockVendorPolicyExemptApps(); + + assertThat(dpm.getPolicyExemptApps()).isEmpty(); + } + + @Test + public void testGetPolicyExemptApps_baseOnly() { + grantManageDeviceAdmins(); + mockPolicyExemptApps("foo"); + mockVendorPolicyExemptApps(); + + assertThat(dpm.getPolicyExemptApps()).containsExactly("foo"); + } + + @Test + public void testGetPolicyExemptApps_vendorOnly() { + grantManageDeviceAdmins(); + mockPolicyExemptApps(); + mockVendorPolicyExemptApps("bar"); + + assertThat(dpm.getPolicyExemptApps()).containsExactly("bar"); + } + + @Test + public void testGetPolicyExemptApps_baseAndVendor() { + grantManageDeviceAdmins(); + mockPolicyExemptApps("4", "23", "15", "42", "8"); + mockVendorPolicyExemptApps("16", "15", "4"); + + assertThat(dpm.getPolicyExemptApps()).containsExactly("4", "8", "15", "16", "23", "42"); + } + private void setUserUnlocked(int userHandle, boolean unlocked) { when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked); } @@ -7436,4 +7453,18 @@ public class DevicePolicyManagerTest extends DpmTestBase { return new StringParceledListSlice(Arrays.asList(s)); } + private void grantManageDeviceAdmins() { + Log.d(TAG, "Granting " + permission.MANAGE_DEVICE_ADMINS); + mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); + } + + private void mockPolicyExemptApps(String... apps) { + Log.d(TAG, "Mocking R.array.policy_exempt_apps to return " + Arrays.toString(apps)); + when(mContext.resources.getStringArray(R.array.policy_exempt_apps)).thenReturn(apps); + } + + private void mockVendorPolicyExemptApps(String... apps) { + Log.d(TAG, "Mocking R.array.vendor_policy_exempt_apps to return " + Arrays.toString(apps)); + when(mContext.resources.getStringArray(R.array.vendor_policy_exempt_apps)).thenReturn(apps); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java index f94b800afbef..2fe2f40f34be 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java @@ -21,6 +21,11 @@ import static com.google.common.truth.Truth.assertThat; import android.app.admin.DeviceAdminInfo; import android.content.ComponentName; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.os.Parcel; +import android.util.TypedXmlPullParser; +import android.util.Xml; import androidx.test.InstrumentationRegistry; @@ -32,9 +37,14 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; @@ -44,18 +54,22 @@ import java.util.function.Function; public class PolicyVersionUpgraderTest { // NOTE: Only change this value if the corresponding CL also adds a test to test the upgrade // to the new version. - private static final int LATEST_TESTED_VERSION = 1; + private static final int LATEST_TESTED_VERSION = 2; + public static final String PERMISSIONS_TAG = "admin-can-grant-sensors-permissions"; + private ComponentName mFakeAdmin; private static class FakePolicyUpgraderDataProvider implements PolicyUpgraderDataProvider { int mDeviceOwnerUserId; + ComponentName mDeviceOwnerComponent = new ComponentName("", ""); boolean mIsFileBasedEncryptionEnabled; Map<Integer, ComponentName> mUserToComponent = new HashMap<>(); - Map<ComponentName, DeviceAdminInfo> mComponentToDeviceAdminInfo; + Map<ComponentName, DeviceAdminInfo> mComponentToDeviceAdminInfo = new HashMap<>(); File mDataDir; + int[] mUsers; @Override - public boolean isUserDeviceOwner(int userId) { - return userId == mDeviceOwnerUserId; + public boolean isDeviceOwner(int userId, ComponentName who) { + return userId == mDeviceOwnerUserId && mDeviceOwnerComponent.equals(who); } @Override @@ -92,6 +106,11 @@ public class PolicyVersionUpgraderTest { public Function<ComponentName, DeviceAdminInfo> getAdminInfoSupplier(int userId) { return componentName -> mComponentToDeviceAdminInfo.get(componentName); } + + @Override + public int[] getUsersForUpgrade() { + return mUsers; + } } private final Context mRealTestContext = InstrumentationRegistry.getTargetContext(); @@ -105,38 +124,69 @@ public class PolicyVersionUpgraderTest { mUpgrader = new PolicyVersionUpgrader(mProvider); mDataDir = new File(mRealTestContext.getCacheDir(), "test-data"); mDataDir.getParentFile().mkdirs(); + // Prepare provider. mProvider.mDataDir = mDataDir; + mFakeAdmin = new ComponentName( + "com.android.frameworks.servicestests", + "com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"); + ActivityInfo activityInfo = createActivityInfo(mFakeAdmin); + DeviceAdminInfo dai = createDeviceAdminInfo(activityInfo); + mProvider.mComponentToDeviceAdminInfo.put(mFakeAdmin, dai); + mProvider.mUsers = new int[] {0}; } @Test public void testSameVersionDoesNothing() throws IOException { - int[] users = new int[] {0}; writeVersionToXml(DevicePolicyManagerService.DPMS_VERSION); - preparePoliciesFile(users[0]); - String oldContents = readPoliciesFile(0); + final int userId = mProvider.mUsers[0]; + preparePoliciesFile(userId); + String oldContents = readPoliciesFile(userId); - mUpgrader.upgradePolicy(users, DevicePolicyManagerService.DPMS_VERSION); + mUpgrader.upgradePolicy(DevicePolicyManagerService.DPMS_VERSION); String newContents = readPoliciesFile(0); assertThat(newContents).isEqualTo(oldContents); } @Test - public void testUpgrade0To1RemovesPasswordMetrics() throws IOException { - int[] users = new int[] {0, 10}; + public void testUpgrade0To1RemovesPasswordMetrics() throws IOException, XmlPullParserException { + final String activePasswordTag = "active-password"; + mProvider.mUsers = new int[] {0, 10}; writeVersionToXml(0); - for (int userId : users) { + for (int userId : mProvider.mUsers) { preparePoliciesFile(userId); } + // Validate test set-up. + assertThat(isTagPresent(readPoliciesFileToStream(0), activePasswordTag)).isTrue(); + + mUpgrader.upgradePolicy(1); + + assertThat(readVersionFromXml()).isGreaterThan(1); + for (int user: mProvider.mUsers) { + assertThat(isTagPresent(readPoliciesFileToStream(user), activePasswordTag)).isFalse(); + } + } - String oldContents = readPoliciesFile(0); - assertThat(oldContents).contains("active-password"); + @Test + public void testUpgrade1To2MarksDoForPermissionControl() + throws IOException, XmlPullParserException { + final int ownerUser = 10; + mProvider.mUsers = new int[] {0, ownerUser}; + writeVersionToXml(1); + for (int userId : mProvider.mUsers) { + preparePoliciesFile(userId); + } + mProvider.mDeviceOwnerUserId = ownerUser; + mProvider.mDeviceOwnerComponent = mFakeAdmin; + mProvider.mUserToComponent.put(ownerUser, mFakeAdmin); - mUpgrader.upgradePolicy(users, 1); + mUpgrader.upgradePolicy(2); - assertThat(readVersionFromXml()).isEqualTo(1); - assertThat(readPoliciesFile(users[0])).doesNotContain("active-password"); - assertThat(readPoliciesFile(users[1])).doesNotContain("active-password"); + assertThat(readVersionFromXml()).isEqualTo(2); + assertThat(getBooleanValueTag(readPoliciesFileToStream(mProvider.mUsers[0]), + PERMISSIONS_TAG)).isFalse(); + assertThat(getBooleanValueTag(readPoliciesFileToStream(ownerUser), + PERMISSIONS_TAG)).isTrue(); } @Test @@ -169,6 +219,70 @@ public class PolicyVersionUpgraderTest { private String readPoliciesFile(int userId) throws IOException { File policiesFile = mProvider.makeDevicePoliciesJournaledFile(userId).chooseForRead(); - return new String(Files.asByteSource(policiesFile).read()); + FileReader reader = new FileReader(policiesFile); + return new String(Files.asByteSource(policiesFile).read(), Charset.defaultCharset()); + } + + private InputStream readPoliciesFileToStream(int userId) throws IOException { + File policiesFile = mProvider.makeDevicePoliciesJournaledFile(userId).chooseForRead(); + return new FileInputStream(policiesFile); + } + + private boolean getBooleanValueTag(InputStream inputXml, String tagName) + throws IOException, XmlPullParserException { + TypedXmlPullParser parser = Xml.resolvePullParser(inputXml); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tag = parser.getName(); + if (tagName.equals(tag)) { + String res = parser.getAttributeValue(null, "value"); + return Boolean.parseBoolean(res); + } + } + eventType = parser.next(); + } + + throw new IllegalStateException("Could not find " + tagName); + } + + private boolean isTagPresent(InputStream inputXml, String tagName) + throws IOException, XmlPullParserException { + TypedXmlPullParser parser = Xml.resolvePullParser(inputXml); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tag = parser.getName(); + if (tagName.equals(tag)) { + return true; + } + } + eventType = parser.next(); + } + + return false; + } + + private ActivityInfo createActivityInfo(ComponentName admin) { + ActivityInfo ai = new ActivityInfo(); + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.className = admin.getClassName(); + applicationInfo.uid = 2222; + ai.applicationInfo = applicationInfo; + ai.name = admin.getClassName(); + ai.packageName = admin.getPackageName(); + return ai; + } + + private DeviceAdminInfo createDeviceAdminInfo(ActivityInfo activityInfo) { + Parcel parcel = Parcel.obtain(); + activityInfo.writeToParcel(parcel, 0); + parcel.writeInt(0); + parcel.writeBoolean(true); + parcel.setDataPosition(0); + + return DeviceAdminInfo.CREATOR.createFromParcel(parcel); } } 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 843296e31800..dbb415c88a5f 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 @@ -68,7 +68,7 @@ public final class UpdatableFontDirTest { */ private static class FakeFontFileParser implements UpdatableFontDir.FontFileParser { @Override - public String getPostScriptName(File file) throws IOException { + public String getCanonicalFileName(File file) throws IOException { String content = FileUtils.readTextFile(file, 100, ""); return content.split(",")[0]; } @@ -160,10 +160,10 @@ public final class UpdatableFontDirTest { assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) .isEqualTo(expectedModifiedDate); dirForPreparation.update(Arrays.asList( - newFontUpdateRequest("foo,1", GOOD_SIGNATURE), - newFontUpdateRequest("bar,2", GOOD_SIGNATURE), - newFontUpdateRequest("foo,3", GOOD_SIGNATURE), - newFontUpdateRequest("bar,4", GOOD_SIGNATURE), + newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,2", GOOD_SIGNATURE), + newFontUpdateRequest("foo.ttf,3", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,4", GOOD_SIGNATURE), newAddFontFamilyRequest("<family name='foobar'>" + " <font>foo.ttf</font>" + " <font>bar.ttf</font>" @@ -214,10 +214,10 @@ public final class UpdatableFontDirTest { mConfigFile); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( - newFontUpdateRequest("foo,1", GOOD_SIGNATURE), - newFontUpdateRequest("bar,2", GOOD_SIGNATURE), - newFontUpdateRequest("foo,3", GOOD_SIGNATURE), - newFontUpdateRequest("bar,4", GOOD_SIGNATURE), + newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,2", GOOD_SIGNATURE), + newFontUpdateRequest("foo.ttf,3", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,4", GOOD_SIGNATURE), newAddFontFamilyRequest("<family name='foobar'>" + " <font>foo.ttf</font>" + " <font>bar.ttf</font>" @@ -246,10 +246,10 @@ public final class UpdatableFontDirTest { mConfigFile); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( - newFontUpdateRequest("foo,1", GOOD_SIGNATURE), - newFontUpdateRequest("bar,2", GOOD_SIGNATURE), - newFontUpdateRequest("foo,3", GOOD_SIGNATURE), - newFontUpdateRequest("bar,4", GOOD_SIGNATURE), + newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,2", GOOD_SIGNATURE), + newFontUpdateRequest("foo.ttf,3", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,4", GOOD_SIGNATURE), newAddFontFamilyRequest("<family name='foobar'>" + " <font>foo.ttf</font>" + " <font>bar.ttf</font>" @@ -279,10 +279,10 @@ public final class UpdatableFontDirTest { mConfigFile); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( - newFontUpdateRequest("foo,1", GOOD_SIGNATURE), - newFontUpdateRequest("bar,2", GOOD_SIGNATURE), - newFontUpdateRequest("foo,3", GOOD_SIGNATURE), - newFontUpdateRequest("bar,4", GOOD_SIGNATURE), + newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,2", GOOD_SIGNATURE), + newFontUpdateRequest("foo.ttf,3", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,4", GOOD_SIGNATURE), newAddFontFamilyRequest("<family name='foobar'>" + " <font>foo.ttf</font>" + " <font>bar.ttf</font>" @@ -332,14 +332,14 @@ public final class UpdatableFontDirTest { mConfigFile); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( - newFontUpdateRequest("foo,1", GOOD_SIGNATURE), + newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), newAddFontFamilyRequest("<family name='foobar'>" + " <font>foo.ttf</font>" + "</family>"))); try { dirForPreparation.update(Arrays.asList( - newFontUpdateRequest("foo,2", GOOD_SIGNATURE), - newFontUpdateRequest("bar,2", "Invalid signature"), + newFontUpdateRequest("foo.ttf,2", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,2", "Invalid signature"), newAddFontFamilyRequest("<family name='foobar'>" + " <font>foo.ttf</font>" + " <font>bar.ttf</font>" @@ -372,7 +372,7 @@ public final class UpdatableFontDirTest { mConfigFile); dir.loadFontFileMap(); - dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE))); assertThat(dir.getFontFileMap()).containsKey("test.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(1); File fontFile = dir.getFontFileMap().get("test.ttf"); @@ -390,9 +390,9 @@ public final class UpdatableFontDirTest { mConfigFile); dir.loadFontFileMap(); - dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE))); Map<String, File> mapBeforeUpgrade = dir.getFontFileMap(); - dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2", GOOD_SIGNATURE))); assertThat(dir.getFontFileMap()).containsKey("test.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(2); assertThat(mapBeforeUpgrade).containsKey("test.ttf"); @@ -409,9 +409,10 @@ public final class UpdatableFontDirTest { mConfigFile); dir.loadFontFileMap(); - dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2", GOOD_SIGNATURE))); try { - dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", + GOOD_SIGNATURE))); fail("Expect SystemFontException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); @@ -430,8 +431,8 @@ public final class UpdatableFontDirTest { mConfigFile); dir.loadFontFileMap(); - dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); - dir.update(Collections.singletonList(newFontUpdateRequest("bar,2", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("bar.ttf,2", GOOD_SIGNATURE))); assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1); assertThat(dir.getFontFileMap()).containsKey("bar.ttf"); @@ -448,8 +449,8 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); dir.update(Arrays.asList( - newFontUpdateRequest("foo,1", GOOD_SIGNATURE), - newFontUpdateRequest("bar,2", GOOD_SIGNATURE))); + newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,2", GOOD_SIGNATURE))); assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1); assertThat(dir.getFontFileMap()).containsKey("bar.ttf"); @@ -467,7 +468,8 @@ public final class UpdatableFontDirTest { try { dir.update( - Collections.singletonList(newFontUpdateRequest("test,1", "Invalid signature"))); + Collections.singletonList(newFontUpdateRequest("test.ttf,1", + "Invalid signature"))); fail("Expect SystemFontException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()) @@ -480,14 +482,15 @@ public final class UpdatableFontDirTest { public void installFontFile_olderThanPreinstalledFont() throws Exception { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); - FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1"); + FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test.ttf,1"); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, mConfigFile); dir.loadFontFileMap(); try { - dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", + GOOD_SIGNATURE))); fail("Expect SystemFontException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); @@ -500,7 +503,7 @@ public final class UpdatableFontDirTest { long expectedModifiedDate = 1234567890; FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); - FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1"); + FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test.ttf,1"); File readonlyDir = new File(mCacheDir, "readonly"); assertThat(readonlyDir.mkdir()).isTrue(); @@ -519,7 +522,8 @@ public final class UpdatableFontDirTest { try { dir.update( - Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE))); + Collections.singletonList(newFontUpdateRequest("test.ttf,2", + GOOD_SIGNATURE))); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()) .isEqualTo(FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG); @@ -539,7 +543,7 @@ public final class UpdatableFontDirTest { mUpdatableFontFilesDir, mPreinstalledFontDirs, new UpdatableFontDir.FontFileParser() { @Override - public String getPostScriptName(File file) throws IOException { + public String getCanonicalFileName(File file) throws IOException { return null; } @@ -551,7 +555,8 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); try { - dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", + GOOD_SIGNATURE))); fail("Expect SystemFontException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()) @@ -567,7 +572,7 @@ public final class UpdatableFontDirTest { mUpdatableFontFilesDir, mPreinstalledFontDirs, new UpdatableFontDir.FontFileParser() { @Override - public String getPostScriptName(File file) throws IOException { + public String getCanonicalFileName(File file) throws IOException { throw new IOException(); } @@ -579,7 +584,8 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); try { - dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", + GOOD_SIGNATURE))); fail("Expect SystemFontException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()) @@ -615,7 +621,8 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); try { - dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", + GOOD_SIGNATURE))); fail("Expect SystemFontException"); } catch (FontManagerService.SystemFontException e) { assertThat(e.getErrorCode()) @@ -633,11 +640,11 @@ public final class UpdatableFontDirTest { mConfigFile); dir.loadFontFileMap(); - dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE))); + dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE))); try { dir.update(Arrays.asList( - newFontUpdateRequest("foo,2", GOOD_SIGNATURE), - newFontUpdateRequest("bar,2", "Invalid signature"))); + newFontUpdateRequest("foo.ttf,2", GOOD_SIGNATURE), + newFontUpdateRequest("bar.ttf,2", "Invalid signature"))); fail("Batch update with invalid signature should fail"); } catch (FontManagerService.SystemFontException e) { // Expected @@ -657,7 +664,7 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); dir.update(Arrays.asList( - newFontUpdateRequest("test,1", GOOD_SIGNATURE), + newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE), newAddFontFamilyRequest("<family name='test'>" + " <font>test.ttf</font>" + "</family>"))); @@ -680,7 +687,7 @@ public final class UpdatableFontDirTest { try { dir.update(Arrays.asList( - newFontUpdateRequest("test,1", GOOD_SIGNATURE), + newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE), newAddFontFamilyRequest("<family lang='en'>" + " <font>test.ttf</font>" + "</family>"))); @@ -722,7 +729,7 @@ public final class UpdatableFontDirTest { assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace"); dir.update(Arrays.asList( - newFontUpdateRequest("test,1", GOOD_SIGNATURE), + newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE), // Updating an existing font family. newAddFontFamilyRequest("<family name='monospace'>" + " <font>test.ttf</font>" @@ -755,7 +762,7 @@ public final class UpdatableFontDirTest { assertThat(firstFontFamily.getName()).isNotEmpty(); dir.update(Arrays.asList( - newFontUpdateRequest("test,1", GOOD_SIGNATURE), + newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE), newAddFontFamilyRequest("<family name='" + firstFontFamily.getName() + "'>" + " <font>test.ttf</font>" + "</family>"))); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java index 137bd88b1489..375704ee31bf 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java @@ -16,183 +16,206 @@ package com.android.server.hdmi; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + import android.annotation.NonNull; import android.content.Context; -import android.util.Slog; - -import com.android.server.hdmi.cec.config.CecSettings; -import com.android.server.hdmi.cec.config.XmlParser; - -import org.xmlpull.v1.XmlPullParserException; +import android.content.ContextWrapper; +import android.content.res.Resources; -import java.io.ByteArrayInputStream; -import java.io.IOException; - -import javax.xml.datatype.DatatypeConfigurationException; +import com.android.internal.R; /** - * Fake class which loads default system configuration with user-configurable + * Fake class which stubs default system configuration with user-configurable * settings (useful for testing). */ final class FakeHdmiCecConfig extends HdmiCecConfig { private static final String TAG = "FakeHdmiCecConfig"; - private static final String SYSTEM_CONFIG_XML = - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + " <setting name=\"power_state_change_on_active_source_lost\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"none\" />" - + " <value string-value=\"standby_now\" />" - + " </allowed-values>" - + " <default-value string-value=\"none\" />" - + " </setting>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"hdmi_cec_version\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0x05\" />" - + " <value int-value=\"0x06\" />" - + " </allowed-values>" - + " <default-value int-value=\"0x05\" />" - + " </setting>" - + " <setting name=\"system_audio_mode_muting\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"volume_control_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"tv_wake_on_one_touch_play\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"tv_send_standby_on_sleep\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"rc_profile_tv\"" - + " value-type=\"int\"" - + " user-configurable=\"false\">" - + " <allowed-values>" - + " <value int-value=\"0x0\" />" - + " <value int-value=\"0x2\" />" - + " <value int-value=\"0x6\" />" - + " <value int-value=\"0xA\" />" - + " <value int-value=\"0xE\" />" - + " </allowed-values>" - + " <default-value int-value=\"0x0\" />" - + " </setting>" - + " <setting name=\"rc_profile_source_handles_root_menu\"" - + " value-type=\"int\"" - + " user-configurable=\"false\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"rc_profile_source_handles_setup_menu\"" - + " value-type=\"int\"" - + " user-configurable=\"false\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"rc_profile_source_handles_contents_menu\"" - + " value-type=\"int\"" - + " user-configurable=\"false\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"0\" />" - + " </setting>" - + " <setting name=\"rc_profile_source_handles_top_menu\"" - + " value-type=\"int\"" - + " user-configurable=\"false\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"0\" />" - + " </setting>" - + " <setting name=\"rc_profile_source_handles_media_context_sensitive_menu\"" - + " value-type=\"int\"" - + " user-configurable=\"false\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"0\" />" - + " </setting>" - + "</cec-settings>"; + public static Context buildContext(Context context) { + Context contextSpy = spy(new ContextWrapper(context)); + doReturn(buildResources(context)).when(contextSpy).getResources(); + return contextSpy; + } - FakeHdmiCecConfig(@NonNull Context context) { - super(context, new StorageAdapter(context), parseFromString(SYSTEM_CONFIG_XML), null); + private static Resources buildResources(Context context) { + Resources resources = spy(context.getResources()); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecHdmiCecEnabled_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecHdmiCecControlEnabled_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecHdmiCecControlEnabled_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecHdmiCecControlDisabled_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecHdmiCecControlDisabled_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecHdmiCecVersion_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecHdmiCecVersion14b_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecHdmiCecVersion14b_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecHdmiCecVersion20_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecHdmiCecVersion20_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecSendStandbyOnSleep_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecPowerControlModeTv_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecPowerControlModeTv_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecPowerControlModeBroadcast_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecPowerControlModeBroadcast_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecPowerControlModeNone_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecPowerControlModeNone_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecPowerStateChangeOnActiveSourceLost_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecPowerStateChangeOnActiveSourceLostNone_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecPowerStateChangeOnActiveSourceLostNone_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecPowerStateChangeOnActiveSourceLostStandbyNow_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecPowerStateChangeOnActiveSourceLostStandbyNow_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecSystemAudioModeMuting_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecSystemAudioModeMutingEnabled_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecSystemAudioModeMutingEnabled_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecSystemAudioModeMutingDisabled_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecSystemAudioModeMutingDisabled_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecVolumeControlMode_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecVolumeControlModeEnabled_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecVolumeControlModeEnabled_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecVolumeControlModeDisabled_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecVolumeControlModeDisabled_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecTvWakeOnOneTouchPlay_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecTvWakeOnOneTouchPlayEnabled_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecTvWakeOnOneTouchPlayEnabled_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecTvWakeOnOneTouchPlayDisabled_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecTvWakeOnOneTouchPlayDisabled_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecTvSendStandbyOnSleep_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecTvSendStandbyOnSleepEnabled_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecTvSendStandbyOnSleepEnabled_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecTvSendStandbyOnSleepDisabled_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecTvSendStandbyOnSleepDisabled_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileTv_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileTvNone_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileTvNone_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileTvOne_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecRcProfileTvOne_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileTvTwo_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecRcProfileTvTwo_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileTvThree_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecRcProfileTvThree_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileTvFour_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecRcProfileTvFour_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceRootMenu_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceRootMenuHandled_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceRootMenuHandled_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceRootMenuNotHandled_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceRootMenuNotHandled_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceSetupMenu_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceSetupMenuHandled_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceSetupMenuHandled_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceSetupMenuNotHandled_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceSetupMenuNotHandled_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceContentsMenu_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceContentsMenuHandled_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceContentsMenuHandled_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceContentsMenuNotHandled_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceContentsMenuNotHandled_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceTopMenu_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceTopMenuHandled_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceTopMenuHandled_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceTopMenuNotHandled_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceTopMenuNotHandled_default); + + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceMediaContextSensitiveMenu_userConfigurable); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuHandled_allowed); + doReturn(false).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuHandled_default); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_allowed); + doReturn(true).when(resources).getBoolean( + R.bool.config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_default); + + return resources; } - private static CecSettings parseFromString(@NonNull String configXml) { - CecSettings config = null; - try { - config = XmlParser.read( - new ByteArrayInputStream(configXml.getBytes())); - } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { - Slog.e(TAG, "Encountered an error while reading/parsing CEC config strings", e); - } - return config; + FakeHdmiCecConfig(@NonNull Context context) { + super(buildContext(context), new StorageAdapter(context)); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java index 798cf85957c0..c834510ba24c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -27,6 +28,7 @@ import static org.testng.Assert.assertThrows; import android.annotation.NonNull; import android.content.Context; +import android.content.res.Resources; import android.hardware.hdmi.HdmiControlManager; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -35,6 +37,8 @@ import android.provider.Settings.Global; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.internal.R; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -61,265 +65,118 @@ public final class HdmiCecConfigTest { @Mock private HdmiCecConfig.StorageAdapter mStorageAdapter; @Mock private HdmiCecConfig.SettingChangeListener mSettingChangeListener; + private void setBooleanResource(int resId, boolean value) { + Resources resources = mContext.getResources(); + doReturn(value).when(resources).getBoolean(resId); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getTargetContext(); - } - - @Test - public void getAllCecSettings_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThat(hdmiCecConfig.getAllSettings()).isEmpty(); - } - - @Test - public void getAllCecSettings_Empty() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); - assertThat(hdmiCecConfig.getAllSettings()).isEmpty(); + mContext = FakeHdmiCecConfig.buildContext(InstrumentationRegistry.getTargetContext()); } @Test public void getAllCecSettings_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"false\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getAllSettings()) .containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, - HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE); - } - - @Test - public void getUserCecSettings_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThat(hdmiCecConfig.getUserSettings()).isEmpty(); - } - - @Test - public void getUserCecSettings_Empty() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); - assertThat(hdmiCecConfig.getUserSettings()).isEmpty(); - } - - @Test - public void getUserCecSettings_OnlyMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE, + HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, + HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, + HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, + HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU, + HdmiControlManager + .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU); + } + + @Test + public void getUserCecSettings_BasicSanity() { + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getUserSettings()) .containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, - HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE); + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE, + HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, + HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, + HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, + HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU, + HdmiControlManager + .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU); } @Test public void getUserCecSettings_WithOverride() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"false\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>"); + setBooleanResource(R.bool.config_cecHdmiCecEnabled_userConfigurable, false); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getUserSettings()) - .containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED); - } - - @Test - public void isStringValueType_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThrows(IllegalArgumentException.class, - () -> hdmiCecConfig.isStringValueType("foo")); + .containsExactly(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE, + HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, + HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, + HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, + HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, + HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU, + HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU, + HdmiControlManager + .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU); } @Test public void isStringValueType_InvalidSetting() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.isStringValueType("foo")); } @Test public void isStringValueType_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertTrue(hdmiCecConfig.isStringValueType( HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)); } @Test - public void isIntValueType_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThrows(IllegalArgumentException.class, - () -> hdmiCecConfig.isIntValueType("foo")); - } - - @Test public void isIntValueType_InvalidSetting() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.isIntValueType("foo")); } @Test public void isIntValueType_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertTrue(hdmiCecConfig.isIntValueType( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)); } @Test - public void getAllowedStringValues_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThrows(IllegalArgumentException.class, - () -> hdmiCecConfig.getAllowedStringValues("foo")); - } - - @Test public void getAllowedStringValues_InvalidSetting() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getAllowedStringValues("foo")); } @Test public void getAllowedStringValues_InvalidValueType() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getAllowedStringValues( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)); @@ -327,21 +184,7 @@ public final class HdmiCecConfigTest { @Test public void getAllowedStringValues_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getAllowedStringValues( HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) .containsExactly(HdmiControlManager.POWER_CONTROL_MODE_TV, @@ -350,41 +193,25 @@ public final class HdmiCecConfigTest { } @Test - public void getAllowedIntValues_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThrows(IllegalArgumentException.class, - () -> hdmiCecConfig.getAllowedIntValues("foo")); + public void getAllowedStringValues_WithOverride() { + setBooleanResource(R.bool.config_cecPowerControlModeNone_allowed, false); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); + assertThat(hdmiCecConfig.getAllowedStringValues( + HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) + .containsExactly(HdmiControlManager.POWER_CONTROL_MODE_TV, + HdmiControlManager.POWER_CONTROL_MODE_BROADCAST); } @Test public void getAllowedIntValues_InvalidSetting() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getAllowedIntValues("foo")); } @Test public void getAllowedIntValues_InvalidValueType() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getAllowedIntValues( HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)); @@ -392,20 +219,7 @@ public final class HdmiCecConfigTest { @Test public void getAllowedIntValues_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getAllowedIntValues( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) .containsExactly(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED, @@ -413,62 +227,24 @@ public final class HdmiCecConfigTest { } @Test - public void getAllowedIntValues_HexValues() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0x00\" />" - + " <value int-value=\"0x01\" />" - + " </allowed-values>" - + " <default-value int-value=\"0x01\" />" - + " </setting>" - + "</cec-settings>", null); + public void getAllowedIntValues_WithOverride() { + setBooleanResource(R.bool.config_cecHdmiCecControlDisabled_allowed, false); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getAllowedIntValues( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) - .containsExactly(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED, - HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - } - - @Test - public void getDefaultStringValue_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThrows(IllegalArgumentException.class, - () -> hdmiCecConfig.getDefaultStringValue("foo")); + .containsExactly(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); } @Test public void getDefaultStringValue_InvalidSetting() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getDefaultStringValue("foo")); } @Test public void getDefaultStringValue_InvalidValueType() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getDefaultStringValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)); @@ -476,62 +252,46 @@ public final class HdmiCecConfigTest { @Test public void getDefaultStringValue_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getDefaultStringValue( HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) .isEqualTo(HdmiControlManager.POWER_CONTROL_MODE_TV); } @Test - public void getDefaultIntValue_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThrows(IllegalArgumentException.class, - () -> hdmiCecConfig.getDefaultIntValue("foo")); + public void getDefaultStringValue_WithOverride() { + setBooleanResource(R.bool.config_cecPowerControlModeTv_default, false); + setBooleanResource(R.bool.config_cecPowerControlModeBroadcast_default, true); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); + assertThat(hdmiCecConfig.getDefaultStringValue( + HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) + .isEqualTo(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST); + } + + @Test + public void getDefaultStringValue_MultipleDefaults() { + setBooleanResource(R.bool.config_cecPowerControlModeBroadcast_default, true); + assertThrows(RuntimeException.class, + () -> new HdmiCecConfig(mContext, mStorageAdapter)); + } + + @Test + public void getDefaultStringValue_NoDefault() { + setBooleanResource(R.bool.config_cecPowerControlModeTv_default, false); + assertThrows(RuntimeException.class, + () -> new HdmiCecConfig(mContext, mStorageAdapter)); } @Test public void getDefaultIntValue_InvalidSetting() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getDefaultIntValue("foo")); } @Test public void getDefaultIntValue_InvalidValueType() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getDefaultIntValue( HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)); @@ -539,81 +299,32 @@ public final class HdmiCecConfigTest { @Test public void getDefaultIntValue_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getDefaultIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) .isEqualTo(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); } @Test - public void getDefaultIntValue_HexValue() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0x00\" />" - + " <value int-value=\"0x01\" />" - + " </allowed-values>" - + " <default-value int-value=\"0x01\" />" - + " </setting>" - + "</cec-settings>", null); + public void getDefaultIntValue_WithOverride() { + setBooleanResource(R.bool.config_cecHdmiCecControlEnabled_default, false); + setBooleanResource(R.bool.config_cecHdmiCecControlDisabled_default, true); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getDefaultIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) - .isEqualTo(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - } - - @Test - public void getStringValue_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThrows(IllegalArgumentException.class, - () -> hdmiCecConfig.getStringValue("foo")); + .isEqualTo(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); } @Test public void getStringValue_InvalidSetting() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getStringValue("foo")); } @Test public void getStringValue_InvalidType() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getStringValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)); @@ -625,21 +336,7 @@ public final class HdmiCecConfigTest { Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP, HdmiControlManager.POWER_CONTROL_MODE_TV)) .thenReturn(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST); - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getStringValue( HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) .isEqualTo(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST); @@ -652,61 +349,22 @@ public final class HdmiCecConfigTest { HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE)) .thenReturn( HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW); - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"power_state_change_on_active_source_lost\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"none\" />" - + " <value string-value=\"standby_now\" />" - + " </allowed-values>" - + " <default-value string-value=\"none\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getStringValue( HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) .isEqualTo(HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW); } @Test - public void getIntValue_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThrows(IllegalArgumentException.class, - () -> hdmiCecConfig.getIntValue("foo")); - } - - @Test public void getIntValue_InvalidSetting() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getIntValue("foo")); } @Test public void getIntValue_InvalidType() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.getIntValue( HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)); @@ -718,45 +376,7 @@ public final class HdmiCecConfigTest { Global.HDMI_CONTROL_ENABLED, Integer.toString(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED))) .thenReturn(Integer.toString(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED)); - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); - assertThat(hdmiCecConfig.getIntValue( - HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) - .isEqualTo(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); - } - - @Test - public void getIntValue_GlobalSetting_HexValue() { - when(mStorageAdapter.retrieveGlobalSetting( - Global.HDMI_CONTROL_ENABLED, - Integer.toHexString(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED))) - .thenReturn(Integer.toString(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED)); - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0x0\" />" - + " <value int-value=\"0x1\" />" - + " </allowed-values>" - + " <default-value int-value=\"0x1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) .isEqualTo(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); @@ -768,61 +388,23 @@ public final class HdmiCecConfigTest { HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, Integer.toString(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_ENABLED))) .thenReturn(Integer.toString(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED)); - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"system_audio_mode_muting\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThat(hdmiCecConfig.getIntValue( HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING)) .isEqualTo(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED); } @Test - public void setStringValue_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThrows(IllegalArgumentException.class, - () -> hdmiCecConfig.setStringValue("foo", "bar")); - } - - @Test public void setStringValue_InvalidSetting() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.setStringValue("foo", "bar")); } @Test public void setStringValue_NotConfigurable() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"false\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + setBooleanResource(R.bool.config_cecSendStandbyOnSleep_userConfigurable, false); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.setStringValue( HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE, @@ -831,21 +413,7 @@ public final class HdmiCecConfigTest { @Test public void setStringValue_InvalidValue() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.setStringValue( HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE, @@ -854,21 +422,7 @@ public final class HdmiCecConfigTest { @Test public void setStringValue_GlobalSetting_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"send_standby_on_sleep\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"to_tv\" />" - + " <value string-value=\"broadcast\" />" - + " <value string-value=\"none\" />" - + " </allowed-values>" - + " <default-value string-value=\"to_tv\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); hdmiCecConfig.setStringValue(HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE, HdmiControlManager.POWER_CONTROL_MODE_BROADCAST); verify(mStorageAdapter).storeGlobalSetting( @@ -878,20 +432,7 @@ public final class HdmiCecConfigTest { @Test public void setStringValue_SharedPref_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"power_state_change_on_active_source_lost\"" - + " value-type=\"string\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value string-value=\"none\" />" - + " <value string-value=\"standby_now\" />" - + " </allowed-values>" - + " <default-value string-value=\"none\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); hdmiCecConfig.setStringValue( HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW); @@ -901,40 +442,16 @@ public final class HdmiCecConfigTest { } @Test - public void setIntValue_NoMasterXml() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, null, null); - assertThrows(IllegalArgumentException.class, - () -> hdmiCecConfig.setIntValue("foo", 0)); - } - - @Test public void setIntValue_InvalidSetting() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.setIntValue("foo", 0)); } @Test public void setIntValue_NotConfigurable() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"false\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + setBooleanResource(R.bool.config_cecHdmiCecEnabled_userConfigurable, false); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, @@ -943,20 +460,7 @@ public final class HdmiCecConfigTest { @Test public void setIntValue_InvalidValue() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); assertThrows(IllegalArgumentException.class, () -> hdmiCecConfig.setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, @@ -965,43 +469,7 @@ public final class HdmiCecConfigTest { @Test public void setIntValue_GlobalSetting_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); - hdmiCecConfig.setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, - HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); - verify(mStorageAdapter).storeGlobalSetting( - Global.HDMI_CONTROL_ENABLED, - Integer.toString(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED)); - } - - @Test - public void setIntValue_GlobalSetting_HexValue() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0x0\" />" - + " <value int-value=\"0x1\" />" - + " </allowed-values>" - + " <default-value int-value=\"0x1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); hdmiCecConfig.setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); verify(mStorageAdapter).storeGlobalSetting( @@ -1011,20 +479,7 @@ public final class HdmiCecConfigTest { @Test public void setIntValue_SharedPref_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"system_audio_mode_muting\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); hdmiCecConfig.setIntValue( HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED); @@ -1035,20 +490,7 @@ public final class HdmiCecConfigTest { @Test public void registerChangeListener_SharedPref_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"system_audio_mode_muting\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); hdmiCecConfig.registerChangeListener( HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, mSettingChangeListener); @@ -1061,20 +503,7 @@ public final class HdmiCecConfigTest { @Test public void removeChangeListener_SharedPref_BasicSanity() { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"system_audio_mode_muting\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); hdmiCecConfig.registerChangeListener( HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, mSettingChangeListener); @@ -1100,20 +529,7 @@ public final class HdmiCecConfigTest { String originalValue = Global.getString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED); try { - HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings( - mContext, mStorageAdapter, - "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<cec-settings>" - + " <setting name=\"hdmi_cec_enabled\"" - + " value-type=\"int\"" - + " user-configurable=\"true\">" - + " <allowed-values>" - + " <value int-value=\"0\" />" - + " <value int-value=\"1\" />" - + " </allowed-values>" - + " <default-value int-value=\"1\" />" - + " </setting>" - + "</cec-settings>", null); + HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter); hdmiCecConfig.registerGlobalSettingsObserver(mTestLooper.getLooper()); HdmiCecConfig.SettingChangeListener latchUpdateListener = new HdmiCecConfig.SettingChangeListener() { 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 907cf3eb1f70..c61635cbd4b6 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java @@ -136,7 +136,6 @@ public class OneTouchPlayActionTest { mPhysicalAddress = 0x2000; mNativeWrapper.setPhysicalAddress(mPhysicalAddress); mTestLooper.dispatchAll(); - mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); } private OneTouchPlayAction createOneTouchPlayAction(HdmiCecLocalDevicePlayback device, @@ -147,7 +146,47 @@ public class OneTouchPlayActionTest { } @Test + public void succeedWithUnknownTvDevice() { + HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( + mHdmiControlService); + playbackDevice.init(); + mLocalDevices.add(playbackDevice); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + + TestActionTimer actionTimer = new TestActionTimer(); + TestCallback callback = new TestCallback(); + OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback, + false); + playbackDevice.addAndStartAction(action); + mTestLooper.dispatchAll(); + + HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( + playbackDevice.mAddress, mPhysicalAddress); + HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(playbackDevice.mAddress, + ADDR_TV); + HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder + .buildGiveDevicePowerStatus(playbackDevice.mAddress, ADDR_TV); + + assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); + assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); + assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); + mNativeWrapper.clearResultMessages(); + assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); + HdmiCecMessage reportPowerStatusOn = new HdmiCecMessage( + ADDR_TV, playbackDevice.mAddress, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); + action.processCommand(reportPowerStatusOn); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn); + assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test public void succeedAfterGettingPowerStatusOn_Cec14b() { + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); @@ -187,6 +226,7 @@ public class OneTouchPlayActionTest { @Test public void succeedAfterGettingTransientPowerStatus_Cec14b() { + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); @@ -236,6 +276,7 @@ public class OneTouchPlayActionTest { @Test public void timeOut_Cec14b() { + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); @@ -276,6 +317,7 @@ public class OneTouchPlayActionTest { @Test public void succeedIfPowerStatusOn_Cec20() { + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); @@ -307,6 +349,7 @@ public class OneTouchPlayActionTest { @Test public void succeedIfPowerStatusUnknown_Cec20() { + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); @@ -348,6 +391,7 @@ public class OneTouchPlayActionTest { @Test public void succeedIfPowerStatusStandby_Cec20() { + mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV); HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback( mHdmiControlService); playbackDevice.init(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java new file mode 100644 index 000000000000..865eb7a3b56d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java @@ -0,0 +1,343 @@ +/* + * 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.hdmi; + + +import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; +import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.hardware.hdmi.HdmiPortInfo; +import android.media.AudioManager; +import android.os.Handler; +import android.os.IPowerManager; +import android.os.IThermalService; +import android.os.Looper; +import android.os.PowerManager; +import android.os.test.TestLooper; + +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 org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +/** + * Test for {@link SystemAudioAutoInitiationAction}. + */ +@SmallTest +@RunWith(JUnit4.class) +public class SystemAudioAutoInitiationActionTest { + + private Context mContextSpy; + private HdmiControlService mHdmiControlService; + private FakeNativeWrapper mNativeWrapper; + + private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv; + + private TestLooper mTestLooper = new TestLooper(); + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + private int mPhysicalAddress; + + @Mock + private IPowerManager mIPowerManagerMock; + @Mock + private IThermalService mIThermalServiceMock; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + + Looper myLooper = mTestLooper.getLooper(); + PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, + mIThermalServiceMock, new Handler(myLooper)); + when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); + when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); + when(mIPowerManagerMock.isInteractive()).thenReturn(true); + + mHdmiControlService = new HdmiControlService(mContextSpy) { + @Override + AudioManager getAudioManager() { + return new AudioManager() { + @Override + public void setWiredDeviceConnectionState( + int type, int state, String address, String name) { + // Do nothing. + } + }; + } + + @Override + void wakeUp() { + } + + @Override + boolean isPowerStandby() { + return false; + } + + @Override + protected PowerManager getPowerManager() { + return powerManager; + } + + @Override + protected void writeStringSystemProperty(String key, String value) { + // do nothing + } + }; + + mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService); + mHdmiCecLocalDeviceTv.init(); + mHdmiControlService.setIoLooper(myLooper); + mNativeWrapper = new FakeNativeWrapper(); + HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( + mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); + mHdmiControlService.setCecController(hdmiCecController); + mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + mLocalDevices.add(mHdmiCecLocalDeviceTv); + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2]; + hdmiPortInfos[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false); + hdmiPortInfos[1] = + new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true); + mNativeWrapper.setPortInfo(hdmiPortInfos); + mHdmiControlService.initService(); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mPhysicalAddress = 0x0000; + mNativeWrapper.setPhysicalAddress(mPhysicalAddress); + mTestLooper.dispatchAll(); + mPhysicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(); + mNativeWrapper.clearResultMessages(); + } + + private void setSystemAudioSetting(boolean on) { + mHdmiCecLocalDeviceTv.setSystemAudioControlFeatureEnabled(on); + } + + private void setTvHasSystemAudioChangeAction() { + mHdmiCecLocalDeviceTv.addAndStartAction( + new SystemAudioActionFromTv(mHdmiCecLocalDeviceTv, Constants.ADDR_AUDIO_SYSTEM, + true, null)); + } + + @Test + public void testReceiveSystemAudioMode_systemAudioOn() { + // Record that previous system audio mode is on. + setSystemAudioSetting(true); + + HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv, + ADDR_AUDIO_SYSTEM); + mHdmiCecLocalDeviceTv.addAndStartAction(action); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveSystemAudioModeStatus = + HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus( + mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM); + + assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus); + + HdmiCecMessage reportSystemAudioMode = HdmiCecMessageBuilder.buildReportSystemAudioMode( + ADDR_AUDIO_SYSTEM, mHdmiCecLocalDeviceTv.mAddress, true); + mHdmiControlService.handleCecCommand(reportSystemAudioMode); + mTestLooper.dispatchAll(); + + assertThat(mHdmiControlService.isSystemAudioActivated()).isTrue(); + } + + @Test + public void testReceiveSystemAudioMode_systemAudioOnAndImpossibleToChangeSystemAudio() { + // Turn on system audio. + setSystemAudioSetting(true); + // Impossible to change system audio mode while SystemAudioActionFromTv is in progress. + setTvHasSystemAudioChangeAction(); + + HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv, + ADDR_AUDIO_SYSTEM); + mHdmiCecLocalDeviceTv.addAndStartAction(action); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveSystemAudioModeStatus = + HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus( + mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM); + + assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus); + + HdmiCecMessage reportSystemAudioMode = HdmiCecMessageBuilder.buildReportSystemAudioMode( + ADDR_AUDIO_SYSTEM, mHdmiCecLocalDeviceTv.mAddress, true); + mHdmiControlService.handleCecCommand(reportSystemAudioMode); + mTestLooper.dispatchAll(); + + assertThat(mHdmiControlService.isSystemAudioActivated()).isFalse(); + } + + @Test + public void testReceiveSystemAudioMode_systemAudioOnAndResponseOff() { + // Record that previous system audio mode is on. + setSystemAudioSetting(true); + + HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv, + ADDR_AUDIO_SYSTEM); + mHdmiCecLocalDeviceTv.addAndStartAction(action); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveSystemAudioModeStatus = + HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus( + mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM); + + assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus); + + HdmiCecMessage reportSystemAudioMode = HdmiCecMessageBuilder.buildReportSystemAudioMode( + ADDR_AUDIO_SYSTEM, mHdmiCecLocalDeviceTv.mAddress, false); + mHdmiControlService.handleCecCommand(reportSystemAudioMode); + mTestLooper.dispatchAll(); + + assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isNotEmpty(); + SystemAudioActionFromTv resultingAction = mHdmiCecLocalDeviceTv.getActions( + SystemAudioActionFromTv.class).get(0); + assertThat(resultingAction.mTargetAudioStatus).isTrue(); + } + + @Test + public void testReceiveSystemAudioMode_settingOffAndResponseOn() { + // Turn off system audio. + setSystemAudioSetting(false); + + HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv, + ADDR_AUDIO_SYSTEM); + mHdmiCecLocalDeviceTv.addAndStartAction(action); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveSystemAudioModeStatus = + HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus( + mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM); + + assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus); + + HdmiCecMessage reportSystemAudioMode = HdmiCecMessageBuilder.buildReportSystemAudioMode( + ADDR_AUDIO_SYSTEM, mHdmiCecLocalDeviceTv.mAddress, true); + mHdmiControlService.handleCecCommand(reportSystemAudioMode); + mTestLooper.dispatchAll(); + + assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isNotEmpty(); + SystemAudioActionFromTv resultingAction = mHdmiCecLocalDeviceTv.getActions( + SystemAudioActionFromTv.class).get(0); + assertThat(resultingAction.mTargetAudioStatus).isFalse(); + } + + @Test + public void testReceiveSystemAudioMode_settingOffAndResponseOff() { + // Turn off system audio. + setSystemAudioSetting(false); + + HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv, + ADDR_AUDIO_SYSTEM); + mHdmiCecLocalDeviceTv.addAndStartAction(action); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveSystemAudioModeStatus = + HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus( + mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM); + + assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus); + + HdmiCecMessage reportSystemAudioMode = HdmiCecMessageBuilder.buildReportSystemAudioMode( + ADDR_AUDIO_SYSTEM, mHdmiCecLocalDeviceTv.mAddress, false); + mHdmiControlService.handleCecCommand(reportSystemAudioMode); + mTestLooper.dispatchAll(); + + assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isEmpty(); + assertThat(mHdmiControlService.isSystemAudioActivated()).isFalse(); + } + + @Test + public void testTimeout_systemAudioOn_retries() { + // Turn on system audio. + setSystemAudioSetting(true); + + HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv, + ADDR_AUDIO_SYSTEM); + mHdmiCecLocalDeviceTv.addAndStartAction(action); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveSystemAudioModeStatus = + HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus( + mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM); + + assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus); + mNativeWrapper.clearResultMessages(); + + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + // Retry sends <Give System Audio Mode Status> again + assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus); + } + + @Test + public void testTimeout_systemAudioOn_allRetriesFail() { + boolean targetStatus = true; + // Turn on system audio. + setSystemAudioSetting(targetStatus); + + HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv, + ADDR_AUDIO_SYSTEM); + mHdmiCecLocalDeviceTv.addAndStartAction(action); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveSystemAudioModeStatus = + HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus( + mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus); + + for (int i = 0; i < RETRIES_ON_TIMEOUT; i++) { + mNativeWrapper.clearResultMessages(); + + // Target device doesn't respond within timeout + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + // Retry sends <Give System Audio Mode Status> again + assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus); + } + + // Target device doesn't respond within timeouts + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isNotEmpty(); + SystemAudioActionFromTv resultingAction = mHdmiCecLocalDeviceTv.getActions( + SystemAudioActionFromTv.class).get(0); + assertThat(resultingAction.mTargetAudioStatus).isEqualTo(targetStatus); + } +} diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java index 1db5544871bf..d07831dd7929 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java @@ -11,15 +11,15 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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.inputmethod; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; +import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index 6ab48e53648c..9092ec325946 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -177,10 +177,10 @@ public class InputMethodSubtypeSwitchingControllerTest { private void assertRotationOrder(final ControllerImpl controller, final boolean onlyCurrentIme, final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) { - final int N = expectedRotationOrderOfImeSubtypeList.length; - for (int i = 0; i < N; i++) { + final int numItems = expectedRotationOrderOfImeSubtypeList.length; + for (int i = 0; i < numItems; i++) { final int currentIndex = i; - final int nextIndex = (currentIndex + 1) % N; + final int nextIndex = (currentIndex + 1) % numItems; final ImeSubtypeListItem currentItem = expectedRotationOrderOfImeSubtypeList[currentIndex]; final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex]; @@ -200,47 +200,47 @@ public class InputMethodSubtypeSwitchingControllerTest { @Test public void testControllerImpl() throws Exception { final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes(); - final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0); + final ImeSubtypeListItem disabledIme_en_us = disabledItems.get(0); final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1); final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2); final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3); final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); - final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); + final ImeSubtypeListItem latinIme_en_us = enabledItems.get(0); final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); - final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); - final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); + final ImeSubtypeListItem switchingUnawareLatinIme_en_uk = enabledItems.get(2); + final ImeSubtypeListItem switchingUnawareLatinIme_hi = enabledItems.get(3); final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); - final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); - final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); + final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(5); + final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(6); final ControllerImpl controller = ControllerImpl.createFrom( null /* currentInstance */, enabledItems); // switching-aware loop assertRotationOrder(controller, false /* onlyCurrentIme */, - latinIme_en_US, latinIme_fr, japaneseIme_ja_JP); + latinIme_en_us, latinIme_fr, japaneseIme_ja_jp); // switching-unaware loop assertRotationOrder(controller, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, - switchUnawareJapaneseIme_ja_JP); + switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_jp); // test onlyCurrentIme == true assertRotationOrder(controller, true /* onlyCurrentIme */, - latinIme_en_US, latinIme_fr); + latinIme_en_us, latinIme_fr); assertRotationOrder(controller, true /* onlyCurrentIme */, - switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); + switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi); assertNextInputMethod(controller, true /* onlyCurrentIme */, subtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, - japaneseIme_ja_JP, null); + japaneseIme_ja_jp, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, - switchUnawareJapaneseIme_ja_JP, null); + switchUnawareJapaneseIme_ja_jp, null); // Make sure that disabled IMEs are not accepted. assertNextInputMethod(controller, false /* onlyCurrentIme */, - disabledIme_en_US, null); + disabledIme_en_us, null); assertNextInputMethod(controller, false /* onlyCurrentIme */, disabledIme_hi, null); assertNextInputMethod(controller, false /* onlyCurrentIme */, @@ -248,7 +248,7 @@ public class InputMethodSubtypeSwitchingControllerTest { assertNextInputMethod(controller, false /* onlyCurrentIme */, disabledSubtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, - disabledIme_en_US, null); + disabledIme_en_us, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, disabledIme_hi, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, @@ -260,82 +260,82 @@ public class InputMethodSubtypeSwitchingControllerTest { @Test public void testControllerImplWithUserAction() throws Exception { final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); - final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); + final ImeSubtypeListItem latinIme_en_us = enabledItems.get(0); final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); - final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); + final ImeSubtypeListItem switchingUnawarelatinIme_en_uk = enabledItems.get(2); final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); - final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); - final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); + final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(5); + final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(6); final ControllerImpl controller = ControllerImpl.createFrom( null /* currentInstance */, enabledItems); // === switching-aware loop === assertRotationOrder(controller, false /* onlyCurrentIme */, - latinIme_en_US, latinIme_fr, japaneseIme_ja_JP); + latinIme_en_us, latinIme_fr, japaneseIme_ja_jp); // Then notify that a user did something for latinIme_fr. onUserAction(controller, latinIme_fr); assertRotationOrder(controller, false /* onlyCurrentIme */, - latinIme_fr, latinIme_en_US, japaneseIme_ja_JP); + latinIme_fr, latinIme_en_us, japaneseIme_ja_jp); // Then notify that a user did something for latinIme_fr again. onUserAction(controller, latinIme_fr); assertRotationOrder(controller, false /* onlyCurrentIme */, - latinIme_fr, latinIme_en_US, japaneseIme_ja_JP); + latinIme_fr, latinIme_en_us, japaneseIme_ja_jp); // Then notify that a user did something for japaneseIme_ja_JP. onUserAction(controller, latinIme_fr); assertRotationOrder(controller, false /* onlyCurrentIme */, - japaneseIme_ja_JP, latinIme_fr, latinIme_en_US); + japaneseIme_ja_jp, latinIme_fr, latinIme_en_us); // Check onlyCurrentIme == true. assertNextInputMethod(controller, true /* onlyCurrentIme */, - japaneseIme_ja_JP, null); + japaneseIme_ja_jp, null); assertRotationOrder(controller, true /* onlyCurrentIme */, - latinIme_fr, latinIme_en_US); + latinIme_fr, latinIme_en_us); assertRotationOrder(controller, true /* onlyCurrentIme */, - latinIme_en_US, latinIme_fr); + latinIme_en_us, latinIme_fr); // === switching-unaware loop === assertRotationOrder(controller, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, - switchUnawareJapaneseIme_ja_JP); + switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_jp); // User action should be ignored for switching unaware IMEs. onUserAction(controller, switchingUnawarelatinIme_hi); assertRotationOrder(controller, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, - switchUnawareJapaneseIme_ja_JP); + switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_jp); // User action should be ignored for switching unaware IMEs. - onUserAction(controller, switchUnawareJapaneseIme_ja_JP); + onUserAction(controller, switchUnawareJapaneseIme_ja_jp); assertRotationOrder(controller, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, - switchUnawareJapaneseIme_ja_JP); + switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_jp); // Check onlyCurrentIme == true. assertRotationOrder(controller, true /* onlyCurrentIme */, - switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); + switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi); assertNextInputMethod(controller, true /* onlyCurrentIme */, subtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, - switchUnawareJapaneseIme_ja_JP, null); + switchUnawareJapaneseIme_ja_jp, null); // Rotation order should be preserved when created with the same subtype list. final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes(); final ControllerImpl newController = ControllerImpl.createFrom(controller, sameEnabledItems); assertRotationOrder(newController, false /* onlyCurrentIme */, - japaneseIme_ja_JP, latinIme_fr, latinIme_en_US); + japaneseIme_ja_jp, latinIme_fr, latinIme_en_us); assertRotationOrder(newController, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, - switchUnawareJapaneseIme_ja_JP); + switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_jp); // Rotation order should be initialized when created with a different subtype list. final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList( - latinIme_en_US, latinIme_fr, switchingUnawarelatinIme_en_UK, - switchUnawareJapaneseIme_ja_JP); + latinIme_en_us, latinIme_fr, switchingUnawarelatinIme_en_uk, + switchUnawareJapaneseIme_ja_jp); final ControllerImpl anotherController = ControllerImpl.createFrom(controller, differentEnabledItems); assertRotationOrder(anotherController, false /* onlyCurrentIme */, - latinIme_en_US, latinIme_fr); + latinIme_en_us, latinIme_fr); assertRotationOrder(anotherController, false /* onlyCurrentIme */, - switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP); + switchingUnawarelatinIme_en_uk, switchUnawareJapaneseIme_ja_jp); } @Test @@ -344,27 +344,27 @@ public class InputMethodSubtypeSwitchingControllerTest { addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"), true /* supportsSwitchingToNextInputMethod*/); - final ImeSubtypeListItem item_en_US = items.get(0); + final ImeSubtypeListItem item_en_us = items.get(0); final ImeSubtypeListItem item_fr = items.get(1); final ImeSubtypeListItem item_en = items.get(2); final ImeSubtypeListItem item_enn = items.get(3); final ImeSubtypeListItem item_e = items.get(4); - final ImeSubtypeListItem item_EN_US = items.get(5); + final ImeSubtypeListItem item_en_us_allcaps = items.get(5); - assertTrue(item_en_US.mIsSystemLocale); + assertTrue(item_en_us.mIsSystemLocale); assertFalse(item_fr.mIsSystemLocale); assertFalse(item_en.mIsSystemLocale); assertFalse(item_en.mIsSystemLocale); assertFalse(item_enn.mIsSystemLocale); assertFalse(item_e.mIsSystemLocale); - assertFalse(item_EN_US.mIsSystemLocale); + assertFalse(item_en_us_allcaps.mIsSystemLocale); - assertTrue(item_en_US.mIsSystemLanguage); + assertTrue(item_en_us.mIsSystemLanguage); assertFalse(item_fr.mIsSystemLanguage); assertTrue(item_en.mIsSystemLanguage); assertFalse(item_enn.mIsSystemLocale); assertFalse(item_e.mIsSystemLocale); - assertFalse(item_EN_US.mIsSystemLocale); + assertFalse(item_en_us_allcaps.mIsSystemLocale); } @Test 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 1d914ec083fa..eebc25aab279 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java @@ -60,6 +60,7 @@ public class InputMethodUtilsTest { private static final boolean IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE = true; private static final boolean IS_ASCII_CAPABLE = true; private static final boolean IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = true; + private static final boolean CHECK_COUNTRY = true; private static final Locale LOCALE_EN = new Locale("en"); private static final Locale LOCALE_EN_US = new Locale("en", "US"); private static final Locale LOCALE_EN_GB = new Locale("en", "GB"); @@ -668,8 +669,6 @@ public class InputMethodUtilsTest { SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE); - final boolean CHECK_COUNTRY = true; - { final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); subtypes.add(nonAutoEnUS); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index b51f4df43259..91342ce925f6 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertNotNull; 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.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -109,7 +110,8 @@ public class RebootEscrowManagerTests { public interface MockableRebootEscrowInjected { int getBootCount(); - void reportMetric(boolean success); + void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, + int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete); } static class MockInjector extends RebootEscrowManager.Injector { @@ -119,6 +121,7 @@ public class RebootEscrowManagerTests { private final UserManager mUserManager; private final MockableRebootEscrowInjected mInjected; private final RebootEscrowKeyStoreManager mKeyStoreManager; + private final boolean mServerBased; MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow, @@ -128,6 +131,7 @@ public class RebootEscrowManagerTests { super(context, storage); mRebootEscrow = rebootEscrow; mServiceConnection = null; + mServerBased = false; RebootEscrowProviderHalImpl.Injector halInjector = new RebootEscrowProviderHalImpl.Injector() { @Override @@ -149,6 +153,7 @@ public class RebootEscrowManagerTests { super(context, storage); mServiceConnection = serviceConnection; mRebootEscrow = null; + mServerBased = true; RebootEscrowProviderServerBasedImpl.Injector injector = new RebootEscrowProviderServerBasedImpl.Injector(serviceConnection); mRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(storage, injector); @@ -168,6 +173,11 @@ public class RebootEscrowManagerTests { } @Override + public boolean serverBasedResumeOnReboot() { + return mServerBased; + } + + @Override public RebootEscrowProviderInterface getRebootEscrowProvider() { return mRebootEscrowProvider; } @@ -195,8 +205,11 @@ public class RebootEscrowManagerTests { } @Override - public void reportMetric(boolean success) { - mInjected.reportMetric(success); + public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, + int escrowDurationInSeconds, int vbmetaDigestStatus, + int durationSinceBootComplete) { + mInjected.reportMetric(success, errorCode, serviceType, attemptCount, + escrowDurationInSeconds, vbmetaDigestStatus, durationSinceBootComplete); } } @@ -418,7 +431,9 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); - doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), + eq(0) /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */, + anyInt(), anyInt(), anyInt()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); mService.loadRebootEscrowDataIfAvailable(null); @@ -451,7 +466,9 @@ public class RebootEscrowManagerTests { // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); - doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), + eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */, + anyInt(), anyInt(), anyInt()); when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); @@ -485,7 +502,8 @@ public class RebootEscrowManagerTests { // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); - doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), + anyInt(), anyInt(), eq(2) /* attempt count */, anyInt(), anyInt(), anyInt()); when(mServiceConnection.unwrap(any(), anyLong())) .thenThrow(new IOException()) @@ -528,7 +546,8 @@ public class RebootEscrowManagerTests { mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); - verify(mInjected, never()).reportMetric(anyBoolean()); + verify(mInjected, never()).reportMetric(anyBoolean(), anyInt(), anyInt(), anyInt(), + anyInt(), anyInt(), anyInt()); } @Test @@ -554,7 +573,8 @@ public class RebootEscrowManagerTests { when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]); mService.loadRebootEscrowDataIfAvailable(null); - verify(mInjected, never()).reportMetric(anyBoolean()); + verify(mInjected, never()).reportMetric(anyBoolean(), anyInt(), anyInt(), anyInt(), + anyInt(), anyInt(), anyInt()); } @Test @@ -588,7 +608,8 @@ public class RebootEscrowManagerTests { when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); mService.loadRebootEscrowDataIfAvailable(null); - verify(mInjected).reportMetric(eq(true)); + verify(mInjected).reportMetric(eq(true), eq(0) /* error code */, eq(1) /* HAL based */, + eq(1) /* attempt count */, anyInt(), anyInt(), anyInt()); } @Test @@ -615,7 +636,9 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); - doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), + anyInt() /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */, + anyInt(), anyInt(), anyInt()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]); mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java index dd3054f6543c..3fd2c97075ac 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java @@ -32,7 +32,6 @@ import android.app.KeyguardManager; import android.content.Context; import android.os.RemoteException; import android.security.GateKeeper; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; @@ -59,6 +58,7 @@ import java.security.UnrecoverableKeyException; import java.util.List; import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; @SmallTest @RunWith(AndroidJUnit4.class) @@ -575,7 +575,7 @@ public class PlatformKeyManagerTest { return (KeyProtection) mProtectionParameterCaptor.getValue(); } - private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { + private SecretKey generateAndroidKeyStoreKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); @@ -584,7 +584,7 @@ public class PlatformKeyManagerTest { .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); - return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); + return keyGenerator.generateKey(); } class PlatformKeyManagerTestable extends PlatformKeyManager { diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 9a52643b57f2..9f428c7cbded 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.SigningDetails; import android.content.pm.Signature; import android.content.pm.UserInfo; import android.content.pm.parsing.ParsingPackage; @@ -141,6 +142,10 @@ public class AppsFilterTest { return pkg(packageName).addReceiver(receiver); } + private static ParsingPackage pkgWithSharedLibrary(String packageName, String libName) { + return pkg(packageName).addLibraryName(libName); + } + private static ParsedActivity createActivity(String packageName, IntentFilter[] filters) { ParsedActivity activity = new ParsedActivity(); activity.setPackageName(packageName); @@ -413,6 +418,118 @@ public class AppsFilterTest { } @Test + public void testNoUsesLibrary_Filters() throws Exception { + final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, + new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null, + mMockExecutor); + + simulateAddBasicAndroid(appsFilter); + appsFilter.onSystemReady(); + + final Signature mockSignature = Mockito.mock(Signature.class); + final SigningDetails mockSigningDetails = new SigningDetails( + new Signature[]{mockSignature}, + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2); + + final PackageSetting target = simulateAddPackage(appsFilter, + pkgWithSharedLibrary("com.some.package", "com.some.shared_library"), + DUMMY_TARGET_APPID, + setting -> setting.setSigningDetails(mockSigningDetails) + .setPkgFlags(ApplicationInfo.FLAG_SYSTEM)); + final PackageSetting calling = simulateAddPackage(appsFilter, + pkg("com.some.other.package"), DUMMY_CALLING_APPID); + + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); + } + + @Test + public void testUsesLibrary_DoesntFilter() throws Exception { + final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, + new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null, + mMockExecutor); + + simulateAddBasicAndroid(appsFilter); + appsFilter.onSystemReady(); + + final Signature mockSignature = Mockito.mock(Signature.class); + final SigningDetails mockSigningDetails = new SigningDetails( + new Signature[]{mockSignature}, + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2); + + final PackageSetting target = simulateAddPackage(appsFilter, + pkgWithSharedLibrary("com.some.package", "com.some.shared_library"), + DUMMY_TARGET_APPID, + setting -> setting.setSigningDetails(mockSigningDetails) + .setPkgFlags(ApplicationInfo.FLAG_SYSTEM)); + final PackageSetting calling = simulateAddPackage(appsFilter, + pkg("com.some.other.package").addUsesLibrary("com.some.shared_library"), + DUMMY_CALLING_APPID); + + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); + } + + @Test + public void testUsesOptionalLibrary_DoesntFilter() throws Exception { + final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, + new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null, + mMockExecutor); + + simulateAddBasicAndroid(appsFilter); + appsFilter.onSystemReady(); + + final Signature mockSignature = Mockito.mock(Signature.class); + final SigningDetails mockSigningDetails = new SigningDetails( + new Signature[]{mockSignature}, + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2); + + final PackageSetting target = simulateAddPackage(appsFilter, + pkgWithSharedLibrary("com.some.package", "com.some.shared_library"), + DUMMY_TARGET_APPID, + setting -> setting.setSigningDetails(mockSigningDetails) + .setPkgFlags(ApplicationInfo.FLAG_SYSTEM)); + final PackageSetting calling = simulateAddPackage(appsFilter, + pkg("com.some.other.package").addUsesOptionalLibrary("com.some.shared_library"), + DUMMY_CALLING_APPID); + + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); + } + + @Test + public void testUsesLibrary_ShareUid_DoesntFilter() throws Exception { + final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, + new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null, + mMockExecutor); + + simulateAddBasicAndroid(appsFilter); + appsFilter.onSystemReady(); + + final Signature mockSignature = Mockito.mock(Signature.class); + final SigningDetails mockSigningDetails = new SigningDetails( + new Signature[]{mockSignature}, + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2); + + final PackageSetting target = simulateAddPackage(appsFilter, + pkgWithSharedLibrary("com.some.package", "com.some.shared_library"), + DUMMY_TARGET_APPID, + setting -> setting.setSigningDetails(mockSigningDetails) + .setPkgFlags(ApplicationInfo.FLAG_SYSTEM)); + final PackageSetting calling = simulateAddPackage(appsFilter, + pkg("com.some.other.package_a").setSharedUserId("com.some.uid"), + DUMMY_CALLING_APPID); + simulateAddPackage(appsFilter, pkg("com.some.other.package_b") + .setSharedUserId("com.some.uid").addUsesLibrary("com.some.shared_library"), + DUMMY_CALLING_APPID); + + // Although package_a doesn't use library, it should be granted visibility. It's because + // package_a shares userId with package_b, and package_b uses that shared library. + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); + } + + @Test public void testForceQueryable_SystemDoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, diff --git a/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java b/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java index 22c38c1961b8..fa2123c0d09a 100644 --- a/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java @@ -31,6 +31,7 @@ import android.content.pm.ServiceInfo; import android.os.CancellationSignal; import android.os.RemoteException; import android.rotationresolver.RotationResolverInternal; +import android.service.rotationresolver.RotationResolutionRequest; import android.view.Surface; import androidx.test.core.app.ApplicationProvider; @@ -58,6 +59,7 @@ public class RotationResolverManagerPerUserServiceTest { private Context mContext; private CancellationSignal mCancellationSignal; private RotationResolverManagerPerUserService mService; + private RotationResolutionRequest mRequest; @Before public void setUp() throws RemoteException { @@ -79,9 +81,10 @@ public class RotationResolverManagerPerUserServiceTest { mCancellationSignal = new CancellationSignal(); + mRequest = new RotationResolutionRequest("", Surface.ROTATION_0, Surface.ROTATION_0, + true, 1000L); this.mService.mCurrentRequest = new RemoteRotationResolverService.RotationRequest( - mMockCallbackInternal, Surface.ROTATION_0, Surface.ROTATION_0, "", 1000L, - mCancellationSignal); + mMockCallbackInternal, mRequest, mCancellationSignal); this.mService.getMaster().mIsServiceEnabled = true; @@ -99,8 +102,7 @@ public class RotationResolverManagerPerUserServiceTest { RotationResolverInternal.RotationResolverCallbackInternal callbackInternal = Mockito.mock(RotationResolverInternal.RotationResolverCallbackInternal.class); - mService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0, - "", 1000L, mCancellationSignal); + mService.resolveRotationLocked(callbackInternal, mRequest, mCancellationSignal); verify(callbackInternal).onSuccess(anyInt()); } @@ -110,8 +112,7 @@ public class RotationResolverManagerPerUserServiceTest { Mockito.mock(RotationResolverInternal.RotationResolverCallbackInternal.class); final CancellationSignal cancellationSignal = new CancellationSignal(); - mService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0, - "", 1000L, cancellationSignal); + mService.resolveRotationLocked(callbackInternal, mRequest, cancellationSignal); cancellationSignal.cancel(); } @@ -132,7 +133,7 @@ public class RotationResolverManagerPerUserServiceTest { @Override public void resolveRotationLocked(RotationRequest request) { - request.mCallbackInternal.onSuccess(request.mProposedRotation); + request.mCallbackInternal.onSuccess(request.mRemoteRequest.getProposedRotation()); } } } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 5f86d282406a..bbf11fd557a3 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -209,7 +209,7 @@ public class TimeDetectorServiceTest { @Test(expected = SecurityException.class) public void testSuggestExternalTime_withoutPermission() { doThrow(new SecurityException("Mock")) - .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any()); + .when(mMockContext).enforceCallingPermission(anyString(), any()); ExternalTimeSuggestion externalTimeSuggestion = createExternalTimeSuggestion(); try { diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java index 5d2755221288..00369829db56 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java @@ -46,7 +46,7 @@ public class ConfigurationInternalTest { public void test_unrestricted() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) @@ -108,7 +108,7 @@ public class ConfigurationInternalTest { public void test_restricted() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) @@ -170,7 +170,7 @@ public class ConfigurationInternalTest { public void test_autoDetectNotSupported() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionFeatureSupported(false) + .setTelephonyDetectionFeatureSupported(false) .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) @@ -232,7 +232,7 @@ public class ConfigurationInternalTest { public void test_geoDetectNotSupported() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java index bad380acf4b3..51f627ab415c 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java @@ -118,6 +118,11 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { } @Override + public MetricsTimeZoneDetectorState generateMetricsState() { + throw new UnsupportedOperationException(); + } + + @Override public void addDumpable(Dumpable dumpable) { mDumpables.add(dumpable); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/OrdinalGeneratorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/OrdinalGeneratorTest.java new file mode 100644 index 000000000000..af954d599334 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/OrdinalGeneratorTest.java @@ -0,0 +1,57 @@ +/* + * 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.timezonedetector; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +public class OrdinalGeneratorTest { + + @Test + public void testOrdinal() { + OrdinalGenerator<String> ordinalGenerator = new OrdinalGenerator<>(); + int oneOrd = ordinalGenerator.ordinal("One"); + int twoOrd = ordinalGenerator.ordinal("Two"); + assertNotEquals(oneOrd, twoOrd); + + assertEquals(oneOrd, ordinalGenerator.ordinal("One")); + assertEquals(twoOrd, ordinalGenerator.ordinal("Two")); + + int threeOrd = ordinalGenerator.ordinal("Three"); + assertNotEquals(oneOrd, threeOrd); + assertNotEquals(twoOrd, threeOrd); + } + + @Test + public void testOrdinals() { + OrdinalGenerator<String> ordinalGenerator = new OrdinalGenerator<>(); + int[] oneTwoOrds = ordinalGenerator.ordinals(Arrays.asList("One", "Two")); + int[] twoThreeOrds = ordinalGenerator.ordinals(Arrays.asList("Two", "Three")); + assertEquals(oneTwoOrds[0], ordinalGenerator.ordinal("One")); + assertEquals(oneTwoOrds[1], ordinalGenerator.ordinal("Two")); + assertEquals(twoThreeOrds[0], ordinalGenerator.ordinal("Two")); + assertEquals(twoThreeOrds[1], ordinalGenerator.ordinal("Three")); + } +} diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java index 14e0bbd6fa42..8af2c4d04fd9 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -364,7 +364,7 @@ public class TimeZoneDetectorServiceTest { // the tests. final boolean geoDetectionEnabled = autoDetectionEnabled; return new ConfigurationInternal.Builder(ARBITRARY_USER_ID) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(autoDetectionEnabled) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index f1f8b2f5e81a..f91ce87e8f08 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -90,7 +90,7 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(false) .setLocationEnabled(true) @@ -100,7 +100,7 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) @@ -110,17 +110,17 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionFeatureSupported(false) + .setTelephonyDetectionFeatureSupported(false) .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(false) .setLocationEnabled(true) .setGeoDetectionEnabled(false) .build(); - private static final ConfigurationInternal CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED = + private static final ConfigurationInternal CONFIG_INT_TELEPHONY_SUPPORTED_GEO_NOT_SUPPORTED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) @@ -130,7 +130,7 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(false) .setLocationEnabled(true) @@ -139,7 +139,7 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED = new ConfigurationInternal.Builder(USER_ID) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(true) @@ -149,7 +149,7 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED = new ConfigurationInternal.Builder(USER_ID) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(true) @@ -266,7 +266,8 @@ public class TimeZoneDetectorStrategyImplTest { @Test public void testUpdateConfiguration_autoDetectSupportedGeoNotSupported() { - Script script = new Script().initializeConfig(CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED); + Script script = new Script().initializeConfig( + CONFIG_INT_TELEPHONY_SUPPORTED_GEO_NOT_SUPPORTED); // Update the configuration with auto detection disabled. script.simulateUpdateConfiguration( @@ -274,7 +275,7 @@ public class TimeZoneDetectorStrategyImplTest { // The settings should have been changed and the StrategyListener onChange() called. ConfigurationInternal expectedConfig = - new ConfigurationInternal.Builder(CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED) + new ConfigurationInternal.Builder(CONFIG_INT_TELEPHONY_SUPPORTED_GEO_NOT_SUPPORTED) .setAutoDetectionEnabled(false) .build(); script.verifyConfigurationChangedAndReset(expectedConfig); @@ -675,6 +676,8 @@ public class TimeZoneDetectorStrategyImplTest { script.simulateManualTimeZoneSuggestion( USER_ID, createManualSuggestion("Europe/Paris"), false /* expectedResult */) .verifyTimeZoneNotChanged(); + + assertNull(mTimeZoneDetectorStrategy.getLatestManualSuggestion()); } @Test @@ -687,6 +690,8 @@ public class TimeZoneDetectorStrategyImplTest { script.simulateManualTimeZoneSuggestion( USER_ID, createManualSuggestion("Europe/Paris"), false /* expectedResult */) .verifyTimeZoneNotChanged(); + + assertNull(mTimeZoneDetectorStrategy.getLatestManualSuggestion()); } @Test @@ -700,6 +705,8 @@ public class TimeZoneDetectorStrategyImplTest { script.simulateManualTimeZoneSuggestion( USER_ID, manualSuggestion, true /* expectedResult */) .verifyTimeZoneChangedAndReset(manualSuggestion); + + assertEquals(manualSuggestion, mTimeZoneDetectorStrategy.getLatestManualSuggestion()); } @Test @@ -713,6 +720,8 @@ public class TimeZoneDetectorStrategyImplTest { script.simulateManualTimeZoneSuggestion( USER_ID, manualSuggestion, false /* expectedResult */) .verifyTimeZoneNotChanged(); + + assertNull(mTimeZoneDetectorStrategy.getLatestManualSuggestion()); } @Test @@ -726,6 +735,8 @@ public class TimeZoneDetectorStrategyImplTest { script.simulateManualTimeZoneSuggestion( USER_ID, manualSuggestion, true /* expectedResult */) .verifyTimeZoneChangedAndReset(manualSuggestion); + + assertEquals(manualSuggestion, mTimeZoneDetectorStrategy.getLatestManualSuggestion()); } @Test @@ -914,6 +925,106 @@ public class TimeZoneDetectorStrategyImplTest { assertTrue(dumpCalled.get()); } + @Test + public void testGenerateMetricsState() { + ConfigurationInternal expectedInternalConfig = CONFIG_INT_AUTO_DISABLED_GEO_DISABLED; + String expectedDeviceTimeZoneId = "InitialZoneId"; + + Script script = new Script() + .initializeConfig(expectedInternalConfig) + .initializeTimeZoneSetting(expectedDeviceTimeZoneId); + + assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId, null, null, + null, MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL); + + // Make sure the manual suggestion is recorded. + ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Zone1"); + script.simulateManualTimeZoneSuggestion(USER_ID, manualSuggestion, + true /* expectedResult */) + .verifyTimeZoneChangedAndReset(manualSuggestion); + expectedDeviceTimeZoneId = manualSuggestion.getZoneId(); + assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId, + manualSuggestion, null, null, + MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL); + + // With time zone auto detection off, telephony suggestions will be recorded, but geo + // suggestions won't out of an abundance of caution around respecting user privacy when + // geo detection is off. + TelephonyTimeZoneSuggestion telephonySuggestion = + createTelephonySuggestion(0 /* slotIndex */, MATCH_TYPE_NETWORK_COUNTRY_ONLY, + QUALITY_SINGLE_ZONE, "Zone2"); + GeolocationTimeZoneSuggestion geolocationTimeZoneSuggestion = + createGeoLocationSuggestion(Arrays.asList("Zone3", "Zone2")); + script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion) + .verifyTimeZoneNotChanged() + .simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion) + .verifyTimeZoneNotChanged(); + + assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId, + manualSuggestion, telephonySuggestion, null /* expectedGeoSuggestion */, + MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL); + + // Update the config and confirm that the config metrics state updates also. + TimeZoneConfiguration configUpdate = + createConfig(true /* autoDetection */, true /* geoDetection */); + expectedInternalConfig = new ConfigurationInternal.Builder(expectedInternalConfig) + .setAutoDetectionEnabled(true) + .setGeoDetectionEnabled(true) + .build(); + script.simulateUpdateConfiguration(USER_ID, configUpdate, true /* expectedResult */) + .verifyConfigurationChangedAndReset(expectedInternalConfig) + .verifyTimeZoneNotChanged(); + assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId, + manualSuggestion, telephonySuggestion, null /* expectedGeoSuggestion */, + MetricsTimeZoneDetectorState.DETECTION_MODE_GEO); + + // Now simulate a geo suggestion and confirm it is used and reported in the metrics too. + expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0); + script.simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion) + .verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId); + assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId, + manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion, + MetricsTimeZoneDetectorState.DETECTION_MODE_GEO); + } + + /** + * Asserts that the information returned by {@link + * TimeZoneDetectorStrategy#generateMetricsState()} matches expectations. + */ + private void assertMetricsState( + ConfigurationInternal expectedInternalConfig, + String expectedDeviceTimeZoneId, ManualTimeZoneSuggestion expectedManualSuggestion, + TelephonyTimeZoneSuggestion expectedTelephonySuggestion, + GeolocationTimeZoneSuggestion expectedGeolocationTimeZoneSuggestion, + int expectedDetectionMode) { + + MetricsTimeZoneDetectorState actualState = mTimeZoneDetectorStrategy.generateMetricsState(); + + // Check the various feature state values are what we expect. + assertFeatureStateMatchesConfig(expectedInternalConfig, actualState, expectedDetectionMode); + + OrdinalGenerator<String> tzIdOrdinalGenerator = new OrdinalGenerator<>(); + MetricsTimeZoneDetectorState expectedState = + MetricsTimeZoneDetectorState.create( + tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId, + expectedManualSuggestion, expectedTelephonySuggestion, + expectedGeolocationTimeZoneSuggestion); + // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID ordinal comparisons. + assertEquals(expectedState, actualState); + } + + private static void assertFeatureStateMatchesConfig(ConfigurationInternal config, + MetricsTimeZoneDetectorState actualState, int expectedDetectionMode) { + assertEquals(config.isTelephonyDetectionSupported(), + actualState.isTelephonyDetectionSupported()); + assertEquals(config.isGeoDetectionSupported(), actualState.isGeoDetectionSupported()); + assertEquals(config.getAutoDetectionEnabledSetting(), + actualState.getAutoDetectionEnabledSetting()); + assertEquals(config.getGeoDetectionEnabledSetting(), + actualState.getGeoDetectionEnabledSetting()); + assertEquals(expectedDetectionMode, actualState.getDetectionMode()); + } + private static ManualTimeZoneSuggestion createManualSuggestion(String zoneId) { return new ManualTimeZoneSuggestion(zoneId); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java index 4284240c72b4..5a100a297cfc 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java @@ -72,18 +72,25 @@ public class ControllerImplTest { private TestCallback mTestCallback; private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider; private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider; + private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker; @Before public void setUp() { // For simplicity, the TestThreadingDomain uses the test's main thread. To execute posted // runnables, the test must call methods on mTestThreadingDomain otherwise those runnables // will never get a chance to execute. + LocationTimeZoneProvider.ProviderMetricsLogger stubbedProviderMetricsLogger = stateEnum -> { + // Stubbed. + }; mTestThreadingDomain = new TestThreadingDomain(); mTestCallback = new TestCallback(mTestThreadingDomain); - mTestPrimaryLocationTimeZoneProvider = - new TestLocationTimeZoneProvider(mTestThreadingDomain, "primary"); - mTestSecondaryLocationTimeZoneProvider = - new TestLocationTimeZoneProvider(mTestThreadingDomain, "secondary"); + mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator(); + mTestPrimaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider( + stubbedProviderMetricsLogger, mTestThreadingDomain, "primary", + mTimeZoneAvailabilityChecker); + mTestSecondaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider( + stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary", + mTimeZoneAvailabilityChecker); } @Test @@ -1177,8 +1184,11 @@ public class ControllerImplTest { /** * Creates the instance. */ - TestLocationTimeZoneProvider(ThreadingDomain threadingDomain, String providerName) { - super(threadingDomain, providerName); + TestLocationTimeZoneProvider(ProviderMetricsLogger providerMetricsLogger, + ThreadingDomain threadingDomain, String providerName, + TimeZoneIdValidator timeZoneIdValidator) { + super(providerMetricsLogger, threadingDomain, providerName, + timeZoneIdValidator); } public void setFailDuringInitialization(boolean failInitialization) { @@ -1311,4 +1321,14 @@ public class ControllerImplTest { mTestProviderState.commitLatest(); } } + + private static final class FakeTimeZoneIdValidator + implements LocationTimeZoneProvider.TimeZoneIdValidator { + + @Override + public boolean isValid(@NonNull String timeZoneId) { + return true; + } + + } } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java index 095c868fc74c..d13a04e13406 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java @@ -32,6 +32,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static java.util.Arrays.asList; + import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; @@ -50,7 +52,10 @@ import org.junit.Test; import java.time.Duration; import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; /** @@ -62,27 +67,31 @@ public class LocationTimeZoneProviderTest { private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 123456789L; private TestThreadingDomain mTestThreadingDomain; - private TestProviderListener mProviderListener; + private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker; @Before public void setUp() { mTestThreadingDomain = new TestThreadingDomain(); mProviderListener = new TestProviderListener(); + mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator(); } @Test public void lifecycle() { String providerName = "arbitrary"; - TestLocationTimeZoneProvider provider = - new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName); + RecordingProviderMetricsLogger providerMetricsLogger = new RecordingProviderMetricsLogger(); + TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider( + providerMetricsLogger, mTestThreadingDomain, providerName, + mTimeZoneAvailabilityChecker); + mTimeZoneAvailabilityChecker.validIds("Europe/London"); // initialize() provider.initialize(mProviderListener); provider.assertOnInitializeCalled(); - ProviderState currentState = provider.getCurrentState(); - assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum); + ProviderState currentState = assertAndReturnProviderState( + provider, providerMetricsLogger, PROVIDER_STATE_STOPPED); assertNull(currentState.currentUserConfiguration); assertSame(provider, currentState.provider); mTestThreadingDomain.assertQueueEmpty(); @@ -96,9 +105,9 @@ public class LocationTimeZoneProviderTest { provider.assertOnStartCalled(arbitraryInitializationTimeout); - currentState = provider.getCurrentState(); + currentState = assertAndReturnProviderState( + provider, providerMetricsLogger, PROVIDER_STATE_STARTED_INITIALIZING); assertSame(provider, currentState.provider); - assertEquals(PROVIDER_STATE_STARTED_INITIALIZING, currentState.stateEnum); assertEquals(config, currentState.currentUserConfiguration); assertNull(currentState.event); // The initialization timeout should be queued. @@ -120,9 +129,9 @@ public class LocationTimeZoneProviderTest { TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion); provider.simulateProviderEventReceived(event); - currentState = provider.getCurrentState(); + currentState = assertAndReturnProviderState( + provider, providerMetricsLogger, PROVIDER_STATE_STARTED_CERTAIN); assertSame(provider, currentState.provider); - assertEquals(PROVIDER_STATE_STARTED_CERTAIN, currentState.stateEnum); assertEquals(event, currentState.event); assertEquals(config, currentState.currentUserConfiguration); mTestThreadingDomain.assertQueueEmpty(); @@ -132,9 +141,9 @@ public class LocationTimeZoneProviderTest { event = TimeZoneProviderEvent.createUncertainEvent(); provider.simulateProviderEventReceived(event); - currentState = provider.getCurrentState(); + currentState = assertAndReturnProviderState( + provider, providerMetricsLogger, PROVIDER_STATE_STARTED_UNCERTAIN); assertSame(provider, currentState.provider); - assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, currentState.stateEnum); assertEquals(event, currentState.event); assertEquals(config, currentState.currentUserConfiguration); mTestThreadingDomain.assertQueueEmpty(); @@ -144,7 +153,8 @@ public class LocationTimeZoneProviderTest { provider.stopUpdates(); provider.assertOnStopUpdatesCalled(); - currentState = provider.getCurrentState(); + currentState = assertAndReturnProviderState( + provider, providerMetricsLogger, PROVIDER_STATE_STOPPED); assertSame(provider, currentState.provider); assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum); assertNull(currentState.event); @@ -162,8 +172,10 @@ public class LocationTimeZoneProviderTest { @Test public void defaultHandleTestCommandImpl() { String providerName = "primary"; - TestLocationTimeZoneProvider provider = - new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName); + StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger(); + TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider( + providerMetricsLogger, mTestThreadingDomain, providerName, + mTimeZoneAvailabilityChecker); TestCommand testCommand = TestCommand.createForTests("test", new Bundle()); AtomicReference<Bundle> resultReference = new AtomicReference<>(); @@ -179,9 +191,12 @@ public class LocationTimeZoneProviderTest { @Test public void stateRecording() { String providerName = "primary"; - TestLocationTimeZoneProvider provider = - new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName); + StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger(); + TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider( + providerMetricsLogger, mTestThreadingDomain, providerName, + mTimeZoneAvailabilityChecker); provider.setStateChangeRecordingEnabled(true); + mTimeZoneAvailabilityChecker.validIds("Europe/London"); // initialize() provider.initialize(mProviderListener); @@ -218,6 +233,33 @@ public class LocationTimeZoneProviderTest { provider.assertLatestRecordedState(PROVIDER_STATE_DESTROYED); } + @Test + public void considerSuggestionWithInvalidTimeZoneIdsAsUncertain() { + String providerName = "primary"; + StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger(); + TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider( + providerMetricsLogger, mTestThreadingDomain, providerName, + mTimeZoneAvailabilityChecker); + provider.setStateChangeRecordingEnabled(true); + provider.initialize(mProviderListener); + + ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED; + Duration arbitraryInitializationTimeout = Duration.ofMinutes(5); + Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2); + provider.startUpdates(config, arbitraryInitializationTimeout, + arbitraryInitializationTimeoutFuzz); + + List<String> invalidTimeZoneIds = asList("Atlantic/Atlantis"); + TimeZoneProviderSuggestion invalidIdSuggestion = new TimeZoneProviderSuggestion.Builder() + .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .setTimeZoneIds(invalidTimeZoneIds) + .build(); + TimeZoneProviderEvent event = + TimeZoneProviderEvent.createSuggestionEvent(invalidIdSuggestion); + provider.simulateProviderEventReceived(event); + provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN); + } + /** A test stand-in for the real {@link LocationTimeZoneProviderController}'s listener. */ private static class TestProviderListener implements ProviderListener { @@ -241,6 +283,20 @@ public class LocationTimeZoneProviderTest { } } + /** + * Returns the provider's state after asserting that the current state matches what is expected. + * This also asserts that the metrics logger was informed of the state change. + */ + private static ProviderState assertAndReturnProviderState( + TestLocationTimeZoneProvider provider, + RecordingProviderMetricsLogger providerMetricsLogger, int expectedStateEnum) { + ProviderState currentState = provider.getCurrentState(); + assertEquals(expectedStateEnum, currentState.stateEnum); + providerMetricsLogger.assertChangeLoggedAndRemove(expectedStateEnum); + providerMetricsLogger.assertNoMoreLogEntries(); + return currentState; + } + private static class TestLocationTimeZoneProvider extends LocationTimeZoneProvider { private boolean mOnInitializeCalled; @@ -250,9 +306,11 @@ public class LocationTimeZoneProviderTest { private boolean mOnStopUpdatesCalled; /** Creates the instance. */ - TestLocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain, - @NonNull String providerName) { - super(threadingDomain, providerName); + TestLocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger, + @NonNull ThreadingDomain threadingDomain, + @NonNull String providerName, + @NonNull TimeZoneIdValidator timeZoneIdValidator) { + super(providerMetricsLogger, threadingDomain, providerName, timeZoneIdValidator); } @Override @@ -308,4 +366,49 @@ public class LocationTimeZoneProviderTest { recordedStates.get(recordedStates.size() - 1).stateEnum); } } + + private static final class FakeTimeZoneIdValidator + implements LocationTimeZoneProvider.TimeZoneIdValidator { + private final Set<String> mValidTimeZoneIds = new HashSet<>(); + + @Override + public boolean isValid(@NonNull String timeZoneId) { + return mValidTimeZoneIds.contains(timeZoneId); + } + + public void validIds(String... timeZoneIdss) { + mValidTimeZoneIds.addAll(asList(timeZoneIdss)); + } + } + + private static class StubbedProviderMetricsLogger implements + LocationTimeZoneProvider.ProviderMetricsLogger { + + @Override + public void onProviderStateChanged(int stateEnum) { + // Stubbed + } + } + + private static class RecordingProviderMetricsLogger implements + LocationTimeZoneProvider.ProviderMetricsLogger { + + private LinkedList<Integer> mStates = new LinkedList<>(); + + @Override + public void onProviderStateChanged(int stateEnum) { + mStates.add(stateEnum); + } + + public void assertChangeLoggedAndRemove(int expectedLoggedState) { + assertEquals("expected loggedState=" + expectedLoggedState + + " but states logged were=" + mStates, + (Integer) expectedLoggedState, mStates.peekFirst()); + mStates.removeFirst(); + } + + public void assertNoMoreLogEntries() { + assertTrue(mStates.isEmpty()); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java index 8280cdcb18c4..16ac1d602de5 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java @@ -44,7 +44,7 @@ final class TestSupport { @UserIdInt int userId, boolean geoDetectionEnabled) { return new ConfigurationInternal.Builder(userId) .setUserConfigAllowed(true) - .setAutoDetectionFeatureSupported(true) + .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java new file mode 100644 index 000000000000..5561b2c6a7aa --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java @@ -0,0 +1,54 @@ +/* + * 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.timezonedetector.location; + +import static com.google.common.truth.Truth.assertWithMessage; + +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.TimeZone; + +@Presubmit +public class ZoneInfoDbTimeZoneIdValidatorTest { + private final LocationTimeZoneProvider.TimeZoneIdValidator mTzChecker = + new ZoneInfoDbTimeZoneIdValidator(); + + @Test + public void timeZoneIdsFromZoneInfoDbAreValid() { + for (String timeZone : TimeZone.getAvailableIDs()) { + assertWithMessage("Time zone %s should be supported", timeZone) + .that(mTzChecker.isValid(timeZone)).isTrue(); + } + } + + @Test + public void nonExistingZones_areNotSupported() { + List<String> nonExistingTimeZones = Arrays.asList( + "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30" + ); + + for (String timeZone : nonExistingTimeZones) { + assertWithMessage(timeZone + " is not a valid time zone") + .that(mTzChecker.isValid(timeZone)) + .isFalse(); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java index 2a3c2c46ce4e..b54b6969e7df 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -49,6 +49,8 @@ final class FakeVibratorControllerProvider { private int mCapabilities; private int[] mSupportedEffects; private int[] mSupportedPrimitives; + private float mResonantFrequency; + private float mQFactor; private final class FakeNativeWrapper extends VibratorController.NativeWrapper { public int vibratorId; @@ -89,6 +91,14 @@ final class FakeVibratorControllerProvider { return mSupportedPrimitives; } + public float getResonantFrequency() { + return mResonantFrequency; + } + + public float getQFactor() { + return mQFactor; + } + public long perform(long effect, long strength, long vibrationId) { if (mSupportedEffects == null || Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) { @@ -198,6 +208,16 @@ final class FakeVibratorControllerProvider { mSupportedPrimitives = primitives; } + /** Set the resonant frequency of the fake vibrator hardware. */ + public void setResonantFrequency(float resonantFrequency) { + mResonantFrequency = resonantFrequency; + } + + /** Set the Q factor of the fake vibrator hardware. */ + public void setQFactor(float qFactor) { + mQFactor = qFactor; + } + /** * Return the amplitudes set by this controller, including zeroes for each time the vibrator was * turned off. diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index a28d18fb74d3..ce6639c6b4aa 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -266,6 +266,8 @@ public class VibratorManagerServiceTest { vibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL); vibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); vibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK); + vibrator.setResonantFrequency(123.f); + vibrator.setQFactor(Float.NaN); VibratorInfo info = createSystemReadyService().getVibratorInfo(1); assertNotNull(info); @@ -279,6 +281,8 @@ public class VibratorManagerServiceTest { info.isEffectSupported(VibrationEffect.EFFECT_TICK)); assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)); + assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/); + assertTrue(Float.isNaN(info.getQFactor())); } @Test diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml index 8789992280d0..799ec53a6e33 100644 --- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml +++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml @@ -17,9 +17,13 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.servicestests.apps.simpleservicetestapp"> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + <application> <service android:name=".SimpleService" android:exported="true" /> + <service android:name=".SimpleFgService" + android:exported="true" /> </application> </manifest> diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java new file mode 100644 index 000000000000..ccfc0b7f0ef1 --- /dev/null +++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java @@ -0,0 +1,113 @@ +/* + * 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.servicestests.apps.simpleservicetestapp; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.R; + +public class SimpleFgService extends Service { + private static final String TAG = SimpleFgService.class.getSimpleName(); + private static final String NOTIFICATION_CHANNEL_ID = TAG; + private static final int NOTIFICATION_ID = 1; + + private static final int MSG_INIT = 0; + private static final int MSG_DONE = 1; + private static final int MSG_START_FOREGROUND = 2; + private static final int MSG_STOP_FOREGROUND = 3; + + private static final String ACTION_FGS_STATS_TEST = + "com.android.servicestests.apps.simpleservicetestapp.ACTION_FGS_STATS_TEST"; + private static final String EXTRA_MESSENGER = "extra_messenger"; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_FOREGROUND: { + Log.i(TAG, "startForeground"); + startForeground(NOTIFICATION_ID, mNotification); + sendRemoteMessage(MSG_DONE, 0, 0, null); + } break; + case MSG_STOP_FOREGROUND: { + Log.i(TAG, "stopForeground"); + stopForeground(true); + sendRemoteMessage(MSG_DONE, 0, 0, null); + } break; + } + } + }; + private final Messenger mMessenger = new Messenger(mHandler); + + private Notification mNotification; + private Messenger mRemoteMessenger; + + @Override + public void onCreate() { + Log.i(TAG, "onCreate"); + final NotificationManager nm = getSystemService(NotificationManager.class); + nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID, + NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW)); + mNotification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID) + .setContentTitle(TAG) + .setSmallIcon(R.drawable.ic_info) + .build(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i(TAG, "onStartCommand"); + startForeground(NOTIFICATION_ID, mNotification); + if (ACTION_FGS_STATS_TEST.equals(intent.getAction())) { + mRemoteMessenger = new Messenger(intent.getExtras().getBinder(EXTRA_MESSENGER)); + sendRemoteMessage(MSG_INIT, 0, 0, mMessenger); + } + return START_NOT_STICKY; + } + + private void sendRemoteMessage(int what, int arg1, int arg2, Object obj) { + final Message msg = Message.obtain(); + msg.what = what; + msg.arg1 = arg1; + msg.arg2 = arg2; + msg.obj = obj; + try { + mRemoteMessenger.send(msg); + } catch (RemoteException e) { + } + } + + @Override + public void onDestroy() { + Log.i(TAG, "onDestroy"); + mNotification = null; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java index 5182b3b69655..b921838e0bfc 100644 --- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -243,10 +243,18 @@ public class ShortcutManagerTestUtils { final UserHandle user = getParentUser(context); List<String> roleHolders = callWithShellPermissionIdentity( () -> roleManager.getRoleHoldersAsUser(RoleManager.ROLE_HOME, user)); - if (roleHolders.size() == 1) { + int size = roleHolders.size(); + if (size == 1) { return roleHolders.get(0); } - fail("Failed to get the default launcher for user " + context.getUserId()); + + if (size > 1) { + fail("Too many launchers for user " + user.getIdentifier() + " using role " + + RoleManager.ROLE_HOME + ": " + roleHolders); + } else { + fail("No default launcher for user " + user.getIdentifier() + " using role " + + RoleManager.ROLE_HOME); + } return null; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index e510b4fbfdd5..5462f47e3a4c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -61,6 +61,7 @@ import android.media.AudioAttributes; import android.media.AudioManager; import android.net.Uri; import android.os.Handler; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.VibrationEffect; @@ -81,10 +82,12 @@ import com.android.internal.logging.InstanceIdSequenceFake; import com.android.internal.util.IntPair; import com.android.server.UiServiceTestCase; import com.android.server.lights.LogicalLight; +import com.android.server.pm.PackageManagerService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; @@ -412,12 +415,16 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { } private void verifyVibrate() { + ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class); verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher), - anyString(), any(AudioAttributes.class)); + anyString(), captor.capture()); + assertEquals(0, (captor.getValue().getAllFlags() + & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)); } private void verifyVibrate(int times) { - verify(mVibrator, times(times)).vibrate(anyInt(), anyString(), any(), anyString(), + verify(mVibrator, times(times)).vibrate(eq(Process.SYSTEM_UID), + eq(PackageManagerService.PLATFORM_PACKAGE_NAME), any(), anyString(), any(AudioAttributes.class)); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index d8e7582633de..c19f3489898d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -159,6 +159,11 @@ public class ActivityRecordTests extends WindowTestsBase { setBooted(mAtm); } + private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() { + return new TestStartingWindowOrganizer(mAtm, + mSystemServicesTestRule.getPowerManagerWrapper()); + } + @Test public void testStackCleanupOnClearingTask() { final ActivityRecord activity = createActivityWith2LevelTask(); @@ -2294,6 +2299,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testCreateRemoveStartingWindow() { + registerTestStartingWindowOrganizer(); final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); activity.addStartingWindow(mPackageName, android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, @@ -2307,6 +2313,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testAddRemoveRace() { + registerTestStartingWindowOrganizer(); // There was once a race condition between adding and removing starting windows final ActivityRecord appToken = new ActivityBuilder(mAtm).setCreateTask(true).build(); for (int i = 0; i < 1000; i++) { @@ -2321,6 +2328,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testTransferStartingWindow() { + registerTestStartingWindowOrganizer(); final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build(); final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build(); activity1.addStartingWindow(mPackageName, @@ -2337,9 +2345,10 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testTransferStartingWindowWhileCreating() { + final TestStartingWindowOrganizer organizer = registerTestStartingWindowOrganizer(); final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build(); final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build(); - ((TestWindowManagerPolicy) activity1.mWmService.mPolicy).setRunnableWhenAddingSplashScreen( + organizer.setRunnableWhenAddingSplashScreen( () -> { // Surprise, ...! Transfer window in the middle of the creation flow. activity2.addStartingWindow(mPackageName, @@ -2357,6 +2366,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testTransferStartingWindowCanAnimate() { + registerTestStartingWindowOrganizer(); final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build(); final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build(); activity1.addStartingWindow(mPackageName, @@ -2380,6 +2390,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testTransferStartingWindowFromFinishingActivity() { + registerTestStartingWindowOrganizer(); final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); final Task task = activity.getTask(); activity.addStartingWindow(mPackageName, android.R.style.Theme, null /* compatInfo */, @@ -2423,6 +2434,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testTransferStartingWindowSetFixedRotation() { + registerTestStartingWindowOrganizer(); final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); final Task task = activity.getTask(); final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build(); @@ -2454,6 +2466,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testTryTransferStartingWindowFromHiddenAboveToken() { + registerTestStartingWindowOrganizer(); // Add two tasks on top of each other. final ActivityRecord activityTop = new ActivityBuilder(mAtm).setCreateTask(true).build(); final ActivityRecord activityBottom = new ActivityBuilder(mAtm).build(); 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 8703c3103607..7f9e7da99579 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -154,7 +154,7 @@ public class DragDropControllerTests extends WindowTestsBase { mWindow = createDropTargetWindow("Drag test window", 0); doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0); when(mWm.mInputManager.transferTouchFocus(any(InputChannel.class), - any(InputChannel.class))).thenReturn(true); + any(InputChannel.class), any(boolean.class))).thenReturn(true); mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); } @@ -370,7 +370,7 @@ public class DragDropControllerTests extends WindowTestsBase { .build(); assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(), - new InputChannel())); + new InputChannel(), true /* isDragDrop */)); mToken = mTarget.performDrag(0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0, data); assertNotNull(mToken); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index 983063125ce9..c483ae9fa4c5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -141,7 +141,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { assertNull(mProvider.getControlTarget()); // We can have the control and the control target after seamless rotation. - mProvider.finishSeamlessRotation(false /* timeout */); + mProvider.finishSeamlessRotation(); mProvider.updateControlForTarget(target, false /* force */); assertNotNull(mProvider.getControl(target)); assertNotNull(mProvider.getControlTarget()); 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 c6be987802b5..7a4ad7410163 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; @@ -49,6 +52,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -511,6 +515,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken; final SurfaceControl.Transaction transaction = navToken.getPendingTransaction(); + verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( + mDefaultDisplay.mDisplayId, false); verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl()); final WindowContainer parent = navToken.getParent(); @@ -518,6 +524,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mDefaultDisplay.getDisplayPolicy().getNavBarFadeAnimationController(); mController.cleanupAnimation(REORDER_MOVE_TO_TOP); + verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( + mDefaultDisplay.mDisplayId, true); verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); verify(navBarFadeAnimationController).fadeWindowToken(true); } @@ -532,6 +540,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken; final SurfaceControl.Transaction transaction = navToken.getPendingTransaction(); + verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( + mDefaultDisplay.mDisplayId, false); verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl()); final WindowContainer parent = navToken.getParent(); @@ -539,6 +549,51 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mDefaultDisplay.getDisplayPolicy().getNavBarFadeAnimationController(); mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); + verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( + mDefaultDisplay.mDisplayId, true); + verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); + verify(navBarFadeAnimationController, never()).fadeWindowToken(anyBoolean()); + } + + @Test + public void testNotAttachNavigationBar_controlledByFixedRotationAnimation() { + setupForShouldAttachNavBarDuringTransition(); + FixedRotationAnimationController mockController = + mock(FixedRotationAnimationController.class); + doReturn(mockController).when(mDefaultDisplay).getFixedRotationAnimationController(); + final ActivityRecord homeActivity = createHomeActivity(); + initializeRecentsAnimationController(mController, homeActivity); + assertFalse(mController.isNavigationBarAttachedToApp()); + } + + @Test + public void testAttachNavBarInSplitScreenMode() { + setupForShouldAttachNavBarDuringTransition(); + final ActivityRecord primary = createActivityRecordWithParentTask(mDefaultDisplay, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD); + final ActivityRecord secondary = createActivityRecordWithParentTask(mDefaultDisplay, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD); + final ActivityRecord homeActivity = createHomeActivity(); + homeActivity.setVisibility(true); + initializeRecentsAnimationController(mController, homeActivity); + + WindowState navWindow = mController.getNavigationBarWindow(); + final WindowToken navToken = navWindow.mToken; + final SurfaceControl.Transaction transaction = navToken.getPendingTransaction(); + + verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( + mDefaultDisplay.mDisplayId, false); + verify(navWindow).setSurfaceTranslationY(-secondary.getBounds().top); + verify(transaction).reparent(navToken.getSurfaceControl(), secondary.getSurfaceControl()); + reset(navWindow); + + mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); + final WindowContainer parent = navToken.getParent(); + final NavBarFadeAnimationController navBarFadeAnimationController = + mDefaultDisplay.getDisplayPolicy().getNavBarFadeAnimationController(); + verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( + mDefaultDisplay.mDisplayId, true); + verify(navWindow).setSurfaceTranslationY(0); verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); verify(navBarFadeAnimationController, never()).fadeWindowToken(anyBoolean()); } @@ -600,9 +655,10 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { private void setupForShouldAttachNavBarDuringTransition() { mController.mShouldAttachNavBarToAppDuringTransition = true; - final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar"); + final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar")); mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs); mWm.setRecentsAnimationController(mController); + doReturn(navBar).when(mController).getNavigationBarWindow(); final NavBarFadeAnimationController mockNavBarFadeAnimationController = mock(NavBarFadeAnimationController.class); final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy()); diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 2fdd63ed93d5..c98e013479f4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -36,6 +36,8 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMAT import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -208,18 +210,20 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } @Test - public void testZeroAnimations() { + public void testZeroAnimations() throws Exception { mController.goodToGo(TRANSIT_OLD_NONE); - verifyNoMoreInteractionsExceptAsBinder(mMockRunner); + verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any()); + verify(mMockRunner).onAnimationCancelled(); } @Test - public void testNotReallyStarted() { + public void testNotReallyStarted() throws Exception { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); mController.createRemoteAnimationRecord(win.mActivityRecord, new Point(50, 100), null, new Rect(50, 100, 150, 150), null); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - verifyNoMoreInteractionsExceptAsBinder(mMockRunner); + verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any()); + verify(mMockRunner).onAnimationCancelled(); } @Test @@ -250,7 +254,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } @Test - public void testRemovedBeforeStarted() { + public void testRemovedBeforeStarted() throws Exception { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord, new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter; @@ -258,7 +262,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mFinishedCallback); win.mActivityRecord.removeImmediately(); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - verifyNoMoreInteractionsExceptAsBinder(mMockRunner); + verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any()); + verify(mMockRunner).onAnimationCancelled(); verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), eq(adapter)); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java index 75226b7e66f7..8bc4cedf6fce 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java @@ -34,7 +34,6 @@ import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; import android.view.InputChannel; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.Before; @@ -63,7 +62,8 @@ public class TaskPositioningControllerTests extends WindowTestsBase { when(mWm.mInputManager.transferTouchFocus( any(InputChannel.class), - any(InputChannel.class))).thenReturn(true); + any(InputChannel.class), + any(boolean.class))).thenReturn(true); mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window"); mWindow.getTask().setResizeMode(RESIZE_MODE_RESIZEABLE); 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 ecb8b607a106..dca6b089d66b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -26,6 +26,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 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.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.google.common.truth.Truth.assertThat; @@ -277,4 +278,15 @@ public class TaskTests extends WindowTestsBase { // Orientation request from standard activity in multi window will not be handled. assertFalse(leafTask2.handlesOrientationChangeFromDescendant()); } + + @Test + public void testAlwaysOnTop() { + final Task task = createTaskStackOnDisplay(mDisplayContent); + task.setAlwaysOnTop(true); + task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + assertTrue(task.isAlwaysOnTop()); + + task.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */); + assertFalse(task.isAlwaysOnTop()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 86d8eee878fd..7822a8514a13 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -119,7 +119,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { mRunnableWhenAddingSplashScreen.run(); mRunnableWhenAddingSplashScreen = null; } - return () -> { + return (a) -> { synchronized (wm.mGlobalLock) { activity.removeChild(window); activity.mStartingWindow = null; 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 2c2c09a5750a..01c503e01326 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -547,7 +547,8 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Override - public void removeStartingWindow(int taskId) { } + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { } @Override public void copySplashScreenView(int taskId) { } @@ -614,7 +615,8 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Override - public void removeStartingWindow(int taskId) { } + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { } @Override public void copySplashScreenView(int taskId) { } @Override @@ -688,7 +690,8 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Override - public void removeStartingWindow(int taskId) { } + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { } @Override public void copySplashScreenView(int taskId) { } @Override @@ -832,7 +835,8 @@ public class WindowOrganizerTests extends WindowTestsBase { @Override public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override - public void removeStartingWindow(int taskId) { } + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { } @Override public void copySplashScreenView(int taskId) { } @Override 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 51aec65f7285..5b5b1da327bd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -521,7 +521,7 @@ public class WindowStateTests extends WindowTestsBase { matrix.mapPoints(curSurfacePos); verify(t).setPosition(eq(app.mSurfaceControl), eq(curSurfacePos[0]), eq(curSurfacePos[1])); - app.finishSeamlessRotation(false /* timeout */); + app.finishSeamlessRotation(t); assertFalse(app.mSeamlesslyRotated); assertNull(app.mPendingSeamlessRotate); 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 827ff6c18a68..4a7784cf1b36 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -36,6 +36,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; @@ -50,6 +51,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat 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.StartingSurfaceController.DEBUG_ENABLE_SHELL_DRAWER; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN; @@ -67,6 +69,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Build; import android.os.Bundle; @@ -74,6 +77,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; +import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import android.view.IDisplayWindowInsetsController; @@ -100,6 +104,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.HashMap; /** Common base class for window manager unit test classes. */ class WindowTestsBase extends SystemServiceTestsBase { @@ -1123,6 +1128,88 @@ class WindowTestsBase extends SystemServiceTestsBase { } } + static class TestStartingWindowOrganizer extends ITaskOrganizer.Stub { + private final ActivityTaskManagerService mAtm; + private final WindowManagerService mWMService; + private final WindowState.PowerManagerWrapper mPowerManagerWrapper; + + private Runnable mRunnableWhenAddingSplashScreen; + private final SparseArray<IBinder> mTaskAppMap = new SparseArray<>(); + private final HashMap<IBinder, WindowState> mAppWindowMap = new HashMap<>(); + + TestStartingWindowOrganizer(ActivityTaskManagerService service, + WindowState.PowerManagerWrapper powerManagerWrapper) { + mAtm = service; + mWMService = mAtm.mWindowManager; + mPowerManagerWrapper = powerManagerWrapper; + if (DEBUG_ENABLE_SHELL_DRAWER) { + mAtm.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer(Runnable::run); + mAtm.mTaskOrganizerController.registerTaskOrganizer(this); + } + } + + void setRunnableWhenAddingSplashScreen(Runnable r) { + if (DEBUG_ENABLE_SHELL_DRAWER) { + mRunnableWhenAddingSplashScreen = r; + } else { + ((TestWindowManagerPolicy) mWMService.mPolicy).setRunnableWhenAddingSplashScreen(r); + } + } + + @Override + public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { + synchronized (mWMService.mGlobalLock) { + final ActivityRecord activity = mWMService.mRoot.getActivityRecord( + appToken); + IWindow iWindow = mock(IWindow.class); + doReturn(mock(IBinder.class)).when(iWindow).asBinder(); + final WindowState window = WindowTestsBase.createWindow(null, + TYPE_APPLICATION_STARTING, activity, + "Starting window", 0 /* ownerId */, 0 /* userId*/, + false /* internalWindows */, mWMService, mock(Session.class), + iWindow, + mPowerManagerWrapper); + activity.mStartingWindow = window; + mAppWindowMap.put(appToken, window); + mTaskAppMap.put(info.taskInfo.taskId, appToken); + } + if (mRunnableWhenAddingSplashScreen != null) { + mRunnableWhenAddingSplashScreen.run(); + mRunnableWhenAddingSplashScreen = null; + } + } + @Override + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { + synchronized (mWMService.mGlobalLock) { + final IBinder appToken = mTaskAppMap.get(taskId); + if (appToken != null) { + mTaskAppMap.remove(taskId); + final ActivityRecord activity = mWMService.mRoot.getActivityRecord( + appToken); + WindowState win = mAppWindowMap.remove(appToken); + activity.removeChild(win); + activity.mStartingWindow = null; + } + } + } + @Override + public void copySplashScreenView(int taskId) { + } + @Override + public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) { + } + @Override + public void onTaskVanished(ActivityManager.RunningTaskInfo info) { + } + @Override + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { + } + @Override + public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { + } + } + static class TestSplitOrganizer extends ITaskOrganizer.Stub { final ActivityTaskManagerService mService; Task mPrimary; @@ -1161,7 +1248,8 @@ class WindowTestsBase extends SystemServiceTestsBase { public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override - public void removeStartingWindow(int taskId) { + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { } @Override public void copySplashScreenView(int taskId) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index c82ba995f12e..d967891fdb76 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -470,7 +470,7 @@ public class ZOrderingTests extends WindowTestsBase { mWm, mockRunner, null, displayId); spyOn(controller); controller.mShouldAttachNavBarToAppDuringTransition = true; - doReturn(mNavBarWindow.mToken).when(controller).getNavigationBarWindowToken(); + doReturn(mNavBarWindow).when(controller).getNavigationBarWindow(); mWm.setRecentsAnimationController(controller); // set ime visible diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index ed71d17b0dde..e089995a7b1c 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -17,6 +17,7 @@ package com.android.server.voiceinteraction; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -25,8 +26,10 @@ import android.hardware.soundtrigger.SoundTrigger; import android.media.AudioAttributes; import android.media.AudioRecord; import android.media.MediaRecorder; +import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.SharedMemory; import android.service.voice.AlwaysOnHotwordDetector; import android.service.voice.HotwordDetectionService; import android.service.voice.IDspHotwordDetectionCallback; @@ -74,7 +77,8 @@ final class HotwordDetectionConnection { boolean mBound; HotwordDetectionConnection(Object lock, Context context, ComponentName serviceName, - int userId, boolean bindInstantServiceAllowed) { + int userId, boolean bindInstantServiceAllowed, @Nullable Bundle options, + @Nullable SharedMemory sharedMemory) { mLock = lock; mContext = context; mDetectionComponentName = serviceName; @@ -90,6 +94,14 @@ final class HotwordDetectionConnection { boolean connected) { synchronized (mLock) { mBound = connected; + if (connected) { + try { + service.setConfig(options, sharedMemory); + } catch (RemoteException e) { + // TODO: (b/181842909) Report an error to voice interactor + Slog.w(TAG, "Failed to setConfig for HotwordDetectionService", e); + } + } } } @@ -117,6 +129,11 @@ final class HotwordDetectionConnection { } } + void setConfigLocked(Bundle options, SharedMemory sharedMemory) { + mRemoteHotwordDetectionService.run( + service -> service.setConfig(options, sharedMemory)); + } + private void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent, IHotwordRecognitionStatusCallback externalCallback) { if (DEBUG) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index dce63ebb0889..2626bfdc3d19 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -60,6 +60,7 @@ import android.os.RemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.SharedMemory; import android.os.ShellCallback; import android.os.Trace; import android.os.UserHandle; @@ -982,23 +983,46 @@ public class VoiceInteractionManagerService extends SystemService { } @Override - public int setHotwordDetectionConfig(Bundle options) { + public void setHotwordDetectionServiceConfig(@Nullable Bundle options, + @Nullable SharedMemory sharedMemory) { synchronized (this) { enforceIsCurrentVoiceInteractionService(); if (mImpl == null) { Slog.w(TAG, - "setHotwordDetectionConfig without running voice interaction service"); - return VoiceInteractionService.HOTWORD_CONFIG_FAILURE; + "setHotwordDetectionServiceConfig without running voice" + + " interaction service"); + return; + } + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.setHotwordDetectionServiceConfigLocked(options, sharedMemory); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public void shutdownHotwordDetectionService() { + synchronized (this) { + enforceIsCurrentVoiceInteractionService(); + + if (mImpl == null) { + Slog.w(TAG, + "shutdownHotwordDetectionService without running voice" + + " interaction service"); + return; } final long caller = Binder.clearCallingIdentity(); try { - return mImpl.setHotwordDetectionConfigLocked(options); + mImpl.shutdownHotwordDetectionServiceLocked(); } finally { Binder.restoreCallingIdentity(caller); } } } + //----------------- Model management APIs --------------------------------// @Override diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 04dea3f7a2c6..58616104755d 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -43,11 +43,13 @@ import android.os.IBinder; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SharedMemory; import android.os.UserHandle; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionServiceInfo; +import android.system.OsConstants; import android.util.PrintWriterPrinter; import android.util.Slog; import android.view.IWindowManager; @@ -389,25 +391,48 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return mInfo.getSupportsLocalInteraction(); } - public int setHotwordDetectionConfigLocked(Bundle options) { + public void setHotwordDetectionServiceConfigLocked(@Nullable Bundle options, + @Nullable SharedMemory sharedMemory) { if (DEBUG) { - Slog.d(TAG, "setHotwordDetectionConfigLocked"); + Slog.d(TAG, "setHotwordDetectionServiceConfigLocked"); } if (mHotwordDetectionComponentName == null) { - Slog.e(TAG, "Calling setHotwordDetectionConfigLocked, but hotword detection service" - + " name not found"); - return VoiceInteractionService.HOTWORD_CONFIG_FAILURE; + Slog.w(TAG, "Hotword detection service name not found"); + throw new IllegalStateException("Hotword detection service name not found"); } if (!isIsolatedProcessLocked(mHotwordDetectionComponentName)) { - return VoiceInteractionService.HOTWORD_CONFIG_FAILURE; + Slog.w(TAG, "Hotword detection service not in isolated process"); + throw new IllegalStateException("Hotword detection service not in isolated process"); } // TODO : Need to check related permissions for hotword detection service // TODO : Sanitize for bundle - mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext, - mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false); + if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) { + Slog.w(TAG, "Can't set sharedMemory to be read-only"); + throw new IllegalStateException("Can't set sharedMemory to be read-only"); + } + + if (mHotwordDetectionConnection == null) { + mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext, + mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false, + options, sharedMemory); + } else { + mHotwordDetectionConnection.setConfigLocked(options, sharedMemory); + } + } + + public void shutdownHotwordDetectionServiceLocked() { + if (DEBUG) { + Slog.d(TAG, "shutdownHotwordDetectionServiceLocked"); + } + + if (mHotwordDetectionConnection == null) { + Slog.w(TAG, "shutdown, but no hotword detection connection"); + return; + } - return VoiceInteractionService.HOTWORD_CONFIG_SUCCESS; + mHotwordDetectionConnection.cancelLocked(); + mHotwordDetectionConnection = null; } public IRecognitionStatusCallback createSoundTriggerCallbackLocked( diff --git a/telecomm/java/android/telecom/CallDiagnosticService.java b/telecomm/java/android/telecom/CallDiagnosticService.java index 201c5db74e16..809f2bc1bb7d 100644 --- a/telecomm/java/android/telecom/CallDiagnosticService.java +++ b/telecomm/java/android/telecom/CallDiagnosticService.java @@ -19,9 +19,12 @@ package android.telecom; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; +import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; @@ -30,6 +33,7 @@ import com.android.internal.telecom.ICallDiagnosticService; import com.android.internal.telecom.ICallDiagnosticServiceAdapter; import java.util.Map; +import java.util.concurrent.Executor; /** * The platform supports a single OEM provided {@link CallDiagnosticService}, as defined by the @@ -51,6 +55,11 @@ import java.util.Map; * </service> * } * </pre> + * <p> + * <h2>Threading Model</h2> + * By default, all incoming IPC from Telecom in this service and in the {@link DiagnosticCall} + * instances will take place on the main thread. You can override {@link #getExecutor()} in your + * implementation to provide your own {@link Executor}. * @hide */ @SystemApi @@ -83,7 +92,7 @@ public abstract class CallDiagnosticService extends Service { @Override public void updateCallAudioState(CallAudioState callAudioState) throws RemoteException { - onCallAudioStateChanged(callAudioState); + getExecutor().execute(() -> onCallAudioStateChanged(callAudioState)); } @Override @@ -133,8 +142,18 @@ public abstract class CallDiagnosticService extends Service { */ private final Map<String, Call.Details> mCallByTelecomCallId = new ArrayMap<>(); private final Map<String, DiagnosticCall> mDiagnosticCallByTelecomCallId = new ArrayMap<>(); + private final Object mLock = new Object(); private ICallDiagnosticServiceAdapter mAdapter; + /** + * Handles binding to the {@link CallDiagnosticService}. + * + * @param intent The Intent that was used to bind to this service, + * as given to {@link android.content.Context#bindService + * Context.bindService}. Note that any extras that were included with + * the Intent at that point will <em>not</em> be seen here. + * @return + */ @Nullable @Override public IBinder onBind(@NonNull Intent intent) { @@ -143,11 +162,29 @@ public abstract class CallDiagnosticService extends Service { } /** + * Returns the {@link Executor} to use for incoming IPS from Telecom into your service + * implementation. + * <p> + * Override this method in your {@link CallDiagnosticService} implementation to provide the + * executor you want to use for incoming IPC. + * + * @return the {@link Executor} to use for incoming IPC from Telecom to + * {@link CallDiagnosticService} and {@link DiagnosticCall}. + */ + @SuppressLint("OnNameExpected") + @NonNull public Executor getExecutor() { + return new HandlerExecutor(Handler.createAsync(getMainLooper())); + } + + /** * Telecom calls this method on the {@link CallDiagnosticService} with details about a new call * which was added to Telecom. * <p> * The {@link CallDiagnosticService} returns an implementation of {@link DiagnosticCall} to be * used for the lifespan of this call. + * <p> + * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see + * {@link CallDiagnosticService#getExecutor()} for more information. * * @param call The details of the new call. * @return An instance of {@link DiagnosticCall} which the {@link CallDiagnosticService} @@ -160,6 +197,10 @@ public abstract class CallDiagnosticService extends Service { /** * Telecom calls this method when a previous created {@link DiagnosticCall} is no longer needed. * This happens when Telecom is no longer tracking the call in question. + * <p> + * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see + * {@link CallDiagnosticService#getExecutor()} for more information. + * * @param call The diagnostic call which is no longer tracked by Telecom. */ public abstract void onRemoveDiagnosticCall(@NonNull DiagnosticCall call); @@ -169,6 +210,9 @@ public abstract class CallDiagnosticService extends Service { * changes. * <p> * Audio state is common to all calls. + * <p> + * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see + * {@link CallDiagnosticService#getExecutor()} for more information. * * @param audioState The new audio state. */ @@ -178,6 +222,10 @@ public abstract class CallDiagnosticService extends Service { /** * Telecom calls this method when a {@link BluetoothCallQualityReport} is received from the * bluetooth stack. + * <p> + * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see + * {@link CallDiagnosticService#getExecutor()} for more information. + * * @param qualityReport the {@link BluetoothCallQualityReport}. */ public abstract void onBluetoothCallQualityReportReceived( @@ -199,15 +247,22 @@ public abstract class CallDiagnosticService extends Service { String telecomCallId = parcelableCall.getId(); Log.i(this, "handleCallAdded: callId=%s - added", telecomCallId); Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall); - mCallByTelecomCallId.put(telecomCallId, newCallDetails); - - DiagnosticCall diagnosticCall = onInitializeDiagnosticCall(newCallDetails); - if (diagnosticCall == null) { - throw new IllegalArgumentException("A valid DiagnosticCall instance was not provided."); + synchronized (mLock) { + mCallByTelecomCallId.put(telecomCallId, newCallDetails); } - diagnosticCall.setListener(mDiagnosticCallListener); - diagnosticCall.setCallId(telecomCallId); - mDiagnosticCallByTelecomCallId.put(telecomCallId, diagnosticCall); + + getExecutor().execute(() -> { + DiagnosticCall diagnosticCall = onInitializeDiagnosticCall(newCallDetails); + if (diagnosticCall == null) { + throw new IllegalArgumentException( + "A valid DiagnosticCall instance was not provided."); + } + synchronized (mLock) { + diagnosticCall.setListener(mDiagnosticCallListener); + diagnosticCall.setCallId(telecomCallId); + mDiagnosticCallByTelecomCallId.put(telecomCallId, diagnosticCall); + } + }); } /** @@ -220,10 +275,12 @@ public abstract class CallDiagnosticService extends Service { String telecomCallId = parcelableCall.getId(); Log.i(this, "handleCallUpdated: callId=%s - updated", telecomCallId); Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall); - - DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(telecomCallId); - mCallByTelecomCallId.put(telecomCallId, newCallDetails); - diagnosticCall.handleCallUpdated(newCallDetails); + DiagnosticCall diagnosticCall; + synchronized (mLock) { + diagnosticCall = mDiagnosticCallByTelecomCallId.get(telecomCallId); + mCallByTelecomCallId.put(telecomCallId, newCallDetails); + } + getExecutor().execute(() -> diagnosticCall.handleCallUpdated(newCallDetails)); } /** @@ -236,10 +293,19 @@ public abstract class CallDiagnosticService extends Service { if (mCallByTelecomCallId.containsKey(telecomCallId)) { mCallByTelecomCallId.remove(telecomCallId); } - if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) { - DiagnosticCall call = mDiagnosticCallByTelecomCallId.remove(telecomCallId); - // Inform the service of the removed call. - onRemoveDiagnosticCall(call); + + DiagnosticCall diagnosticCall; + synchronized (mLock) { + if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) { + diagnosticCall = mDiagnosticCallByTelecomCallId.remove(telecomCallId); + } else { + diagnosticCall = null; + } + } + + // Inform the service of the removed call. + if (diagnosticCall != null) { + getExecutor().execute(() -> onRemoveDiagnosticCall(diagnosticCall)); } } @@ -252,8 +318,14 @@ public abstract class CallDiagnosticService extends Service { */ private void handleReceivedD2DMessage(@NonNull String callId, int message, int value) { Log.i(this, "handleReceivedD2DMessage: callId=%s, msg=%d/%d", callId, message, value); - DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId); - diagnosticCall.onReceiveDeviceToDeviceMessage(message, value); + DiagnosticCall diagnosticCall; + synchronized (mLock) { + diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId); + } + if (diagnosticCall != null) { + getExecutor().execute( + () -> diagnosticCall.onReceiveDeviceToDeviceMessage(message, value)); + } } /** @@ -265,7 +337,7 @@ public abstract class CallDiagnosticService extends Service { private void handleBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport qualityReport) { Log.i(this, "handleBluetoothCallQualityReport; report=%s", qualityReport); - onBluetoothCallQualityReportReceived(qualityReport); + getExecutor().execute(() -> onBluetoothCallQualityReportReceived(qualityReport)); } /** diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index dc2fb948fdbe..f84dd7b0bb17 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -338,7 +338,7 @@ public abstract class Conference extends Conferenceable { * * @param videoState The video state in which to answer the connection. */ - public void onAnswer(int videoState) {} + public void onAnswer(@VideoProfile.VideoState int videoState) {} /** * Notifies this Conference, which is in {@code STATE_RINGING}, of diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 97a06a81072e..d8bd6a576fad 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -2594,9 +2594,9 @@ public abstract class ConnectionService extends Service { * @return The {@code Connection} object to satisfy this call, or {@code null} to * not handle the call. */ - public final RemoteConnection createRemoteIncomingConnection( - PhoneAccountHandle connectionManagerPhoneAccount, - ConnectionRequest request) { + public final @Nullable RemoteConnection createRemoteIncomingConnection( + @NonNull PhoneAccountHandle connectionManagerPhoneAccount, + @NonNull ConnectionRequest request) { return mRemoteConnectionManager.createRemoteConnection( connectionManagerPhoneAccount, request, true); } @@ -2613,9 +2613,9 @@ public abstract class ConnectionService extends Service { * @return The {@code Connection} object to satisfy this call, or {@code null} to * not handle the call. */ - public final RemoteConnection createRemoteOutgoingConnection( - PhoneAccountHandle connectionManagerPhoneAccount, - ConnectionRequest request) { + public final @Nullable RemoteConnection createRemoteOutgoingConnection( + @NonNull PhoneAccountHandle connectionManagerPhoneAccount, + @NonNull ConnectionRequest request) { return mRemoteConnectionManager.createRemoteConnection( connectionManagerPhoneAccount, request, false); } @@ -2855,12 +2855,14 @@ public abstract class ConnectionService extends Service { * @param connectionManagerPhoneAccount See description at * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. * @param request Details about the incoming conference call. - * @return The {@code Conference} object to satisfy this call, or {@code null} to - * not handle the call. + * @return The {@code Conference} object to satisfy this call. If the conference attempt is + * failed, the return value will be a result of an invocation of + * {@link Connection#createFailedConnection(DisconnectCause)}. + * Return {@code null} if the {@link ConnectionService} cannot handle the call. */ public @Nullable Conference onCreateIncomingConference( - @Nullable PhoneAccountHandle connectionManagerPhoneAccount, - @Nullable ConnectionRequest request) { + @NonNull PhoneAccountHandle connectionManagerPhoneAccount, + @NonNull ConnectionRequest request) { return null; } @@ -2963,8 +2965,8 @@ public abstract class ConnectionService extends Service { * @param request The outgoing connection request. */ public void onCreateOutgoingConferenceFailed( - @Nullable PhoneAccountHandle connectionManagerPhoneAccount, - @Nullable ConnectionRequest request) { + @NonNull PhoneAccountHandle connectionManagerPhoneAccount, + @NonNull ConnectionRequest request) { } @@ -3028,12 +3030,14 @@ public abstract class ConnectionService extends Service { * a {@code PhoneAccount} registered by this {@code ConnectionService} to use for * making the connection. * @param request Details about the outgoing call. - * @return The {@code Conference} object to satisfy this call, or the result of an invocation - * of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call. + * @return The {@code Conference} object to satisfy this call. If the conference attempt is + * failed, the return value will be a result of an invocation of + * {@link Connection#createFailedConnection(DisconnectCause)}. + * Return {@code null} if the {@link ConnectionService} cannot handle the call. */ public @Nullable Conference onCreateOutgoingConference( - @Nullable PhoneAccountHandle connectionManagerPhoneAccount, - @Nullable ConnectionRequest request) { + @NonNull PhoneAccountHandle connectionManagerPhoneAccount, + @NonNull ConnectionRequest request) { return null; } diff --git a/telecomm/java/android/telecom/DiagnosticCall.java b/telecomm/java/android/telecom/DiagnosticCall.java index a4952899eb46..af46b7759fb5 100644 --- a/telecomm/java/android/telecom/DiagnosticCall.java +++ b/telecomm/java/android/telecom/DiagnosticCall.java @@ -26,15 +26,27 @@ import android.telephony.ims.ImsReasonInfo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * A {@link DiagnosticCall} provides a way for a {@link CallDiagnosticService} to receive diagnostic - * information about a mobile call on the device. The {@link CallDiagnosticService} can generate - * mid-call diagnostic messages using the {@link #displayDiagnosticMessage(int, CharSequence)} API - * which provides the user with valuable information about conditions impacting their call and - * corrective actions. For example, if the {@link CallDiagnosticService} determines that conditions - * on the call are degrading, it can inform the user that the call may soon drop and that they - * can try using a different calling method (e.g. VOIP or WIFI). + * information about a mobile call on the device. A {@link DiagnosticCall} is similar to a + * {@link Call}, however it does not expose call control capabilities and exposes extra diagnostic + * and messaging capabilities not present on a {@link Call}. The {@link CallDiagnosticService} + * creates a {@link DiagnosticCall} for each {@link Call} on the device. This means that for each + * in progress call on the device, the {@link CallDiagnosticService} will create an instance of + * {@link DiagnosticCall}. + * <p> + * The {@link CallDiagnosticService} can generate mid-call diagnostic messages using the + * {@link #displayDiagnosticMessage(int, CharSequence)} API which provides the user with valuable + * information about conditions impacting their call and corrective actions. For example, if the + * {@link CallDiagnosticService} determines that conditions on the call are degrading, it can inform + * the user that the call may soon drop and that they can try using a different calling method + * (e.g. VOIP or WIFI). + * <h2>Threading Model</h2> + * All incoming IPC from Telecom in this class will use the same {@link Executor} as the + * {@link CallDiagnosticService}. See {@link CallDiagnosticService#setExecutor(Executor)} for more + * information. * @hide */ @SystemApi @@ -53,15 +65,19 @@ public abstract class DiagnosticCall { /** * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the radio access type - * used for the current call. Based loosely on the - * {@link android.telephony.TelephonyManager#getNetworkType(int)} for the call, provides a - * high level summary of the call radio access type. + * used for the current call. The call network type communicated here is an intentional + * simplification of the {@link android.telephony.TelephonyManager#getNetworkType(int)} which + * removes some of the resolution inherent in those values; the + * {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE_CA} value, for example is + * collapsed into the {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE} value for + * efficiency of transport. For a discussion on the necessity of this simplification, see + * {@link #sendDeviceToDeviceMessage(int, int)}. * <p> - * Valid values: + * Valid values are below: * <UL> - * <LI>{@link #NETWORK_TYPE_LTE}</LI> - * <LI>{@link #NETWORK_TYPE_IWLAN}</LI> - * <LI>{@link #NETWORK_TYPE_NR}</LI> + * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE}</LI> + * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_IWLAN}</LI> + * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_NR}</LI> * </UL> */ public static final int MESSAGE_CALL_NETWORK_TYPE = 1; @@ -69,14 +85,21 @@ public abstract class DiagnosticCall { /** * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the call audio codec - * used for the current call. Based loosely on the {@link Connection#EXTRA_AUDIO_CODEC} for a - * call. + * used for the current call. + * <p> + * The audio codec communicated here is an intentional simplification of the + * {@link Connection#EXTRA_AUDIO_CODEC} for a call and focuses on communicating the most common + * variants of these audio codecs. Other variants of these codecs are reported as the next + * closest variant. For example, the {@link Connection#AUDIO_CODEC_EVS_FB} full band codec + * is reported via device to device communication as {@link Connection#AUDIO_CODEC_EVS_WB}. + * For a discussion on the necessity of this simplification, see + * {@link #sendDeviceToDeviceMessage(int, int)}. * <p> * Valid values: * <UL> - * <LI>{@link #AUDIO_CODEC_EVS}</LI> - * <LI>{@link #AUDIO_CODEC_AMR_WB}</LI> - * <LI>{@link #AUDIO_CODEC_AMR_NB}</LI> + * <LI>{@link Connection#AUDIO_CODEC_EVS_WB}</LI> + * <LI>{@link Connection#AUDIO_CODEC_AMR_WB}</LI> + * <LI>{@link Connection#AUDIO_CODEC_AMR}</LI> * </UL> */ public static final int MESSAGE_CALL_AUDIO_CODEC = 2; @@ -122,41 +145,6 @@ public abstract class DiagnosticCall { public @interface MessageType {} /** - * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate an LTE network is being used for the - * call. - */ - public static final int NETWORK_TYPE_LTE = 1; - - /** - * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate WIFI calling is in use for the call. - */ - public static final int NETWORK_TYPE_IWLAN = 2; - - /** - * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate a 5G NR (new radio) network is in - * used for the call. - */ - public static final int NETWORK_TYPE_NR = 3; - - /** - * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the - * Enhanced Voice Services (EVS) codec for the call. - */ - public static final int AUDIO_CODEC_EVS = 1; - - /** - * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the AMR - * (adaptive multi-rate) WB (wide band) audio codec. - */ - public static final int AUDIO_CODEC_AMR_WB = 2; - - /** - * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the AMR - * (adaptive multi-rate) NB (narrow band) audio codec. - */ - public static final int AUDIO_CODEC_AMR_NB = 3; - - /** * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is low. */ public static final int BATTERY_STATE_LOW = 1; @@ -183,7 +171,6 @@ public abstract class DiagnosticCall { private Listener mListener; private String mCallId; - private Call.Details mCallDetails; /** * @hide @@ -210,16 +197,10 @@ public abstract class DiagnosticCall { } /** - * Returns the latest {@link Call.Details} associated with this {@link DiagnosticCall} as - * reported by {@link #onCallDetailsChanged(Call.Details)}. - * @return The latest {@link Call.Details}. - */ - public @NonNull Call.Details getCallDetails() { - return mCallDetails; - } - - /** * Telecom calls this method when the details of a call changes. + * <p> + * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; + * see {@link CallDiagnosticService#getExecutor()} for more information. */ public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details details); @@ -234,6 +215,9 @@ public abstract class DiagnosticCall { * devices communicating are using a different version of the protocol, messages the recipient * are not aware of are silently discarded. This means an older client talking to a new client * will not receive newer messages and values sent by the new client. + * <p> + * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; + * see {@link CallDiagnosticService#getExecutor()} for more information. */ public abstract void onReceiveDeviceToDeviceMessage( @MessageType int message, @@ -253,39 +237,19 @@ public abstract class DiagnosticCall { * platform due to the extreme bandwidth constraints inherent with underlying device to device * communication transports used by the telephony framework. Device to device communication is * either accomplished by adding RFC8285 compliant RTP header extensions to the audio packets - * for a call, or using the DTMF digits A-D as a communication pathway. Signalling requirements - * for DTMF digits place a significant limitation on the amount of information which can be - * communicated during a call. + * for a call, or using the DTMF digits A-D as a communication pathway. RTP header extension + * packets ride alongside a the audio for a call, and are thus limited to roughly a byte for + * a message. Signalling requirements for DTMF digits place even more significant limitations + * on the amount of information which can be communicated during a call, offering only a few + * bits of potential information per message. The messages and values are constrained in order + * to meet the limited bandwidth inherent with DTMF signalling. * <p> - * Allowed message types and values are: + * Allowed message types are: * <ul> - * <li>{@link #MESSAGE_CALL_NETWORK_TYPE} - * <ul> - * <li>{@link #NETWORK_TYPE_LTE}</li> - * <li>{@link #NETWORK_TYPE_IWLAN}</li> - * <li>{@link #NETWORK_TYPE_NR}</li> - * </ul> - * </li> - * <li>{@link #MESSAGE_CALL_AUDIO_CODEC} - * <ul> - * <li>{@link #AUDIO_CODEC_EVS}</li> - * <li>{@link #AUDIO_CODEC_AMR_WB}</li> - * <li>{@link #AUDIO_CODEC_AMR_NB}</li> - * </ul> - * </li> - * <li>{@link #MESSAGE_DEVICE_BATTERY_STATE} - * <ul> - * <li>{@link #BATTERY_STATE_LOW}</li> - * <li>{@link #BATTERY_STATE_GOOD}</li> - * <li>{@link #BATTERY_STATE_CHARGING}</li> - * </ul> - * </li> - * <li>{@link #MESSAGE_DEVICE_NETWORK_COVERAGE} - * <ul> - * <li>{@link #COVERAGE_POOR}</li> - * <li>{@link #COVERAGE_GOOD}</li> - * </ul> - * </li> + * <li>{@link #MESSAGE_CALL_NETWORK_TYPE}</LI> + * <li>{@link #MESSAGE_CALL_AUDIO_CODEC}</LI> + * <li>{@link #MESSAGE_DEVICE_BATTERY_STATE}</LI> + * <li>{@link #MESSAGE_DEVICE_NETWORK_COVERAGE}</LI> * </ul> * @param message The message type to send. * @param value The message value corresponding to the type. @@ -307,6 +271,9 @@ public abstract class DiagnosticCall { * @param preciseDisconnectCause the precise disconnect cause for the call. * @return the disconnect message to use in place of the default Telephony message, or * {@code null} if the default message will not be overridden. + * <p> + * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; + * see {@link CallDiagnosticService#getExecutor()} for more information. */ // TODO: Wire in Telephony support for this. public abstract @Nullable CharSequence onCallDisconnected( @@ -323,6 +290,9 @@ public abstract class DiagnosticCall { * @param disconnectReason The {@link ImsReasonInfo} associated with the call disconnection. * @return A user-readable call disconnect message to use in place of the platform-generated * disconnect message, or {@code null} if the disconnect message should not be overridden. + * <p> + * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; + * see {@link CallDiagnosticService#getExecutor()} for more information. */ // TODO: Wire in Telephony support for this. public abstract @Nullable CharSequence onCallDisconnected( @@ -332,6 +302,9 @@ public abstract class DiagnosticCall { * Telecom calls this method when a {@link CallQuality} report is received from the telephony * stack for a call. * @param callQuality The call quality report for this call. + * <p> + * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService}; + * see {@link CallDiagnosticService#getExecutor()} for more information. */ public abstract void onCallQualityReceived(@NonNull CallQuality callQuality); @@ -375,7 +348,6 @@ public abstract class DiagnosticCall { * @hide */ public void handleCallUpdated(@NonNull Call.Details newDetails) { - mCallDetails = newDetails; onCallDetailsChanged(newDetails); } } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 17749e8b0a8f..1677c8c1f08e 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -314,8 +314,8 @@ public class TelecomManager { public static final String EXTRA_HAS_PICTURE = "android.telecom.extra.HAS_PICTURE"; /** - * A URI representing the picture that was downloaded when a call is received or uploaded - * when a call is placed. + * A {@link Uri} representing the picture that was downloaded when a call is received or + * uploaded when a call is placed. * * This is a content URI within the call log provider which can be used to open a file * descriptor. This could be set a short time after a call is added to the Dialer app if the diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 763c968ef50b..1a71f808daa7 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -27,6 +27,7 @@ import android.annotation.SystemService; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; +import android.net.ipsec.ike.SaProposal; import android.os.Build; import android.os.PersistableBundle; import android.os.RemoteException; @@ -1340,6 +1341,38 @@ public class CarrierConfigManager { "support_ims_conference_event_package_on_peer_bool"; /** + * Indicates whether the carrier supports the use of RFC8285 compliant RTP header extensions for + * the purpose of device to device communication while in a call. + * <p> + * See also {@link #KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL}. + */ + public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL = + "supports_device_to_device_communication_using_rtp_bool"; + + /** + * Indicates whether the carrier supports the negotiations of RFC8285 compliant RTP header + * extensions supported on a call during the Session Description Protocol (SDP). This option + * is only used when {@link #KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL} is + * {@code true}. + * <p> + * When {@code true}, the RTP header extensions the platform uses for device to device + * communication will be offered to the remote end during the SDP negotiation process. + * When {@code false}, the RTP header extensions will not be negotiated during the SDP + * negotiation process and the platform will send RTP header extensions without prior + * negotiation if {@link #KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL} is + * {@code true}. + */ + public static final String KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL = + "supports_sdp_negotiation_of_d2d_rtp_header_extensions_bool"; + + /** + * Indicates whether the carrier supports the use of DTMF digits A-D for the purpose of device + * to device communication while in a call. + */ + public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = + "supports_device_to_device_communication_using_dtmf_bool"; + + /** * Determines whether High Definition audio property is displayed in the dialer UI. * If {@code false}, remove the HD audio property from the connection so that HD audio related * UI is not displayed. If {@code true}, keep HD audio property as it is configured. @@ -4138,9 +4171,11 @@ public class CarrierConfigManager { KEY_PREFIX + "child_sa_rekey_soft_timer_sec_int"; /** - * Supported DH groups for IKE negotiation. Possible values are {@link #DH_GROUP_NONE}, - * {@link #DH_GROUP_1024_BIT_MODP}, {@link #DH_GROUP_1536_BIT_MODP}, {@link - * #DH_GROUP_2048_BIT_MODP} + * Supported DH groups for IKE negotiation. Possible values are: + * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_NONE}, + * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_1024_BIT_MODP}, + * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_1536_BIT_MODP}, + * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_2048_BIT_MODP} */ public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = KEY_PREFIX + "diffie_hellman_groups_int_array"; @@ -4176,23 +4211,29 @@ public class CarrierConfigManager { /** * List of supported key sizes for AES Cipher Block Chaining (CBC) encryption mode of child - * session. Possible values are {@link #KEY_LEN_UNUSED}, {@link #KEY_LEN_AES_128}, {@link - * #KEY_LEN_AES_192}, {@link #KEY_LEN_AES_256} + * session. Possible values are: + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256} */ public static final String KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = KEY_PREFIX + "child_session_aes_cbc_key_size_int_array"; /** * List of supported key sizes for AES Counter (CTR) encryption mode of child session. - * Possible values are {@link #KEY_LEN_UNUSED}, - * {@link #KEY_LEN_AES_128}, {@link #KEY_LEN_AES_192}, {@link #KEY_LEN_AES_256} + * Possible values are: + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256} */ public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = KEY_PREFIX + "child_session_aes_ctr_key_size_int_array"; /** * List of supported encryption algorithms for child session. Possible values are - * {@link #ENCRYPTION_ALGORITHM_AES_CBC} + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC} */ public static final String KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = KEY_PREFIX + "supported_child_session_encryption_algorithms_int_array"; @@ -4213,8 +4254,11 @@ public class CarrierConfigManager { /** * List of supported key sizes for AES Cipher Block Chaining (CBC) encryption mode of IKE - * session. Possible values - {@link #KEY_LEN_UNUSED}, {@link #KEY_LEN_AES_128}, {@link - * #KEY_LEN_AES_192}, {@link #KEY_LEN_AES_256} + * session. Possible values: + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256} */ public static final String KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = KEY_PREFIX + "ike_session_encryption_aes_cbc_key_size_int_array"; @@ -4222,24 +4266,31 @@ public class CarrierConfigManager { /** * List of supported key sizes for AES Counter (CTR) encryption mode of IKE session. - * Possible values - {@link #KEY_LEN_UNUSED}, {@link #KEY_LEN_AES_128}, - * {@link #KEY_LEN_AES_192}, {@link #KEY_LEN_AES_256} + * Possible values - + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256} */ public static final String KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = KEY_PREFIX + "ike_session_encryption_aes_ctr_key_size_int_array"; /** * List of supported encryption algorithms for IKE session. Possible values are - * {@link #ENCRYPTION_ALGORITHM_AES_CBC}, {@link #ENCRYPTION_ALGORITHM_AES_CTR} + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR} */ public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = KEY_PREFIX + "supported_ike_session_encryption_algorithms_int_array"; /** - * List of supported integrity algorithms for IKE session Possible values are {@link - * #INTEGRITY_ALGORITHM_NONE}, {@link #INTEGRITY_ALGORITHM_HMAC_SHA1_96}, {@link - * #INTEGRITY_ALGORITHM_AES_XCBC_96}, {@link #INTEGRITY_ALGORITHM_HMAC_SHA2_256_128}, {@link - * #INTEGRITY_ALGORITHM_HMAC_SHA2_384_192}, {@link #INTEGRITY_ALGORITHM_HMAC_SHA2_512_256} + * List of supported integrity algorithms for IKE session. Possible values are + * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_NONE}, + * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_HMAC_SHA1_96}, + * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_AES_XCBC_96}, + * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_HMAC_SHA2_256_128}, + * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_HMAC_SHA2_384_192}, + * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_HMAC_SHA2_512_256} */ public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = KEY_PREFIX + "supported_integrity_algorithms_int_array"; @@ -4259,9 +4310,11 @@ public class CarrierConfigManager { /** * List of supported pseudo random function algorithms for IKE session. Possible values are - * {@link #PSEUDORANDOM_FUNCTION_HMAC_SHA1}, {@link #PSEUDORANDOM_FUNCTION_AES128_XCBC}, - * {@link #PSEUDORANDOM_FUNCTION_SHA2_256}, {@link #PSEUDORANDOM_FUNCTION_SHA2_384}, - * {@link #PSEUDORANDOM_FUNCTION_SHA2_512} + * {@link android.net.ipsec.ike.SaProposal#PSEUDORANDOM_FUNCTION_HMAC_SHA1}, + * {@link android.net.ipsec.ike.SaProposal#PSEUDORANDOM_FUNCTION_AES128_XCBC}, + * {@link android.net.ipsec.ike.SaProposal#PSEUDORANDOM_FUNCTION_SHA2_256}, + * {@link android.net.ipsec.ike.SaProposal#PSEUDORANDOM_FUNCTION_SHA2_384}, + * {@link android.net.ipsec.ike.SaProposal#PSEUDORANDOM_FUNCTION_SHA2_512} */ public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = KEY_PREFIX + "supported_prf_algorithms_int_array"; @@ -4334,182 +4387,6 @@ public class CarrierConfigManager { public static final int EPDG_ADDRESS_CELLULAR_LOC = 3; /** @hide */ - @IntDef({KEY_LEN_UNUSED, KEY_LEN_AES_128, KEY_LEN_AES_192, KEY_LEN_AES_256}) - public @interface EncrpytionKeyLengthType {} - - public static final int KEY_LEN_UNUSED = 0; - /** AES Encryption/Ciphering Algorithm key length 128 bits. */ - public static final int KEY_LEN_AES_128 = 128; - /** AES Encryption/Ciphering Algorithm key length 192 bits. */ - public static final int KEY_LEN_AES_192 = 192; - /** AES Encryption/Ciphering Algorithm key length 256 bits. */ - public static final int KEY_LEN_AES_256 = 256; - - /** @hide */ - @IntDef({ - DH_GROUP_NONE, - DH_GROUP_1024_BIT_MODP, - DH_GROUP_1536_BIT_MODP, - DH_GROUP_2048_BIT_MODP, - DH_GROUP_3072_BIT_MODP, - DH_GROUP_4096_BIT_MODP - }) - public @interface DhGroup {} - - /** None Diffie-Hellman Group. */ - public static final int DH_GROUP_NONE = 0; - /** - * 1024-bit MODP Diffie-Hellman Group. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int DH_GROUP_1024_BIT_MODP = 2; - /** - * 1536-bit MODP Diffie-Hellman Group. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int DH_GROUP_1536_BIT_MODP = 5; - /** - * 2048-bit MODP Diffie-Hellman Group. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int DH_GROUP_2048_BIT_MODP = 14; - /** - * 3072-bit MODP Diffie-Hellman Group. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int DH_GROUP_3072_BIT_MODP = 15; - /** - * 4096-bit MODP Diffie-Hellman Group. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int DH_GROUP_4096_BIT_MODP = 16; - - /** @hide */ - @IntDef({ENCRYPTION_ALGORITHM_AES_CBC, ENCRYPTION_ALGORITHM_AES_CTR}) - public @interface EncryptionAlgorithm {} - - /** - * AES-CBC Encryption/Ciphering Algorithm. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int ENCRYPTION_ALGORITHM_AES_CBC = 12; - - /** - * AES-CTR Encryption/Ciphering Algorithm. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int ENCRYPTION_ALGORITHM_AES_CTR = 13; - - /** @hide */ - @IntDef({ - INTEGRITY_ALGORITHM_NONE, - INTEGRITY_ALGORITHM_HMAC_SHA1_96, - INTEGRITY_ALGORITHM_AES_XCBC_96, - INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, - INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, - INTEGRITY_ALGORITHM_HMAC_SHA2_512_256 - }) - public @interface IntegrityAlgorithm {} - - /** None Authentication/Integrity Algorithm. */ - public static final int INTEGRITY_ALGORITHM_NONE = 0; - /** - * HMAC-SHA1 Authentication/Integrity Algorithm. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int INTEGRITY_ALGORITHM_HMAC_SHA1_96 = 2; - /** - * AES-XCBC-96 Authentication/Integrity Algorithm. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int INTEGRITY_ALGORITHM_AES_XCBC_96 = 5; - /** - * HMAC-SHA256 Authentication/Integrity Algorithm with 128-bit truncation. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_256_128 = 12; - /** - * HMAC-SHA384 Authentication/Integrity Algorithm with 192-bit truncation. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_384_192 = 13; - /** - * HMAC-SHA512 Authentication/Integrity Algorithm with 256-bit truncation. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_512_256 = 14; - - /** @hide */ - @IntDef({ - PSEUDORANDOM_FUNCTION_HMAC_SHA1, - PSEUDORANDOM_FUNCTION_AES128_XCBC, - PSEUDORANDOM_FUNCTION_SHA2_256, - PSEUDORANDOM_FUNCTION_SHA2_384, - PSEUDORANDOM_FUNCTION_SHA2_512 - }) - public @interface PseudorandomFunction {} - - /** - * HMAC-SHA1 Pseudorandom Function. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int PSEUDORANDOM_FUNCTION_HMAC_SHA1 = 2; - /** - * AES128-XCBC Pseudorandom Function. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int PSEUDORANDOM_FUNCTION_AES128_XCBC = 4; - /** - * HMAC-SHA2-256 Pseudorandom Function. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int PSEUDORANDOM_FUNCTION_SHA2_256 = 5; - /** - * HMAC-SHA2-384 Pseudorandom Function. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int PSEUDORANDOM_FUNCTION_SHA2_384 = 6; - /** - * HMAC-SHA2-384 Pseudorandom Function. - * - * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key - * Exchange Protocol Version 2 (IKEv2)</a> - */ - public static final int PSEUDORANDOM_FUNCTION_SHA2_512 = 7; - - /** @hide */ @IntDef({ID_TYPE_FQDN, ID_TYPE_RFC822_ADDR, ID_TYPE_KEY_ID}) public @interface IkeIdType {} @@ -4550,31 +4427,33 @@ public class CarrierConfigManager { defaults.putIntArray( KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY, new int[] { - DH_GROUP_1024_BIT_MODP, DH_GROUP_1536_BIT_MODP, DH_GROUP_2048_BIT_MODP + SaProposal.DH_GROUP_1024_BIT_MODP, + SaProposal.DH_GROUP_1536_BIT_MODP, + SaProposal.DH_GROUP_2048_BIT_MODP }); defaults.putIntArray( KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY, - new int[] {ENCRYPTION_ALGORITHM_AES_CBC}); + new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC}); defaults.putIntArray( KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY, - new int[] {ENCRYPTION_ALGORITHM_AES_CBC}); + new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC}); defaults.putIntArray( KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY, new int[] { - INTEGRITY_ALGORITHM_AES_XCBC_96, - INTEGRITY_ALGORITHM_HMAC_SHA1_96, - INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, - INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, - INTEGRITY_ALGORITHM_HMAC_SHA2_512_256, + SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96, + SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96, + SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, + SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, + SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256, }); defaults.putIntArray( KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY, new int[] { - PSEUDORANDOM_FUNCTION_HMAC_SHA1, - PSEUDORANDOM_FUNCTION_AES128_XCBC, - PSEUDORANDOM_FUNCTION_SHA2_256, - PSEUDORANDOM_FUNCTION_SHA2_384, - PSEUDORANDOM_FUNCTION_SHA2_512 + SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1, + SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC, + SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256, + SaProposal.PSEUDORANDOM_FUNCTION_SHA2_384, + SaProposal.PSEUDORANDOM_FUNCTION_SHA2_512 }); defaults.putInt(KEY_EPDG_AUTHENTICATION_METHOD_INT, AUTHENTICATION_METHOD_EAP_ONLY); @@ -4584,16 +4463,28 @@ public class CarrierConfigManager { defaults.putInt(KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT, 20); defaults.putIntArray( KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY, - new int[] {KEY_LEN_AES_128, KEY_LEN_AES_192, KEY_LEN_AES_256}); + new int[] { + SaProposal.KEY_LEN_AES_128, + SaProposal.KEY_LEN_AES_192, + SaProposal.KEY_LEN_AES_256}); defaults.putIntArray( KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY, - new int[] {KEY_LEN_AES_128, KEY_LEN_AES_192, KEY_LEN_AES_256}); + new int[] { + SaProposal.KEY_LEN_AES_128, + SaProposal.KEY_LEN_AES_192, + SaProposal.KEY_LEN_AES_256}); defaults.putIntArray( KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY, - new int[] {KEY_LEN_AES_128, KEY_LEN_AES_192, KEY_LEN_AES_256}); + new int[] { + SaProposal.KEY_LEN_AES_128, + SaProposal.KEY_LEN_AES_192, + SaProposal.KEY_LEN_AES_256}); defaults.putIntArray( KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY, - new int[] {KEY_LEN_AES_128, KEY_LEN_AES_192, KEY_LEN_AES_256}); + new int[] { + SaProposal.KEY_LEN_AES_128, + SaProposal.KEY_LEN_AES_192, + SaProposal.KEY_LEN_AES_256}); defaults.putIntArray( KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, new int[] {EPDG_ADDRESS_PLMN, EPDG_ADDRESS_STATIC}); @@ -5008,6 +4899,9 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL, true); + sDefaults.putBoolean(KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL, false); + sDefaults.putBoolean(KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL, false); + sDefaults.putBoolean(KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL, false); sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false); sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false); sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index cf4e6779b363..f7580d77186d 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -416,7 +416,7 @@ public class SubscriptionManager { * <p> * Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the * subscription cross sim calling enabled - * {@link ImsMmTelManager#isCrossSimCallingEnabledByUser()} + * {@link ImsMmTelManager#isCrossSimCallingEnabled()} * while your app is running. You can also use a {@link android.app.job.JobService} * to ensure your app * is notified of changes to the {@link Uri} even when it is not running. @@ -602,6 +602,43 @@ public class SubscriptionManager { public @interface SimDisplayNameSource {} /** + * Device status is not shared to a remote party. + */ + public static final int D2D_SHARING_DISABLED = 0; + + /** + * Device status is shared with all numbers in the user's contacts. + */ + public static final int D2D_SHARING_ALL_CONTACTS = 1; + + /** + * Device status is shared with all starred contacts. + */ + public static final int D2D_SHARING_STARRED_CONTACTS = 2; + + /** + * Device status is shared whenever possible. + */ + public static final int D2D_SHARING_ALL = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"D2D_SHARING_"}, + value = { + D2D_SHARING_DISABLED, + D2D_SHARING_ALL_CONTACTS, + D2D_SHARING_STARRED_CONTACTS, + D2D_SHARING_ALL + }) + public @interface DeviceToDeviceStatusSharing {} + + /** + * TelephonyProvider column name for device to device sharing status. + * <P>Type: INTEGER (int)</P> + */ + public static final String D2D_STATUS_SHARING = SimInfo.COLUMN_D2D_STATUS_SHARING; + + /** * TelephonyProvider column name for the color of a SIM. * <P>Type: INTEGER (int)</P> */ @@ -3374,6 +3411,36 @@ public class SubscriptionManager { } /** + * Set the device to device status sharing user preference for a subscription ID. The setting + * app uses this method to indicate with whom they wish to share device to device status + * information. + * @param sharing the status sharing preference + * @param subId the unique Subscription ID in database + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setDeviceToDeviceStatusSharing(@DeviceToDeviceStatusSharing int sharing, + int subId) { + if (VDBG) { + logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: " + subId); + } + setSubscriptionPropertyHelper(subId, "setDeviceToDeviceSharingStatus", + (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subId)); + } + + /** + * Returns the user-chosen device to device status sharing preference + * @param subId Subscription id of subscription + * @return The device to device status sharing preference + */ + public @DeviceToDeviceStatusSharing int getDeviceToDeviceStatusSharing(int subId) { + if (VDBG) { + logd("[getDeviceToDeviceStatusSharing] + subId: " + subId); + } + return getIntegerSubscriptionProperty(subId, D2D_STATUS_SHARING, D2D_SHARING_DISABLED, + mContext); + } + + /** * DO NOT USE. * This API is designed for features that are not finished at this point. Do not call this API. * @hide diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 3cb72b5e0c0d..4dc6c7ce35cf 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -139,6 +139,8 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Provides access to information about the telephony services on @@ -3147,6 +3149,10 @@ public class TelephonyManager { return NETWORK_TYPE_BITMASK_LTE_CA; case NETWORK_TYPE_NR: return NETWORK_TYPE_BITMASK_NR; + case NETWORK_TYPE_IWLAN: + return NETWORK_TYPE_BITMASK_IWLAN; + case NETWORK_TYPE_IDEN: + return (1 << (NETWORK_TYPE_IDEN - 1)); default: return NETWORK_TYPE_BITMASK_UNKNOWN; } @@ -8642,8 +8648,8 @@ public class TelephonyManager { public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; /** - * Set the allowed network types of the device and - * provide the reason triggering the allowed network change. + * Set the allowed network types of the device and provide the reason triggering the allowed + * network change. * This can be called for following reasons * <ol> * <li>Allowed network types control by USER {@link #ALLOWED_NETWORK_TYPES_REASON_USER} @@ -8655,10 +8661,15 @@ public class TelephonyManager { * </ol> * This API will result in allowing an intersection of allowed network types for all reasons, * including the configuration done through other reasons. + * + * The functionality of this API with the parameter + * {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER} is the same as the API + * {@link TelephonyManager#setAllowedNetworkTypes}. Use this API instead of + * {@link TelephonyManager#setAllowedNetworkTypes}. * <p> * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported} * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then - * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise, + * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise, * setPreferredNetworkTypesBitmap is used instead. * * @param reason the reason the allowed network type change is taking place @@ -8698,21 +8709,17 @@ public class TelephonyManager { * {@link #getAllowedNetworkTypesForReason} returns allowed network type for a * specific reason. * - * <p>Requires Permission: - * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} - * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). - * * @param reason the reason the allowed network type change is taking place * @return the allowed network type bitmask * @throws IllegalStateException if the Telephony process is not currently available. * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed. * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @RequiresFeature( enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported", value = TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED) + @SystemApi public @NetworkTypeBitMask long getAllowedNetworkTypesForReason( @AllowedNetworkTypesReason int reason) { if (!isValidAllowedNetworkTypesReason(reason)) { @@ -8757,6 +8764,25 @@ public class TelephonyManager { } /** + * Returns a string representation of the allowed network types{@link NetworkTypeBitMask}. + * + * @param networkTypeBitmask The bitmask of allowed network types. + * @return the name of the allowed network types + * @hide + */ + public static String convertNetworkTypeBitmaskToString( + @NetworkTypeBitMask long networkTypeBitmask) { + String networkTypeName = IntStream.rangeClosed(NETWORK_TYPE_GPRS, NETWORK_TYPE_NR) + .filter(x -> { + return (networkTypeBitmask & getBitMaskForNetworkType(x)) + == getBitMaskForNetworkType(x); + }) + .mapToObj(x -> getNetworkTypeName(x)) + .collect(Collectors.joining("|")); + return TextUtils.isEmpty(networkTypeName) ? "UNKNOWN" : networkTypeName; + } + + /** * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA. * * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). @@ -9135,9 +9161,7 @@ public class TelephonyManager { /** * Set the user-set status for enriched calling with call composer. * - * @param status user-set status for enriched calling with call composer; - * it must be either {@link #CALL_COMPOSER_STATUS_ON} or - * {@link #CALL_COMPOSER_STATUS_OFF}. + * @param status user-set status for enriched calling with call composer. * * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} @@ -12579,6 +12603,7 @@ public class TelephonyManager { NETWORK_TYPE_BITMASK_LTE, NETWORK_TYPE_BITMASK_LTE_CA, NETWORK_TYPE_BITMASK_NR, + NETWORK_TYPE_BITMASK_IWLAN }) public @interface NetworkTypeBitMask {} @@ -15218,13 +15243,17 @@ public class TelephonyManager { * </ul> * @param appType icc application type, like {@link #APPTYPE_USIM} or {@link * #APPTYPE_ISIM} or {@link#APPTYPE_UNKNOWN} - * @param nafId Network Application Function(NAF) fully qualified domain name and - * the selected GBA mode. It shall contain two parts delimited by "@" sign. The first - * part is the constant string "3GPP-bootstrapping" (GBA_ME), - * "3GPP-bootstrapping-uicc" (GBA_ U), or "3GPP-bootstrapping-digest" (GBA_Digest), - * and the latter part shall be the FQDN of the NAF (e.g. - * "3GPP-bootstrapping@naf1.operator.com" or "3GPP-bootstrapping-uicc@naf1.operator.com", - * or "3GPP-bootstrapping-digest@naf1.operator.com"). + * @param nafId A URI to specify Network Application Function(NAF) fully qualified domain + * name (FQDN) and the selected GBA mode. The authority of the URI must contain two parts + * delimited by "@" sign. The first part is the constant string "3GPP-bootstrapping" (GBA_ME), + * "3GPP-bootstrapping-uicc" (GBA_ U), or "3GPP-bootstrapping-digest" (GBA_Digest). + * The second part shall be the FQDN of the NAF. The scheme of the URI is not actually used + * for the authentication, which may be set the same as the resource that the application is + * going to access. For example, the nafId can be + * "https://3GPP-bootstrapping@naf1.operator.com", + * "https://3GPP-bootstrapping-uicc@naf1.operator.com", + * "https://3GPP-bootstrapping-digest@naf1.operator.com", + * "ftps://3GPP-bootstrapping-digest@naf1.operator.com". * @param securityProtocol Security protocol identifier between UE and NAF. See * 3GPP TS 33.220 Annex H. Application can use * {@link UaSecurityProtocolIdentifier#createDefaultUaSpId}, diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index a76422977cb6..ffe5399e406b 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -18,6 +18,7 @@ package android.telephony.data; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -29,6 +30,7 @@ import android.telephony.DataFailCause; import android.telephony.data.ApnSetting.ProtocolType; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -812,11 +814,19 @@ public final class DataCallResponse implements Parcelable { /** * Set pdu session id. + * <p/> + * The id must be between 1 and 15 when linked to a pdu session. If no pdu session + * exists for the current data call, the id must be set to {@link PDU_SESSION_ID_NOT_SET}. * * @param pduSessionId Pdu Session Id of the data call. * @return The same instance of the builder. */ - public @NonNull Builder setPduSessionId(int pduSessionId) { + public @NonNull Builder setPduSessionId( + @IntRange(from = PDU_SESSION_ID_NOT_SET, to = 15) int pduSessionId) { + Preconditions.checkArgument(pduSessionId >= PDU_SESSION_ID_NOT_SET, + "pduSessionId must be greater than or equal to" + PDU_SESSION_ID_NOT_SET); + Preconditions.checkArgument(pduSessionId <= 15, + "pduSessionId must be less than or equal to 15."); mPduSessionId = pduSessionId; return this; } diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index f5f29c65b7cd..048b3297a1b4 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -41,6 +41,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Base class of data service. Services that extend DataService must register the service in @@ -284,11 +285,11 @@ public abstract class DataService extends Service { * * Any resources being transferred cannot be released while a * handover is underway. - * + * <p/> * If a handover was unsuccessful, then the framework calls * {@link DataService#cancelHandover}. The target transport retains ownership over any of * the resources being transferred. - * + * <p/> * If a handover was successful, the framework calls {@link DataService#deactivateDataCall} * with reason {@link DataService.REQUEST_REASON_HANDOVER}. The target transport now owns * the transferred resources and is responsible for releasing them. @@ -299,21 +300,27 @@ public abstract class DataService extends Service { * @hide */ public void startHandover(int cid, @NonNull DataServiceCallback callback) { + Objects.requireNonNull(callback, "callback cannot be null"); // The default implementation is to return unsupported. - if (callback != null) { - Log.d(TAG, "startHandover: " + cid); - callback.onHandoverStarted(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); - } else { - Log.e(TAG, "startHandover: " + cid + ", callback is null"); - } + Log.d(TAG, "startHandover: " + cid); + callback.onHandoverStarted(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); } /** * Indicates that a handover was cancelled after a call to * {@link DataService#startHandover}. This is called on the source transport. - * + * <p/> * Since the handover was unsuccessful, the source transport retains ownership over any of * the resources being transferred and is still responsible for releasing them. + * <p/> + * The handover can be cancelled up until either: + * <ul><li> + * The handover was successful after receiving a successful response from + * {@link DataService#setupDataCall} on the target transport. + * </li><li> + * The data call on the source transport was lost. + * </li> + * </ul> * * @param cid The identifier of the data call which is provided in {@link DataCallResponse} * @param callback The result callback for this request. @@ -321,13 +328,10 @@ public abstract class DataService extends Service { * @hide */ public void cancelHandover(int cid, @NonNull DataServiceCallback callback) { + Objects.requireNonNull(callback, "callback cannot be null"); // The default implementation is to return unsupported. - if (callback != null) { - Log.d(TAG, "cancelHandover: " + cid); - callback.onHandoverCancelled(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); - } else { - Log.e(TAG, "cancelHandover: " + cid + ", callback is null"); - } + Log.d(TAG, "cancelHandover: " + cid); + callback.onHandoverCancelled(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); } /** diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index ca1f861f9808..363e47a6d242 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -254,15 +254,15 @@ public class DataServiceCallback { } /** - * The APN is throttled for the duration specified in - * {@link DataCallResponse#getRetryDurationMillis}. Calling this method unthrottles that - * APN. + * Unthrottles the APN on the current transport. There is no matching "APN throttle" method. + * Instead, the APN is throttled for the time specified in + * {@link DataCallResponse#getRetryDurationMillis}. * <p/> * see: {@link DataCallResponse#getRetryDurationMillis} * * @param apn Access Point Name defined by the carrier. */ - public void onApnUnthrottled(@NonNull String apn) { + public void onApnUnthrottled(final @NonNull String apn) { if (mCallback != null) { try { if (DBG) Rlog.d(TAG, "onApnUnthrottled"); diff --git a/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.java b/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.java index 041edc00c4d2..406c38bf60ef 100644 --- a/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.java +++ b/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.java @@ -184,16 +184,6 @@ public final class EpsBearerQosSessionAttributes implements Parcelable, QosSessi mRemoteAddresses = Collections.unmodifiableList(remoteAddresses); } - /** - * Creates attributes based off of a parcel - * @param in the parcel - * @return the attributes - */ - @NonNull - public static EpsBearerQosSessionAttributes create(@NonNull final Parcel in) { - return new EpsBearerQosSessionAttributes(in); - } - @Override public int describeContents() { return 0; diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 9bb4db8edf79..486f74632ca2 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -277,6 +277,10 @@ public final class ImsCallProfile implements Parcelable { * server infrastructure to get the picture. It can be set via * {@link #setCallExtra(String, String)}. * + * Note that this URL is not intended to be parsed by the IMS stack -- it should be sent + * directly to the network for consumption by the called party or forwarded directly from the + * network to the platform for caching and download. + * * Reference: RCC.20 Section 2.4.3.2 */ public static final String EXTRA_PICTURE_URL = "android.telephony.ims.extra.PICTURE_URL"; @@ -729,6 +733,10 @@ public final class ImsCallProfile implements Parcelable { /** * Set the call extra value (Parcelable), given the call extra name. + * + * Note that the {@link Parcelable} provided must be a class defined in the Android API surface, + * as opposed to a class defined by your app. + * * @param name call extra name * @param parcelable call extra value */ diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index fcb4782c7f62..4b2829685d66 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -1018,7 +1018,7 @@ public class ImsMmTelManager implements RegistrationManager { @RequiresPermission(anyOf = { android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PRECISE_PHONE_STATE}) - public boolean isCrossSimCallingEnabledByUser() throws ImsException { + public boolean isCrossSimCallingEnabled() throws ImsException { ITelephony iTelephony = getITelephony(); if (iTelephony == null) { throw new ImsException("Could not find Telephony Service.", @@ -1058,7 +1058,7 @@ public class ImsMmTelManager implements RegistrationManager { * the IMS service is not available. * @param isEnabled true if the user's setting for Voice over Cross SIM is enabled, * false otherwise - * @see #isCrossSimCallingEnabledByUser() + * @see #isCrossSimCallingEnabled() * @hide */ @SystemApi diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 85cd81bb4eb5..abc5606e6743 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -1010,6 +1010,16 @@ public class ProvisioningManager { } } + @Override + public void onPreProvisioningReceived(byte[] configXml) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mLocalCallback.onPreProvisioningReceived(configXml)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + private void setExecutor(Executor executor) { mExecutor = executor; } @@ -1022,7 +1032,7 @@ public class ProvisioningManager { * due to various triggers defined in GSMA RCC.14 for ACS(auto configuration * server) or other operator defined triggers. If RCS provisioning is already * completed at the time of callback registration, then this method shall be - * invoked with the current configuration + * invoked with the current configuration. * @param configXml The RCS configuration XML received by OTA. It is defined * by GSMA RCC.07. */ @@ -1055,6 +1065,20 @@ public class ProvisioningManager { */ public void onRemoved() {} + /** + * Some carriers using ACS (auto configuration server) may send a carrier-specific + * pre-provisioning configuration XML if the user has not been provisioned for RCS + * services yet. When this provisioning XML is received, the framework will move + * into a "not provisioned" state for RCS. In order for provisioning to proceed, + * the application must parse this configuration XML and perform the carrier specific + * opt-in flow for RCS services. If the user accepts, {@link #triggerRcsReconfiguration} + * must be called in order for the device to move out of this state and try to fetch + * the RCS provisioning information. + * + * @param configXml the pre-provisioning config in carrier specified format. + */ + public void onPreProvisioningReceived(@NonNull byte[] configXml) {} + /**@hide*/ public final IRcsConfigCallback getBinder() { return mBinder; diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java index cedf48b0b8e1..9c28c36521f5 100644 --- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java +++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java @@ -25,7 +25,6 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -389,22 +388,6 @@ public final class RcsContactPresenceTuple implements Parcelable { * The optional timestamp indicating the data and time of the status change of this tuple. * Per RFC3863 section 4.1.7, the timestamp is formatted as an IMPP datetime format * string per RFC3339. - * @hide - */ - public @NonNull Builder setTimestamp(@NonNull String timestamp) { - try { - mPresenceTuple.mTimestamp = - DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp, Instant::from); - } catch (DateTimeParseException e) { - Log.d(LOG_TAG, "Parse timestamp failed " + e); - } - return this; - } - - /** - * The optional timestamp indicating the data and time of the status change of this tuple. - * Per RFC3863 section 4.1.7, the timestamp is formatted as an IMPP datetime format - * string per RFC3339. */ public @NonNull Builder setTime(@NonNull Instant timestamp) { mPresenceTuple.mTimestamp = timestamp; @@ -534,14 +517,6 @@ public final class RcsContactPresenceTuple implements Parcelable { return mContactUri; } - /** - * @return the timestamp element contained in the tuple if it exists - * @hide - */ - public @Nullable String getTimestamp() { - return (mTimestamp == null) ? null : mTimestamp.toString(); - } - /** @return the timestamp element contained in the tuple if it exists */ public @Nullable Instant getTime() { return mTimestamp; diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java index 815c08d120c2..dd9102699529 100644 --- a/telephony/java/android/telephony/ims/RcsUceAdapter.java +++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java @@ -439,8 +439,7 @@ public class RcsUceAdapter { /** * The pending request has resulted in an error and may need to be retried, depending on the - * error code. The callback {@link #onCapabilitiesReceived(List)} - * for each contacts is required to be called before {@link #onError} is called. + * error code. * @param errorCode The reason for the framework being unable to process the request. * @param retryIntervalMillis The time in milliseconds the requesting application should * wait before retrying, if non-zero. @@ -487,93 +486,6 @@ public class RcsUceAdapter { * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes. * @hide */ - @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, - Manifest.permission.READ_CONTACTS}) - public void requestCapabilities(@NonNull List<Uri> contactNumbers, - @NonNull @CallbackExecutor Executor executor, - @NonNull CapabilitiesCallback c) throws ImsException { - if (c == null) { - throw new IllegalArgumentException("Must include a non-null CapabilitiesCallback."); - } - if (executor == null) { - throw new IllegalArgumentException("Must include a non-null Executor."); - } - if (contactNumbers == null) { - throw new IllegalArgumentException("Must include non-null contact number list."); - } - - IImsRcsController imsRcsController = getIImsRcsController(); - if (imsRcsController == null) { - Log.e(TAG, "requestCapabilities: IImsRcsController is null"); - throw new ImsException("Can not find remote IMS service", - ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); - } - - IRcsUceControllerCallback internalCallback = new IRcsUceControllerCallback.Stub() { - @Override - public void onCapabilitiesReceived(List<RcsContactUceCapability> contactCapabilities) { - final long callingIdentity = Binder.clearCallingIdentity(); - try { - executor.execute(() -> c.onCapabilitiesReceived(contactCapabilities)); - } finally { - restoreCallingIdentity(callingIdentity); - } - } - @Override - public void onComplete() { - final long callingIdentity = Binder.clearCallingIdentity(); - try { - executor.execute(() -> c.onComplete()); - } finally { - restoreCallingIdentity(callingIdentity); - } - } - @Override - public void onError(int errorCode, long retryAfterMilliseconds) { - final long callingIdentity = Binder.clearCallingIdentity(); - try { - executor.execute(() -> c.onError(errorCode, retryAfterMilliseconds)); - } finally { - restoreCallingIdentity(callingIdentity); - } - } - }; - - try { - imsRcsController.requestCapabilities(mSubId, mContext.getOpPackageName(), - mContext.getAttributionTag(), contactNumbers, internalCallback); - } catch (ServiceSpecificException e) { - throw new ImsException(e.toString(), e.errorCode); - } catch (RemoteException e) { - Log.e(TAG, "Error calling IImsRcsController#requestCapabilities", e); - throw new ImsException("Remote IMS Service is not available", - ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); - } - } - - /** - * Request the User Capability Exchange capabilities for one or more contacts. - * <p> - * This will return the cached capabilities of the contact and will not perform a capability - * poll on the network unless there are contacts being queried with stale information. - * <p> - * Be sure to check the availability of this feature using - * {@link ImsRcsManager#isAvailable(int, int)} and ensuring - * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or - * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else - * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}. - * - * @param contactNumbers A list of numbers that the capabilities are being requested for. - * @param executor The executor that will be used when the request is completed and the - * {@link CapabilitiesCallback} is called. - * @param c A one-time callback for when the request for capabilities completes or there is an - * error processing the request. - * @throws ImsException if the subscription associated with this instance of - * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not - * available. This can happen if the ImsService has crashed, for example, or if the subscription - * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes. - * @hide - */ @SystemApi @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, Manifest.permission.READ_CONTACTS}) diff --git a/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl b/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl index 5a8973e37bce..d0853d1846ac 100644 --- a/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl @@ -25,5 +25,6 @@ oneway interface IRcsConfigCallback { void onAutoConfigurationErrorReceived(int errorCode, String errorString); void onConfigurationReset(); void onRemoved(); + void onPreProvisioningReceived(in byte[] config); } diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index 21aeb64bb417..d75da9035124 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -552,11 +552,34 @@ public class ImsConfigImplBase { } mRcsCallbacks.broadcastAction(c -> { try { - //TODO compressed by default? c.onAutoConfigurationErrorReceived(errorCode, errorString); } catch (RemoteException e) { Log.w(TAG, "dead binder in notifyAutoConfigurationErrorReceived, skipping."); } }); } + + /** + * Notifies application that pre-provisioning config is received. + * + * <p>Some carriers using ACS (auto configuration server) may send a carrier-specific + * pre-provisioning configuration XML if the user has not been provisioned for RCS + * services yet. When such provisioning XML is received, ACS client must call this + * method to notify the application with the XML. + * + * @param configXml the pre-provisioning config in carrier specified format. + */ + public final void notifyPreProvisioningReceived(@NonNull byte[] configXml) { + // can be null in testing + if (mRcsCallbacks == null) { + return; + } + mRcsCallbacks.broadcastAction(c -> { + try { + c.onPreProvisioningReceived(configXml); + } catch (RemoteException e) { + Log.w(TAG, "dead binder in notifyPreProvisioningReceived, skipping."); + } + }); + } } diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java index 00c91681d9ea..03e17fbc2c0d 100644 --- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java +++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java @@ -386,41 +386,6 @@ public class RcsCapabilityExchangeImplBase { * {@link SubscribeResponseCallback#onTerminated(String, long)} must be called for the * framework to finish listening for NOTIFY responses. * - * @param uris A {@link List} of the {@link Uri}s that the framework is requesting the UCE - * capabilities for. - * @param cb The callback of the subscribe request. - * @hide - */ - // executor used is defined in the constructor. - @SuppressLint("ExecutorRegistration") - public void subscribeForCapabilities(@NonNull List<Uri> uris, - @NonNull SubscribeResponseCallback cb) { - // Stub - to be implemented by service - Log.w(LOG_TAG, "subscribeForCapabilities called with no implementation."); - try { - cb.onCommandError(COMMAND_CODE_NOT_SUPPORTED); - } catch (ImsException e) { - // Do not do anything, this is a stub implementation. - } - } - - /** - * The user capabilities of one or multiple contacts have been requested by the framework. - * <p> - * The implementer must follow up this call with an - * {@link SubscribeResponseCallback#onCommandError} call to indicate this operation has failed. - * The response from the network to the SUBSCRIBE request must be sent back to the framework - * using {@link SubscribeResponseCallback#onNetworkResponse(int, String)}. - * As NOTIFY requests come in from the network, the requested contact’s capabilities should be - * sent back to the framework using - * {@link SubscribeResponseCallback#onNotifyCapabilitiesUpdate(List<String>}) and - * {@link SubscribeResponseCallback#onResourceTerminated(List<Pair<Uri, String>>)} - * should be called with the presence information for the contacts specified. - * <p> - * Once the subscription is terminated, - * {@link SubscribeResponseCallback#onTerminated(String, long)} must be called for the - * framework to finish listening for NOTIFY responses. - * * @param uris A {@link Collection} of the {@link Uri}s that the framework is requesting the * UCE capabilities for. * @param cb The callback of the subscribe request. diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 571efcee0e15..9493c76d9a57 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -300,4 +300,6 @@ interface ISub { boolean canDisablePhysicalSubscription(); int setUiccApplicationsEnabled(boolean enabled, int subscriptionId); + + int setDeviceToDeviceStatusSharing(int sharing, int subId); } diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml index b2719fbcac82..896ec9ae922c 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTest.xml @@ -11,6 +11,8 @@ <option name="force-skip-system-props" value="true" /> <!-- set WM tracing verbose level to all --> <option name="run-command" value="cmd window tracing level all" /> + <!-- set WM tracing to frame (avoid incomplete states) --> + <option name="run-command" value="cmd window tracing frame" /> <!-- restart launcher to activate TAPL --> <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" /> </target_preparer> diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index fbf18d45afd8..c92d40cdd555 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -16,37 +16,12 @@ package com.android.server.wm.flicker.close -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.launcherReplacesAppWindowAsTopWindow -import com.android.server.wm.flicker.wallpaperWindowBecomesVisible -import com.android.server.wm.flicker.wallpaperLayerReplacesAppLayer -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.repetitions -import com.android.server.wm.flicker.startRotation -import org.junit.Assume import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -59,110 +34,15 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class CloseAppBackButtonTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = SimpleAppHelper(instrumentation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } - setup { - test { - device.wakeUpAndGoToHomeScreen() - } - eachRun { - this.setRotation(testSpec.config.startRotation) - testApp.launchViaIntent(wmHelper) - } - } +class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) transitions { device.pressBack() wmHelper.waitForHomeActivityVisible() } - teardown { - eachRun { - this.setRotation(Surface.ROTATION_0) - } - test { - testApp.exit() - } - } } - } - - @Presubmit - @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun launcherReplacesAppWindowAsTopWindow() = - testSpec.launcherReplacesAppWindowAsTopWindow(testApp) - - @Presubmit - @Test - fun wallpaperWindowBecomesVisible() = testSpec.wallpaperWindowBecomesVisible() - - @Presubmit - @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, - Surface.ROTATION_0) - - @Presubmit - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun wallpaperLayerReplacesAppLayer() = testSpec.wallpaperLayerReplacesAppLayer(testApp) - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest - @Test - fun navBarLayerRotatesAndScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @Presubmit - @Test - fun statusBarLayerRotatesScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest - @Test - fun statusBarLayerRotatesScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest(bugId = 173684672) - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() - - @FlakyTest(bugId = 173684672) - @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index 08d2b7c206bf..1f880f61d65e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -16,37 +16,12 @@ package com.android.server.wm.flicker.close -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.launcherReplacesAppWindowAsTopWindow -import com.android.server.wm.flicker.wallpaperWindowBecomesVisible -import com.android.server.wm.flicker.wallpaperLayerReplacesAppLayer -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.repetitions -import com.android.server.wm.flicker.startRotation -import org.junit.Assume import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -59,110 +34,15 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class CloseAppHomeButtonTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = SimpleAppHelper(instrumentation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } - setup { - test { - device.wakeUpAndGoToHomeScreen() - } - eachRun { - testApp.launchViaIntent(wmHelper) - this.setRotation(testSpec.config.startRotation) - } - } +class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) transitions { device.pressHome() wmHelper.waitForHomeActivityVisible() } - teardown { - eachRun { - this.setRotation(Surface.ROTATION_0) - } - test { - testApp.exit() - } - } } - } - - @Presubmit - @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun launcherReplacesAppWindowAsTopWindow() = - testSpec.launcherReplacesAppWindowAsTopWindow(testApp) - - @Presubmit - @Test - fun wallpaperWindowBecomesVisible() = testSpec.wallpaperWindowBecomesVisible() - - @Presubmit - @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions( - testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun wallpaperLayerReplacesAppLayer() = testSpec.wallpaperLayerReplacesAppLayer(testApp) - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest - @Test - fun navBarLayerRotatesAndScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @Presubmit - @Test - fun statusBarLayerRotatesScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest - @Test - fun statusBarLayerRotatesScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest(bugId = 173689015) - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() - - @FlakyTest(bugId = 173689015) - @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt new file mode 100644 index 000000000000..fef49d9433a8 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -0,0 +1,157 @@ +/* + * 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.flicker.close + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.view.Surface +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 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.StandardAppHelper +import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.launcherReplacesAppWindowAsTopWindow +import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerRotatesScales +import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry +import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.server.wm.flicker.wallpaperLayerReplacesAppLayer +import com.android.server.wm.flicker.wallpaperWindowBecomesVisible +import org.junit.Assume +import org.junit.Test + +abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) { + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) + protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { + setup { + eachRun { + testApp.launchViaIntent(wmHelper) + this.setRotation(testSpec.config.startRotation) + } + } + teardown { + test { + testApp.exit() + } + } + } + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + transition(testSpec.config) + } + } + + @Presubmit + @Test + open fun navBarWindowIsAlwaysVisible() { + testSpec.navBarWindowIsAlwaysVisible() + } + + @Presubmit + @Test + open fun statusBarWindowIsAlwaysVisible() { + testSpec.statusBarWindowIsAlwaysVisible() + } + + @Presubmit + @Test + open fun navBarLayerIsAlwaysVisible() { + testSpec.navBarLayerIsAlwaysVisible() + } + + @Presubmit + @Test + open fun statusBarLayerIsAlwaysVisible() { + testSpec.statusBarLayerIsAlwaysVisible() + } + + @Presubmit + @Test + open fun navBarLayerRotatesAndScales() { + Assume.assumeFalse(testSpec.isRotated) + testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + } + + @FlakyTest + @Test + open fun navBarLayerRotatesAndScales_Flaky() { + Assume.assumeTrue(testSpec.isRotated) + testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + } + + @Presubmit + @Test + open fun statusBarLayerRotatesScales() { + Assume.assumeFalse(testSpec.isRotated) + testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + } + + @FlakyTest + @Test + open fun statusBarLayerRotatesScales_Flaky() { + Assume.assumeTrue(testSpec.isRotated) + testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + } + + @FlakyTest(bugId = 173689015) + @Test + open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + @FlakyTest(bugId = 173689015) + @Test + open fun visibleLayersShownMoreThanOneConsecutiveEntry() { + testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + @Presubmit + @Test + open fun noUncoveredRegions() { + testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) + } + + @Presubmit + @Test + open fun launcherReplacesAppWindowAsTopWindow() { + testSpec.launcherReplacesAppWindowAsTopWindow(testApp) + } + + @Presubmit + @Test + open fun wallpaperWindowBecomesVisible() { + testSpec.wallpaperWindowBecomesVisible() + } + + @Presubmit + @Test + open fun wallpaperLayerReplacesAppLayer() { + testSpec.wallpaperLayerReplacesAppLayer(testApp) + } +}
\ No newline at end of file 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 74f002d67229..56ed21b70b82 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,37 +16,14 @@ package com.android.server.wm.flicker.launch -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.wallpaperWindowBecomesInvisible -import com.android.server.wm.flicker.appLayerReplacesWallpaperLayer import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.SimpleAppHelper -import org.junit.Assume import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -59,114 +36,25 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppColdTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = SimpleAppHelper(instrumentation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } +class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) setup { - test { - device.wakeUpAndGoToHomeScreen() - } eachRun { this.setRotation(testSpec.config.startRotation) } } - transitions { - testApp.launchViaIntent(wmHelper) - // wmHelper.waitForFullScreenApp(testApp.component) - } teardown { eachRun { - testApp.exit() - wmHelper.waitForAppTransitionIdle() - this.setRotation(Surface.ROTATION_0) + testApp.exit(wmHelper) } } + transitions { + testApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp.component) + } } - } - - @Presubmit - @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - fun appWindowReplacesLauncherAsTopWindow() = - testSpec.appWindowReplacesLauncherAsTopWindow(testApp) - - @Presubmit - @Test - fun wallpaperWindowBecomesInvisible() = testSpec.wallpaperWindowBecomesInvisible() - - @Presubmit - @Test - // During testing the launcher is always in portrait mode - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, - Surface.ROTATION_0) - - @Presubmit - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun appLayerReplacesWallpaperLayer() = - testSpec.appLayerReplacesWallpaperLayer(testApp.`package`) - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest - @Test - fun navBarLayerRotatesAndScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @Presubmit - @Test - fun statusBarLayerRotatesScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest - @Test - fun statusBarLayerRotatesScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @Presubmit - @Test - fun focusChanges() = testSpec.focusChanges("NexusLauncherActivity", testApp.`package`) - - @FlakyTest - @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") 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 18fac6a82de7..4a32a9eb3851 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 @@ -16,36 +16,19 @@ package com.android.server.wm.flicker.launch -import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.endRotation -import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.wallpaperWindowBecomesInvisible -import com.android.server.wm.flicker.appLayerReplacesWallpaperLayer import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.SimpleAppHelper import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -61,18 +44,12 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppFromOverviewTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = SimpleAppHelper(instrumentation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } +class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) setup { test { - device.wakeUpAndGoToHomeScreen() testApp.launchViaIntent(wmHelper) } eachRun { @@ -87,71 +64,50 @@ class OpenAppFromOverviewTest(private val testSpec: FlickerTestParameter) { device.reopenAppFromOverview(wmHelper) wmHelper.waitForFullScreenApp(testApp.component) } - teardown { - test { - testApp.exit() - } - } } - } - @Presubmit - @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + override fun appWindowReplacesLauncherAsTopWindow() = + super.appWindowReplacesLauncherAsTopWindow() @Test - fun appWindowReplacesLauncherAsTopWindow() = - testSpec.appWindowReplacesLauncherAsTopWindow(testApp) - - @Test - fun wallpaperWindowBecomesInvisible() = testSpec.wallpaperWindowBecomesInvisible() - - @Presubmit - @Test - fun appLayerReplacesWallpaperLayer() = - testSpec.appLayerReplacesWallpaperLayer(testApp.`package`) - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + override fun wallpaperWindowBecomesInvisible() { + testSpec.wallpaperWindowBecomesInvisible() } @Presubmit @Test - fun statusBarLayerRotatesScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + override fun statusBarLayerIsAlwaysVisible() { + Assume.assumeTrue(testSpec.isRotated) + super.statusBarLayerIsAlwaysVisible() } @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() { + override fun navBarLayerIsAlwaysVisible() { Assume.assumeTrue(testSpec.isRotated) - testSpec.statusBarLayerIsAlwaysVisible() + super.navBarLayerIsAlwaysVisible() } - @Presubmit + @FlakyTest @Test - fun navBarLayerIsAlwaysVisible() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.navBarLayerIsAlwaysVisible() + fun statusBarLayerIsAlwaysVisible_Flaky() { + Assume.assumeFalse(testSpec.isRotated) + super.statusBarLayerIsAlwaysVisible() } - @Presubmit + @FlakyTest @Test - fun focusChanges() = testSpec.focusChanges("NexusLauncherActivity", testApp.`package`) + fun navBarLayerIsAlwaysVisible_Flaky() { + Assume.assumeFalse(testSpec.isRotated) + super.navBarLayerIsAlwaysVisible() + } @Presubmit @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { Assume.assumeFalse(testSpec.isRotated) - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() + super.visibleWindowsShownMoreThanOneConsecutiveEntry() } @FlakyTest @@ -163,9 +119,9 @@ class OpenAppFromOverviewTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() { + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { Assume.assumeFalse(testSpec.isRotated) - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() + super.visibleLayersShownMoreThanOneConsecutiveEntry() } @FlakyTest @@ -175,11 +131,6 @@ class OpenAppFromOverviewTest(private val testSpec: FlickerTestParameter) { testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() } - @Presubmit - @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(Surface.ROTATION_0, - testSpec.config.endRotation) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt new file mode 100644 index 000000000000..e9f053452589 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt @@ -0,0 +1,170 @@ +/* + * 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.flicker.launch + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.view.Surface +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 +import com.android.server.wm.flicker.appLayerReplacesWallpaperLayer +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.endRotation +import com.android.server.wm.flicker.focusChanges +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.StandardAppHelper +import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.repetitions +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerRotatesScales +import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry +import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.server.wm.flicker.wallpaperWindowBecomesInvisible +import org.junit.Assume +import org.junit.Test + +abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + protected val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) + + protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { + withTestName { testSpec.name } + repeat { testSpec.config.repetitions } + setup { + test { + device.wakeUpAndGoToHomeScreen() + this.setRotation(testSpec.config.startRotation) + } + } + teardown { + test { + testApp.exit() + } + } + } + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + transition(testSpec.config) + } + } + + @Presubmit + @Test + open fun navBarWindowIsAlwaysVisible() { + testSpec.navBarWindowIsAlwaysVisible() + } + + @Presubmit + @Test + open fun navBarLayerIsAlwaysVisible() { + testSpec.navBarLayerIsAlwaysVisible() + } + + @Presubmit + @Test + open fun navBarLayerRotatesAndScales() { + Assume.assumeFalse(testSpec.isRotated) + testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) + } + + @FlakyTest + @Test + open fun navBarLayerRotatesAndScales_Flaky() { + Assume.assumeTrue(testSpec.isRotated) + testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) + } + + @Presubmit + @Test + open fun statusBarWindowIsAlwaysVisible() { + testSpec.statusBarWindowIsAlwaysVisible() + } + + @Presubmit + @Test + open fun statusBarLayerIsAlwaysVisible() { + testSpec.statusBarLayerIsAlwaysVisible() + } + + @Presubmit + @Test + open fun statusBarLayerRotatesScales() { + Assume.assumeFalse(testSpec.isRotated) + testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation) + } + + @FlakyTest + @Test + open fun statusBarLayerRotatesScales_Flaky() { + Assume.assumeTrue(testSpec.isRotated) + testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation) + } + + @Presubmit + @Test + open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + @FlakyTest + @Test + open fun visibleLayersShownMoreThanOneConsecutiveEntry() { + testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + @Presubmit + @Test + // During testing the launcher is always in portrait mode + open fun noUncoveredRegions() { + testSpec.noUncoveredRegions(Surface.ROTATION_0, testSpec.config.endRotation) + } + + @Presubmit + @Test + open fun focusChanges() { + testSpec.focusChanges("NexusLauncherActivity", testApp.`package`) + } + + @Presubmit + @Test + open fun appLayerReplacesWallpaperLayer() { + testSpec.appLayerReplacesWallpaperLayer(testApp.`package`) + } + + @Presubmit + @Test + open fun appWindowReplacesLauncherAsTopWindow() { + testSpec.appWindowReplacesLauncherAsTopWindow(testApp) + } + + @Presubmit + @Test + open fun wallpaperWindowBecomesInvisible() { + testSpec.wallpaperWindowBecomesInvisible() + } +}
\ No newline at end of file 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 b61310aa4bd8..a8b5ea1604ec 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,34 +16,14 @@ package com.android.server.wm.flicker.launch -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.wallpaperWindowBecomesInvisible -import com.android.server.wm.flicker.appLayerReplacesWallpaperLayer import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.SimpleAppHelper -import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -58,20 +38,13 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppWarmTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = SimpleAppHelper(instrumentation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } +class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) setup { test { - device.wakeUpAndGoToHomeScreen() testApp.launchViaIntent(wmHelper) - // wmHelper.waitForFullScreenApp(testApp.component) } eachRun { device.pressHome() @@ -79,93 +52,21 @@ class OpenAppWarmTest(private val testSpec: FlickerTestParameter) { this.setRotation(testSpec.config.startRotation) } } - transitions { - testApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp.component) - } teardown { eachRun { - this.setRotation(Surface.ROTATION_0) - } - test { - testApp.exit() + testApp.exit(wmHelper) } } + transitions { + testApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp.component) + } } - } - - @Presubmit - @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @FlakyTest - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - fun appWindowReplacesLauncherAsTopWindow() = - testSpec.appWindowReplacesLauncherAsTopWindow(testApp) - - @Presubmit - @Test - fun wallpaperWindowBecomesInvisible() = testSpec.wallpaperWindowBecomesInvisible() - - @Presubmit - @Test - // During testing the launcher is always in portrait mode - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, - Surface.ROTATION_0) - - @Presubmit - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun appLayerReplacesWallpaperLayer() = - testSpec.appLayerReplacesWallpaperLayer(testApp.`package`) - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } @FlakyTest @Test - fun navBarLayerRotatesAndScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @Presubmit - @Test - fun statusBarLayerRotatesScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest - @Test - fun statusBarLayerRotatesScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @Presubmit - @Test - fun focusChanges() = testSpec.focusChanges("NexusLauncherActivity", testApp.`package`) + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index 335c8d0127eb..eacf5b287a2e 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -9,14 +9,17 @@ package { android_test { name: "InputTests", - srcs: ["src/**/*.kt"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], platform_apis: true, certificate: "platform", static_libs: [ - "androidx.test.ext.junit", - "androidx.test.rules", - "truth-prebuilt", - "ub-uiautomator", - ], + "androidx.test.ext.junit", + "androidx.test.rules", + "truth-prebuilt", + "ub-uiautomator", + ], test_suites: ["device-tests"], } diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java new file mode 100644 index 000000000000..63500774816a --- /dev/null +++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.os.Parcel; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class InputDeviceTest { + private static final float DELTA = 0.01f; + private static final int DEVICE_ID = 1000; + + private void assertMotionRangeEquals(InputDevice.MotionRange range, + InputDevice.MotionRange outRange) { + assertEquals(range.getAxis(), outRange.getAxis()); + assertEquals(range.getSource(), outRange.getSource()); + assertEquals(range.getMin(), outRange.getMin(), DELTA); + assertEquals(range.getMax(), outRange.getMax(), DELTA); + assertEquals(range.getFlat(), outRange.getFlat(), DELTA); + assertEquals(range.getFuzz(), outRange.getFuzz(), DELTA); + assertEquals(range.getResolution(), outRange.getResolution(), DELTA); + } + + private void assertDeviceEquals(InputDevice device, InputDevice outDevice) { + assertEquals(device.getId(), outDevice.getId()); + assertEquals(device.getGeneration(), outDevice.getGeneration()); + assertEquals(device.getControllerNumber(), outDevice.getControllerNumber()); + assertEquals(device.getName(), outDevice.getName()); + assertEquals(device.getVendorId(), outDevice.getVendorId()); + assertEquals(device.getProductId(), outDevice.getProductId()); + assertEquals(device.getDescriptor(), outDevice.getDescriptor()); + assertEquals(device.isExternal(), outDevice.isExternal()); + assertEquals(device.getSources(), outDevice.getSources()); + assertEquals(device.getKeyboardType(), outDevice.getKeyboardType()); + assertEquals(device.getMotionRanges().size(), outDevice.getMotionRanges().size()); + + KeyCharacterMap keyCharacterMap = device.getKeyCharacterMap(); + KeyCharacterMap outKeyCharacterMap = outDevice.getKeyCharacterMap(); + assertTrue("keyCharacterMap not equal", keyCharacterMap.equals(outKeyCharacterMap)); + + for (int j = 0; j < device.getMotionRanges().size(); j++) { + assertMotionRangeEquals(device.getMotionRanges().get(j), + outDevice.getMotionRanges().get(j)); + } + } + + private void assertInputDeviceParcelUnparcel(KeyCharacterMap keyCharacterMap) { + final InputDevice device = + new InputDevice(DEVICE_ID, 0 /* generation */, 0 /* controllerNumber */, "name", + 0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */, + 0 /* sources */, 0 /* keyboardType */, keyCharacterMap, + false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */, + true /* hasSensor */, false /* hasBattery */); + + Parcel parcel = Parcel.obtain(); + device.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + InputDevice outDevice = InputDevice.CREATOR.createFromParcel(parcel); + assertDeviceEquals(device, outDevice); + } + + @Test + public void testParcelUnparcelInputDevice_VirtualCharacterMap() { + final KeyCharacterMap keyCharacterMap = + KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + assertInputDeviceParcelUnparcel(keyCharacterMap); + } + + @Test + public void testParcelUnparcelInputDevice_EmptyCharacterMap() { + final KeyCharacterMap keyCharacterMap = KeyCharacterMap.obtainEmptyMap(DEVICE_ID); + assertInputDeviceParcelUnparcel(keyCharacterMap); + } +} diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt index 2e985fbba269..b9b347b02958 100644 --- a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt +++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt @@ -43,7 +43,7 @@ fun createMotionEvent(action: Int, eventTime: Long, source: Int): MotionEvent { xPrecision, yPrecision, deviceId, edgeFlags, source, displayId) } -fun createKeyEvent(action: Int, eventTime: Long): KeyEvent { +private fun createKeyEvent(action: Int, eventTime: Long): KeyEvent { val code = KeyEvent.KEYCODE_A val repeat = 0 return KeyEvent(eventTime, eventTime, action, code, repeat) diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt new file mode 100644 index 000000000000..4f95ce585de2 --- /dev/null +++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.input + +import android.os.HandlerThread +import android.os.Looper +import android.view.InputChannel +import android.view.InputEvent +import android.view.InputEventReceiver +import android.view.InputEventSender +import android.view.KeyEvent +import android.view.MotionEvent +import java.util.concurrent.CountDownLatch +import org.junit.Assert.assertEquals +import org.junit.After +import org.junit.Before +import org.junit.Test + +private fun assertKeyEvent(expected: KeyEvent, received: KeyEvent) { + assertEquals(expected.action, received.action) + assertEquals(expected.deviceId, received.deviceId) + assertEquals(expected.downTime, received.downTime) + assertEquals(expected.eventTime, received.eventTime) + assertEquals(expected.keyCode, received.keyCode) + assertEquals(expected.scanCode, received.scanCode) + assertEquals(expected.repeatCount, received.repeatCount) + assertEquals(expected.metaState, received.metaState) + assertEquals(expected.flags, received.flags) + assertEquals(expected.source, received.source) + assertEquals(expected.displayId, received.displayId) +} + +class TestInputEventReceiver(channel: InputChannel, looper: Looper) : + InputEventReceiver(channel, looper) { + companion object { + const val TAG = "TestInputEventReceiver" + } + + var lastEvent: InputEvent? = null + + override fun onInputEvent(event: InputEvent) { + lastEvent = when (event) { + is KeyEvent -> KeyEvent.obtain(event) + is MotionEvent -> MotionEvent.obtain(event) + else -> throw Exception("Received $event is neither a key nor a motion") + } + finishInputEvent(event, true /*handled*/) + } +} + +class TestInputEventSender(channel: InputChannel, looper: Looper) : + InputEventSender(channel, looper) { + companion object { + const val TAG = "TestInputEventSender" + } + data class FinishedResult(val seq: Int, val handled: Boolean) + + private var mFinishedSignal = CountDownLatch(1) + override fun onInputEventFinished(seq: Int, handled: Boolean) { + finishedResult = FinishedResult(seq, handled) + mFinishedSignal.countDown() + } + lateinit var finishedResult: FinishedResult + + fun waitForFinish() { + mFinishedSignal.await() + mFinishedSignal = CountDownLatch(1) // Ready for next event + } +} + +class InputEventSenderAndReceiverTest { + companion object { + private const val TAG = "InputEventSenderAndReceiverTest" + } + private val mHandlerThread = HandlerThread("Process input events") + private lateinit var mReceiver: TestInputEventReceiver + private lateinit var mSender: TestInputEventSender + + @Before + fun setUp() { + val channels = InputChannel.openInputChannelPair("TestChannel") + mHandlerThread.start() + + val looper = mHandlerThread.getLooper() + mSender = TestInputEventSender(channels[0], looper) + mReceiver = TestInputEventReceiver(channels[1], looper) + } + + @After + fun tearDown() { + mHandlerThread.quitSafely() + } + + @Test + fun testSendAndReceiveKey() { + val key = KeyEvent(1 /*downTime*/, 1 /*eventTime*/, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_A, 0 /*repeat*/) + val seq = 10 + mSender.sendInputEvent(seq, key) + mSender.waitForFinish() + + // Check receiver + assertKeyEvent(key, mReceiver.lastEvent!! as KeyEvent) + + // Check sender + assertEquals(seq, mSender.finishedResult.seq) + assertEquals(true, mSender.finishedResult.handled) + } +} diff --git a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java index d6846faa5c00..e121b68ca8fa 100644 --- a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java +++ b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java @@ -94,17 +94,16 @@ public class PlatformCompatChangeRule extends CoreCompatChangeRule { if (platformCompat == null) { throw new IllegalStateException("Could not get IPlatformCompat service!"); } - uiAutomation.adoptShellPermissionIdentity( - Manifest.permission.LOG_COMPAT_CHANGE, - Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG, - Manifest.permission.READ_COMPAT_CHANGE_CONFIG); + adoptShellPermissions(uiAutomation); Compatibility.setOverrides(mConfig); try { platformCompat.setOverridesForTest(new CompatibilityChangeConfig(mConfig), packageName); try { + uiAutomation.dropShellPermissionIdentity(); mTestStatement.evaluate(); } finally { + adoptShellPermissions(uiAutomation); platformCompat.clearOverridesForTest(packageName); } } catch (RemoteException e) { @@ -114,5 +113,12 @@ public class PlatformCompatChangeRule extends CoreCompatChangeRule { Compatibility.clearOverrides(); } } + + private static void adoptShellPermissions(UiAutomation uiAutomation) { + uiAutomation.adoptShellPermissionIdentity( + Manifest.permission.LOG_COMPAT_CHANGE, + Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG, + Manifest.permission.READ_COMPAT_CHANGE_CONFIG); + } } } diff --git a/tests/RollbackTest/MultiUserRollbackTest.xml b/tests/RollbackTest/MultiUserRollbackTest.xml index 2f62af1856da..8fa0510d9e71 100644 --- a/tests/RollbackTest/MultiUserRollbackTest.xml +++ b/tests/RollbackTest/MultiUserRollbackTest.xml @@ -20,6 +20,8 @@ <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="run-command" value="setprop persist.rollback.is_test 1" /> + <option name="teardown-command" value="setprop persist.rollback.is_test 0" /> </target_preparer> <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="com.android.tests.rollback.host.MultiUserRollbackTest" /> diff --git a/tests/RollbackTest/NetworkStagedRollbackTest.xml b/tests/RollbackTest/NetworkStagedRollbackTest.xml index 2ab907a59298..13f603140aee 100644 --- a/tests/RollbackTest/NetworkStagedRollbackTest.xml +++ b/tests/RollbackTest/NetworkStagedRollbackTest.xml @@ -24,6 +24,8 @@ <option name="run-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es package "com.google.android.gms.platformconfigurator" --es user '\\*' --esa flags "ModuleConfig__versioned_immediate_commit_packages" --esa types "bytes" --esa values "Cm5vdGFwYWNrYWdlOgA=" com.google.android.gms" /> <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package "com.google.android.gms.platformconfigurator" --es user '\*' --esa flag "ModuleConfig__immediate_commit_packages" com.google.android.gms" /> <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package "com.google.android.gms.platformconfigurator" --es user '\*' --esa flag "ModuleConfig__versioned_immediate_commit_packages" com.google.android.gms" /> + <option name="run-command" value="setprop persist.rollback.is_test 1" /> + <option name="teardown-command" value="setprop persist.rollback.is_test 0" /> </target_preparer> <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="com.android.tests.rollback.host.NetworkStagedRollbackTest" /> diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml index 7b85cc84f1f5..fbb6e46e1721 100644 --- a/tests/RollbackTest/RollbackTest.xml +++ b/tests/RollbackTest/RollbackTest.xml @@ -27,6 +27,8 @@ <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="run-command" value="setprop persist.rollback.is_test 1" /> + <option name="teardown-command" value="setprop persist.rollback.is_test 0" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.tests.rollback" /> diff --git a/tests/RollbackTest/StagedRollbackTest.xml b/tests/RollbackTest/StagedRollbackTest.xml index 83fef8e0a04b..0ca4dafae59d 100644 --- a/tests/RollbackTest/StagedRollbackTest.xml +++ b/tests/RollbackTest/StagedRollbackTest.xml @@ -24,6 +24,8 @@ <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="run-command" value="setprop persist.rollback.is_test 1" /> + <option name="teardown-command" value="setprop persist.rollback.is_test 0" /> </target_preparer> <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="com.android.tests.rollback.host.StagedRollbackTest" /> diff --git a/tests/net/Android.bp b/tests/net/Android.bp index 7f0318a135dc..e1a424f214a5 100644 --- a/tests/net/Android.bp +++ b/tests/net/Android.bp @@ -79,6 +79,7 @@ android_test { "android.test.runner", "android.test.base", "android.test.mock", + "ServiceConnectivityResources", ], jni_libs: [ "libservice-connectivity", diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index ddc31bfc20b8..0dfec7592274 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -28,6 +28,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; @@ -44,6 +45,10 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES; import static android.os.Process.INVALID_UID; +import static com.android.modules.utils.build.SdkLevel.isAtLeastR; +import static com.android.modules.utils.build.SdkLevel.isAtLeastS; +import static com.android.testutils.MiscAsserts.assertEmpty; +import static com.android.testutils.MiscAsserts.assertThrows; import static com.android.testutils.ParcelUtils.assertParcelSane; import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; import static com.android.testutils.ParcelUtils.parcelingRoundTrip; @@ -67,7 +72,7 @@ import android.util.ArraySet; import androidx.test.runner.AndroidJUnit4; -import com.android.modules.utils.build.SdkLevel; +import com.android.testutils.CompatUtil; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; @@ -84,6 +89,9 @@ import java.util.Set; public class NetworkCapabilitiesTest { private static final String TEST_SSID = "TEST_SSID"; private static final String DIFFERENT_TEST_SSID = "DIFFERENT_TEST_SSID"; + private static final int TEST_SUBID1 = 1; + private static final int TEST_SUBID2 = 2; + private static final int TEST_SUBID3 = 3; @Rule public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); @@ -91,14 +99,6 @@ public class NetworkCapabilitiesTest { private DiscoverySession mDiscoverySession = Mockito.mock(DiscoverySession.class); private PeerHandle mPeerHandle = Mockito.mock(PeerHandle.class); - private boolean isAtLeastR() { - return SdkLevel.isAtLeastR(); - } - - private boolean isAtLeastS() { - return SdkLevel.isAtLeastS(); - } - @Test public void testMaybeMarkCapabilitiesRestricted() { // verify EIMS is restricted @@ -211,7 +211,7 @@ public class NetworkCapabilitiesTest { nc1 = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI); nc2 = new NetworkCapabilities() .addTransportType(TRANSPORT_WIFI) - .setNetworkSpecifier(new EthernetNetworkSpecifier("eth42")); + .setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier("eth42")); assertNotEquals("", nc1.describeImmutableDifferences(nc2)); assertEquals("", nc1.describeImmutableDifferences(nc1)); } @@ -304,7 +304,9 @@ public class NetworkCapabilitiesTest { .setUids(uids) .addCapability(NET_CAPABILITY_EIMS) .addCapability(NET_CAPABILITY_NOT_METERED); - if (isAtLeastR()) { + if (isAtLeastS()) { + netCap.setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2)); + } else if (isAtLeastR()) { netCap.setOwnerUid(123); netCap.setAdministratorUids(new int[] {5, 11}); } @@ -379,7 +381,7 @@ public class NetworkCapabilitiesTest { private void testParcelSane(NetworkCapabilities cap) { if (isAtLeastS()) { - assertParcelSane(cap, 16); + assertParcelSane(cap, 17); } else if (isAtLeastR()) { assertParcelSane(cap, 15); } else { @@ -613,6 +615,20 @@ public class NetworkCapabilitiesTest { assertFalse(nc2.appliesToUid(12)); assertTrue(nc1.appliesToUid(22)); assertTrue(nc2.appliesToUid(22)); + + // Verify the subscription id list can be combined only when they are equal. + if (isAtLeastS()) { + nc1.setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2)); + nc2.setSubIds(Set.of(TEST_SUBID2)); + assertThrows(IllegalStateException.class, () -> nc2.combineCapabilities(nc1)); + + nc2.setSubIds(Set.of()); + assertThrows(IllegalStateException.class, () -> nc2.combineCapabilities(nc1)); + + nc2.setSubIds(Set.of(TEST_SUBID2, TEST_SUBID1)); + nc2.combineCapabilities(nc1); + assertEquals(Set.of(TEST_SUBID2, TEST_SUBID1), nc2.getSubIds()); + } } @Test @IgnoreUpTo(Build.VERSION_CODES.Q) @@ -671,7 +687,7 @@ public class NetworkCapabilitiesTest { NetworkCapabilities nc1 = new NetworkCapabilities(); nc1.addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI); try { - nc1.setNetworkSpecifier(new EthernetNetworkSpecifier("eth0")); + nc1.setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier("eth0")); fail("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!"); } catch (IllegalStateException expected) { // empty @@ -680,7 +696,7 @@ public class NetworkCapabilitiesTest { // Sequence 2: Transport + NetworkSpecifier + Transport NetworkCapabilities nc2 = new NetworkCapabilities(); nc2.addTransportType(TRANSPORT_CELLULAR).setNetworkSpecifier( - new EthernetNetworkSpecifier("testtap3")); + CompatUtil.makeEthernetNetworkSpecifier("testtap3")); try { nc2.addTransportType(TRANSPORT_WIFI); fail("Cannot set a second TransportType of a network which has a NetworkSpecifier!"); @@ -761,6 +777,24 @@ public class NetworkCapabilitiesTest { nc1.setUids(uidRange(10, 13)); nc2.set(nc1); // Overwrites, as opposed to combineCapabilities assertEquals(nc1, nc2); + + if (isAtLeastS()) { + assertThrows(NullPointerException.class, () -> nc1.setSubIds(null)); + nc1.setSubIds(Set.of()); + nc2.set(nc1); + assertEquals(nc1, nc2); + + nc1.setSubIds(Set.of(TEST_SUBID1)); + nc2.set(nc1); + assertEquals(nc1, nc2); + + nc2.setSubIds(Set.of(TEST_SUBID2, TEST_SUBID1)); + nc2.set(nc1); + assertEquals(nc1, nc2); + + nc2.setSubIds(Set.of(TEST_SUBID3, TEST_SUBID2)); + assertNotEquals(nc1, nc2); + } } @Test @@ -841,6 +875,50 @@ public class NetworkCapabilitiesTest { } catch (NullPointerException expected) { } } + private static NetworkCapabilities capsWithSubIds(Integer ... subIds) { + // Since the NetworkRequest would put NOT_VCN_MANAGED capabilities in general, for + // every NetworkCapabilities that simulates networks needs to add it too in order to + // satisfy these requests. + final NetworkCapabilities nc = new NetworkCapabilities.Builder() + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) + .setSubIds(new ArraySet<>(subIds)).build(); + assertEquals(new ArraySet<>(subIds), nc.getSubIds()); + return nc; + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void testSubIds() throws Exception { + final NetworkCapabilities ncWithoutId = capsWithSubIds(); + final NetworkCapabilities ncWithId = capsWithSubIds(TEST_SUBID1); + final NetworkCapabilities ncWithOtherIds = capsWithSubIds(TEST_SUBID1, TEST_SUBID3); + final NetworkCapabilities ncWithoutRequestedIds = capsWithSubIds(TEST_SUBID3); + + final NetworkRequest requestWithoutId = new NetworkRequest.Builder().build(); + assertEmpty(requestWithoutId.networkCapabilities.getSubIds()); + final NetworkRequest requestWithIds = new NetworkRequest.Builder() + .setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2)).build(); + assertEquals(Set.of(TEST_SUBID1, TEST_SUBID2), + requestWithIds.networkCapabilities.getSubIds()); + + assertFalse(requestWithIds.canBeSatisfiedBy(ncWithoutId)); + assertTrue(requestWithIds.canBeSatisfiedBy(ncWithOtherIds)); + assertFalse(requestWithIds.canBeSatisfiedBy(ncWithoutRequestedIds)); + assertTrue(requestWithIds.canBeSatisfiedBy(ncWithId)); + assertTrue(requestWithoutId.canBeSatisfiedBy(ncWithoutId)); + assertTrue(requestWithoutId.canBeSatisfiedBy(ncWithId)); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void testEqualsSubIds() throws Exception { + assertEquals(capsWithSubIds(), capsWithSubIds()); + assertNotEquals(capsWithSubIds(), capsWithSubIds(TEST_SUBID1)); + assertEquals(capsWithSubIds(TEST_SUBID1), capsWithSubIds(TEST_SUBID1)); + assertNotEquals(capsWithSubIds(TEST_SUBID1), capsWithSubIds(TEST_SUBID2)); + assertNotEquals(capsWithSubIds(TEST_SUBID1), capsWithSubIds(TEST_SUBID2, TEST_SUBID1)); + assertEquals(capsWithSubIds(TEST_SUBID1, TEST_SUBID2), + capsWithSubIds(TEST_SUBID2, TEST_SUBID1)); + } + @Test public void testLinkBandwidthKbps() { final NetworkCapabilities nc = new NetworkCapabilities(); @@ -1021,5 +1099,11 @@ public class NetworkCapabilitiesTest { fail("Should not set null into NetworkCapabilities.Builder"); } catch (NullPointerException expected) { } assertEquals(nc, new NetworkCapabilities.Builder(nc).build()); + + if (isAtLeastS()) { + final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() + .setSubIds(Set.of(TEST_SUBID1)).build(); + assertEquals(Set.of(TEST_SUBID1), nc2.getSubIds()); + } } } diff --git a/tests/net/common/java/android/net/NetworkProviderTest.kt b/tests/net/common/java/android/net/NetworkProviderTest.kt index 71a7a7c4ebd9..340e6f963137 100644 --- a/tests/net/common/java/android/net/NetworkProviderTest.kt +++ b/tests/net/common/java/android/net/NetworkProviderTest.kt @@ -27,6 +27,7 @@ import android.os.HandlerThread import android.os.Looper import androidx.test.InstrumentationRegistry import com.android.net.module.util.ArrayTrackRecord +import com.android.testutils.CompatUtil import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import com.android.testutils.DevSdkIgnoreRunner import com.android.testutils.isDevSdkInRange @@ -102,7 +103,8 @@ class NetworkProviderTest { mCm.registerNetworkProvider(provider) assertNotEquals(provider.getProviderId(), NetworkProvider.ID_NONE) - val specifier = EthernetNetworkSpecifier(UUID.randomUUID().toString()) + val specifier = CompatUtil.makeTestNetworkSpecifier( + UUID.randomUUID().toString()) val nr: NetworkRequest = NetworkRequest.Builder() .addTransportType(TRANSPORT_TEST) .setNetworkSpecifier(specifier) @@ -183,7 +185,8 @@ class NetworkProviderTest { mCm.registerNetworkProvider(provider) - val specifier = EthernetNetworkSpecifier(UUID.randomUUID().toString()) + val specifier = CompatUtil.makeTestNetworkSpecifier( + UUID.randomUUID().toString()) val nr: NetworkRequest = NetworkRequest.Builder() .addTransportType(TRANSPORT_TEST) .setNetworkSpecifier(specifier) diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index e1da3d0ae2b3..01d8186c7d1b 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -64,6 +64,7 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { private final HandlerThread mHandlerThread; private final Context mContext; private final String mLogTag; + private final NetworkAgentConfig mNetworkAgentConfig; private final ConditionVariable mDisconnected = new ConditionVariable(); private final ConditionVariable mPreventReconnectReceived = new ConditionVariable(); @@ -115,13 +116,19 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { mHandlerThread = new HandlerThread(mLogTag); mHandlerThread.start(); - mNetworkAgent = makeNetworkAgent(linkProperties, type, typeName); + // extraInfo is set to "" by default in NetworkAgentConfig. + final String extraInfo = (transport == TRANSPORT_CELLULAR) ? "internet.apn" : ""; + mNetworkAgentConfig = new NetworkAgentConfig.Builder() + .setLegacyType(type) + .setLegacyTypeName(typeName) + .setLegacyExtraInfo(extraInfo) + .build(); + mNetworkAgent = makeNetworkAgent(linkProperties, mNetworkAgentConfig); } protected InstrumentedNetworkAgent makeNetworkAgent(LinkProperties linkProperties, - final int type, final String typeName) - throws Exception { - return new InstrumentedNetworkAgent(this, linkProperties, type, typeName); + final NetworkAgentConfig nac) throws Exception { + return new InstrumentedNetworkAgent(this, linkProperties, nac); } public static class InstrumentedNetworkAgent extends NetworkAgent { @@ -129,11 +136,9 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { private static final String PROVIDER_NAME = "InstrumentedNetworkAgentProvider"; public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp, - final int type, final String typeName) { + NetworkAgentConfig nac) { super(wrapper.mContext, wrapper.mHandlerThread.getLooper(), wrapper.mLogTag, - wrapper.mNetworkCapabilities, lp, wrapper.mScore, - new NetworkAgentConfig.Builder() - .setLegacyType(type).setLegacyTypeName(typeName).build(), + wrapper.mNetworkCapabilities, lp, wrapper.mScore, nac, new NetworkProvider(wrapper.mContext, wrapper.mHandlerThread.getLooper(), PROVIDER_NAME)); mWrapper = wrapper; @@ -301,6 +306,14 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { return mNetworkCapabilities; } + public int getLegacyType() { + return mNetworkAgentConfig.getLegacyType(); + } + + public String getExtraInfo() { + return mNetworkAgentConfig.getLegacyExtraInfo(); + } + public @NonNull ArrayTrackRecord<CallbackType>.ReadHead getCallbackHistory() { return mCallbackHistory; } diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java index 6a09b0237a38..6fc605e269fe 100644 --- a/tests/net/java/android/net/ConnectivityManagerTest.java +++ b/tests/net/java/android/net/ConnectivityManagerTest.java @@ -220,7 +220,7 @@ public class ConnectivityManagerTest { // register callback when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(), - any(), nullable(String.class))).thenReturn(request); + anyInt(), any(), nullable(String.class))).thenReturn(request); manager.requestNetwork(request, callback, handler); // callback triggers @@ -248,7 +248,7 @@ public class ConnectivityManagerTest { // register callback when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(), - any(), nullable(String.class))).thenReturn(req1); + anyInt(), any(), nullable(String.class))).thenReturn(req1); manager.requestNetwork(req1, callback, handler); // callback triggers @@ -266,7 +266,7 @@ public class ConnectivityManagerTest { // callback can be registered again when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(), - any(), nullable(String.class))).thenReturn(req2); + anyInt(), any(), nullable(String.class))).thenReturn(req2); manager.requestNetwork(req2, callback, handler); // callback triggers @@ -289,8 +289,8 @@ public class ConnectivityManagerTest { info.targetSdkVersion = VERSION_CODES.N_MR1 + 1; when(mCtx.getApplicationInfo()).thenReturn(info); - when(mService.requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(), any(), - nullable(String.class))).thenReturn(request); + when(mService.requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), + any(), nullable(String.class))).thenReturn(request); Handler handler = new Handler(Looper.getMainLooper()); manager.requestNetwork(request, callback, handler); @@ -358,34 +358,34 @@ public class ConnectivityManagerTest { manager.requestNetwork(request, callback); verify(mService).requestNetwork(eq(request.networkCapabilities), - eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), + eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); // Verify that register network callback does not calls requestNetwork at all. manager.registerNetworkCallback(request, callback); - verify(mService, never()).requestNetwork(any(), anyInt(), any(), anyInt(), any(), + verify(mService, never()).requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any()); - verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), + verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); manager.registerDefaultNetworkCallback(callback); verify(mService).requestNetwork(eq(null), - eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), + eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); - manager.requestBackgroundNetwork(request, null, callback); + Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + manager.requestBackgroundNetwork(request, handler, callback); verify(mService).requestNetwork(eq(request.networkCapabilities), - eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), + eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); - Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); manager.registerSystemDefaultNetworkCallback(callback, handler); verify(mService).requestNetwork(eq(null), - eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), + eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 51952016632b..fadd1eac14ef 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -275,6 +275,7 @@ import com.android.internal.util.test.FakeSettingsProvider; import com.android.net.module.util.ArrayTrackRecord; import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo; import com.android.server.connectivity.ConnectivityConstants; +import com.android.server.connectivity.ConnectivityResources; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; import com.android.server.connectivity.NetworkAgentInfo; @@ -444,6 +445,7 @@ public class ConnectivityServiceTest { @Mock NetworkPolicyManager mNetworkPolicyManager; @Mock VpnProfileStore mVpnProfileStore; @Mock SystemConfigManager mSystemConfigManager; + @Mock Resources mResources; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); @@ -471,7 +473,7 @@ public class ConnectivityServiceTest { private class MockContext extends BroadcastInterceptingContext { private final MockContentResolver mContentResolver; - @Spy private Resources mResources; + @Spy private Resources mInternalResources; private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>(); // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant @@ -480,21 +482,15 @@ public class ConnectivityServiceTest { MockContext(Context base, ContentProvider settingsProvider) { super(base); - mResources = spy(base.getResources()); - when(mResources.getStringArray(com.android.internal.R.array.networkAttributes)). - thenReturn(new String[] { + mInternalResources = spy(base.getResources()); + when(mInternalResources.getStringArray(com.android.internal.R.array.networkAttributes)) + .thenReturn(new String[] { "wifi,1,1,1,-1,true", "mobile,0,0,0,-1,true", "mobile_mms,2,0,2,60000,true", "mobile_supl,3,0,2,60000,true", }); - when(mResources.getStringArray( - com.android.internal.R.array.config_wakeonlan_supported_interfaces)) - .thenReturn(new String[]{ - WIFI_WOL_IFNAME, - }); - mContentResolver = new MockContentResolver(); mContentResolver.addProvider(Settings.AUTHORITY, settingsProvider); } @@ -559,7 +555,7 @@ public class ConnectivityServiceTest { @Override public Resources getResources() { - return mResources; + return mInternalResources; } @Override @@ -718,7 +714,7 @@ public class ConnectivityServiceTest { @Override protected InstrumentedNetworkAgent makeNetworkAgent(LinkProperties linkProperties, - final int type, final String typeName) throws Exception { + NetworkAgentConfig nac) throws Exception { mNetworkMonitor = mock(INetworkMonitor.class); final Answer validateAnswer = inv -> { @@ -737,8 +733,8 @@ public class ConnectivityServiceTest { any() /* name */, nmCbCaptor.capture()); - final InstrumentedNetworkAgent na = new InstrumentedNetworkAgent(this, linkProperties, - type, typeName) { + final InstrumentedNetworkAgent na = + new InstrumentedNetworkAgent(this, linkProperties, nac) { @Override public void networkStatus(int status, String redirectUrl) { mRedirectUrl = redirectUrl; @@ -1454,6 +1450,8 @@ public class ConnectivityServiceTest { applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q; when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) .thenReturn(applicationInfo); + when(mPackageManager.getTargetSdkVersion(anyString())) + .thenReturn(applicationInfo.targetSdkVersion); when(mSystemConfigManager.getSystemPermissionUids(anyString())).thenReturn(new int[0]); // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not. @@ -1530,6 +1528,18 @@ public class ConnectivityServiceTest { inv.getArgument(0), inv.getArgument(1), inv.getArgument(2)); return mPolicyTracker; }).when(deps).makeMultinetworkPolicyTracker(any(), any(), any()); + doReturn(true).when(deps).getCellular464XlatEnabled(); + + doReturn(60000).when(mResources).getInteger( + com.android.connectivity.resources.R.integer.config_networkTransitionTimeout); + doReturn("").when(mResources).getString( + com.android.connectivity.resources.R.string.config_networkCaptivePortalServerUrl); + doReturn(new String[]{ WIFI_WOL_IFNAME }).when(mResources).getStringArray( + com.android.connectivity.resources.R.array.config_wakeonlan_supported_interfaces); + final com.android.server.connectivity.ConnectivityResources connRes = mock( + ConnectivityResources.class); + doReturn(mResources).when(connRes).get(); + doReturn(connRes).when(deps).getResources(any()); return deps; } @@ -1740,11 +1750,29 @@ public class ConnectivityServiceTest { return expected; } + private boolean extraInfoInBroadcastHasExpectedNullness(NetworkInfo ni) { + final DetailedState state = ni.getDetailedState(); + if (state == DetailedState.CONNECTED && ni.getExtraInfo() == null) return false; + // Expect a null extraInfo if the network is CONNECTING, because a CONNECTIVITY_ACTION + // broadcast with a state of CONNECTING only happens due to legacy VPN lockdown, which also + // nulls out extraInfo. + if (state == DetailedState.CONNECTING && ni.getExtraInfo() != null) return false; + // Can't make any assertions about DISCONNECTED broadcasts. When a network actually + // disconnects, disconnectAndDestroyNetwork sets its state to DISCONNECTED and its extraInfo + // to null. But if the DISCONNECTED broadcast is just simulated by LegacyTypeTracker due to + // a network switch, extraInfo will likely be populated. + // This is likely a bug in CS, but likely not one we can fix without impacting apps. + return true; + } + private ExpectedBroadcast expectConnectivityAction(int type, NetworkInfo.DetailedState state) { - return registerConnectivityBroadcastThat(1, intent -> - type == intent.getIntExtra(EXTRA_NETWORK_TYPE, -1) && state.equals( - ((NetworkInfo) intent.getParcelableExtra(EXTRA_NETWORK_INFO)) - .getDetailedState())); + return registerConnectivityBroadcastThat(1, intent -> { + final int actualType = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1); + final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO); + return type == actualType + && state == ni.getDetailedState() + && extraInfoInBroadcastHasExpectedNullness(ni); + }); } @Test @@ -3730,8 +3758,8 @@ public class ConnectivityServiceTest { networkCapabilities.addTransportType(TRANSPORT_WIFI) .setNetworkSpecifier(new MatchAllNetworkSpecifier()); mService.requestNetwork(networkCapabilities, NetworkRequest.Type.REQUEST.ordinal(), - null, 0, null, ConnectivityManager.TYPE_WIFI, mContext.getPackageName(), - getAttributionTag()); + null, 0, null, ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE, + mContext.getPackageName(), getAttributionTag()); }); class NonParcelableSpecifier extends NetworkSpecifier { @@ -4008,7 +4036,8 @@ public class ConnectivityServiceTest { grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid()); final TestNetworkCallback cellBgCallback = new TestNetworkCallback(); mCm.requestBackgroundNetwork(new NetworkRequest.Builder() - .addTransportType(TRANSPORT_CELLULAR).build(), null, cellBgCallback); + .addTransportType(TRANSPORT_CELLULAR).build(), + mCsHandlerThread.getThreadHandler(), cellBgCallback); // Make callbacks for monitoring. final NetworkRequest request = new NetworkRequest.Builder().build(); @@ -7182,12 +7211,14 @@ public class ConnectivityServiceTest { assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); setUidRulesChanged(RULE_REJECT_ALL); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mCellNetworkAgent); // ConnectivityService should cache it not to invoke the callback again. setUidRulesChanged(RULE_REJECT_METERED); @@ -7198,12 +7229,14 @@ public class ConnectivityServiceTest { assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); setUidRulesChanged(RULE_REJECT_METERED); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mCellNetworkAgent); // Restrict the network based on UID rule and NOT_METERED capability change. mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); @@ -7212,6 +7245,7 @@ public class ConnectivityServiceTest { assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, @@ -7220,12 +7254,14 @@ public class ConnectivityServiceTest { assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mCellNetworkAgent); setUidRulesChanged(RULE_ALLOW_METERED); cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); setUidRulesChanged(RULE_NONE); cellNetworkCallback.assertNoCallback(); @@ -7236,6 +7272,7 @@ public class ConnectivityServiceTest { assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mCellNetworkAgent); setRestrictBackgroundChanged(true); cellNetworkCallback.assertNoCallback(); @@ -7243,12 +7280,14 @@ public class ConnectivityServiceTest { cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); setRestrictBackgroundChanged(false); cellNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); mCm.unregisterNetworkCallback(cellNetworkCallback); } @@ -7307,6 +7346,15 @@ public class ConnectivityServiceTest { assertNotNull(ni); assertEquals(type, ni.getType()); assertEquals(ConnectivityManager.getNetworkTypeName(type), state, ni.getDetailedState()); + if (state == DetailedState.CONNECTED || state == DetailedState.SUSPENDED) { + assertNotNull(ni.getExtraInfo()); + } else { + // Technically speaking, a network that's in CONNECTING state will generally have a + // non-null extraInfo. This doesn't actually happen in this test because it never calls + // a legacy API while a network is connecting. When a network is in CONNECTING state + // because of legacy lockdown VPN, its extraInfo is always null. + assertNull(ni.getExtraInfo()); + } } private void assertActiveNetworkInfo(int type, DetailedState state) { @@ -7316,6 +7364,26 @@ public class ConnectivityServiceTest { checkNetworkInfo(mCm.getNetworkInfo(type), type, state); } + private void assertExtraInfoFromCm(TestNetworkAgentWrapper network, boolean present) { + final NetworkInfo niForNetwork = mCm.getNetworkInfo(network.getNetwork()); + final NetworkInfo niForType = mCm.getNetworkInfo(network.getLegacyType()); + if (present) { + assertEquals(network.getExtraInfo(), niForNetwork.getExtraInfo()); + assertEquals(network.getExtraInfo(), niForType.getExtraInfo()); + } else { + assertNull(niForNetwork.getExtraInfo()); + assertNull(niForType.getExtraInfo()); + } + } + + private void assertExtraInfoFromCmBlocked(TestNetworkAgentWrapper network) { + assertExtraInfoFromCm(network, false); + } + + private void assertExtraInfoFromCmPresent(TestNetworkAgentWrapper network) { + assertExtraInfoFromCm(network, true); + } + // Checks that each of the |agents| receive a blocked status change callback with the specified // |blocked| value, in any order. This is needed because when an event affects multiple // networks, ConnectivityService does not guarantee the order in which callbacks are fired. @@ -7630,6 +7698,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mCellNetworkAgent); // TODO: it would be nice if we could simply rely on the production code here, and have // LockdownVpnTracker start the VPN, have the VPN code register its NetworkAgent with @@ -7658,6 +7727,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI)); @@ -7700,6 +7770,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mWiFiNetworkAgent); // The VPN comes up again on wifi. b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); @@ -7714,6 +7785,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mWiFiNetworkAgent); vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); assertTrue(vpnNc.hasTransport(TRANSPORT_WIFI)); @@ -7730,6 +7802,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mWiFiNetworkAgent); b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); mWiFiNetworkAgent.disconnect(); @@ -8262,6 +8335,45 @@ public class ConnectivityServiceTest { } @Test + public void testWith464XlatDisable() throws Exception { + doReturn(false).when(mDeps).getCellular464XlatEnabled(); + + final TestNetworkCallback callback = new TestNetworkCallback(); + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + final NetworkRequest networkRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .build(); + mCm.registerNetworkCallback(networkRequest, callback); + mCm.registerDefaultNetworkCallback(defaultCallback); + + // Bring up validated cell. + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName(MOBILE_IFNAME); + cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); + cellLp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, MOBILE_IFNAME)); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + + mCellNetworkAgent.sendLinkProperties(cellLp); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + final int cellNetId = mCellNetworkAgent.getNetwork().netId; + waitForIdle(); + + verify(mMockDnsResolver, never()).startPrefix64Discovery(cellNetId); + Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent); + assertTrue("Nat464Xlat was not IDLE", !clat.isStarted()); + + // This cannot happen because prefix discovery cannot succeed if it is never started. + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(cellNetId, PREFIX_OPERATION_ADDED, "64:ff9b::", 96)); + + // ... but still, check that even if it did, clatd would not be started. + verify(mMockNetd, never()).clatdStart(anyString(), anyString()); + assertTrue("Nat464Xlat was not IDLE", !clat.isStarted()); + } + + @Test public void testDataActivityTracking() throws Exception { final TestNetworkCallback networkCallback = new TestNetworkCallback(); final NetworkRequest networkRequest = new NetworkRequest.Builder() @@ -8653,6 +8765,7 @@ public class ConnectivityServiceTest { applicationInfo.targetSdkVersion = targetSdk; when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) .thenReturn(applicationInfo); + when(mPackageManager.getTargetSdkVersion(any())).thenReturn(targetSdk); when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle); @@ -8667,102 +8780,183 @@ public class ConnectivityServiceTest { } } - private int getOwnerUidNetCapsForCallerPermission(int ownerUid, int callerUid) { + private int getOwnerUidNetCapsPermission(int ownerUid, int callerUid, + boolean includeLocationSensitiveInfo) { final NetworkCapabilities netCap = new NetworkCapabilities().setOwnerUid(ownerUid); return mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( - netCap, callerUid, mContext.getPackageName(), getAttributionTag()).getOwnerUid(); + netCap, includeLocationSensitiveInfo, callerUid, + mContext.getPackageName(), getAttributionTag()) + .getOwnerUid(); } - private void verifyWifiInfoCopyNetCapsForCallerPermission( - int callerUid, boolean shouldMakeCopyWithLocationSensitiveFieldsParcelable) { + private void verifyWifiInfoCopyNetCapsPermission( + int callerUid, boolean includeLocationSensitiveInfo, + boolean shouldMakeCopyWithLocationSensitiveFieldsParcelable) { final WifiInfo wifiInfo = mock(WifiInfo.class); when(wifiInfo.hasLocationSensitiveFields()).thenReturn(true); final NetworkCapabilities netCap = new NetworkCapabilities().setTransportInfo(wifiInfo); mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( - netCap, callerUid, mContext.getPackageName(), getAttributionTag()); + netCap, includeLocationSensitiveInfo, callerUid, + mContext.getPackageName(), getAttributionTag()); verify(wifiInfo).makeCopy(eq(shouldMakeCopyWithLocationSensitiveFieldsParcelable)); } + private void verifyOwnerUidAndWifiInfoNetCapsPermission( + boolean shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag, + boolean shouldInclLocationSensitiveOwnerUidWithIncludeFlag, + boolean shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag, + boolean shouldInclLocationSensitiveWifiInfoWithIncludeFlag) { + final int myUid = Process.myUid(); + + final int expectedOwnerUidWithoutIncludeFlag = + shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag + ? Process.myUid() : INVALID_UID; + assertEquals(expectedOwnerUidWithoutIncludeFlag, getOwnerUidNetCapsPermission( + myUid, myUid, false /* includeLocationSensitiveInfo */)); + + final int expectedOwnerUidWithIncludeFlag = + shouldInclLocationSensitiveOwnerUidWithIncludeFlag ? myUid : INVALID_UID; + assertEquals(expectedOwnerUidWithIncludeFlag, getOwnerUidNetCapsPermission( + myUid, myUid, true /* includeLocationSensitiveInfo */)); + + verifyWifiInfoCopyNetCapsPermission(myUid, + false, /* includeLocationSensitiveInfo */ + shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag); + + verifyWifiInfoCopyNetCapsPermission(myUid, + true, /* includeLocationSensitiveInfo */ + shouldInclLocationSensitiveWifiInfoWithIncludeFlag); + + } + @Test - public void testCreateForCallerWithLocationInfoSanitizedWithFineLocationAfterQ() + public void testCreateWithLocationInfoSanitizedWithFineLocationAfterQ() throws Exception { setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION); - final int myUid = Process.myUid(); - assertEquals(myUid, getOwnerUidNetCapsForCallerPermission(myUid, myUid)); + verifyOwnerUidAndWifiInfoNetCapsPermission( + // Ensure that we include owner uid even if the request asks to remove it since the + // app has necessary permissions and targetSdk < S. + true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ + true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ + false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */ + // Ensure that we remove location info if the request asks to remove it even if the + // app has necessary permissions. + true /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */ + ); + } + + @Test + public void testCreateWithLocationInfoSanitizedWithFineLocationPreSWithAndWithoutCallbackFlag() + throws Exception { + setupLocationPermissions(Build.VERSION_CODES.R, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + verifyOwnerUidAndWifiInfoNetCapsPermission( + // Ensure that we include owner uid even if the request asks to remove it since the + // app has necessary permissions and targetSdk < S. + true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ + true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ + false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */ + // Ensure that we remove location info if the request asks to remove it even if the + // app has necessary permissions. + true /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */ + ); + } + + @Test + public void + testCreateWithLocationInfoSanitizedWithFineLocationAfterSWithAndWithoutCallbackFlag() + throws Exception { + setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); - verifyWifiInfoCopyNetCapsForCallerPermission(myUid, - true /* shouldMakeCopyWithLocationSensitiveFieldsParcelable */); + verifyOwnerUidAndWifiInfoNetCapsPermission( + // Ensure that we owner UID if the request asks us to remove it even if the app + // has necessary permissions since targetSdk >= S. + false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ + true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ + false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */ + // Ensure that we remove location info if the request asks to remove it even if the + // app has necessary permissions. + true /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */ + ); } @Test - public void testCreateForCallerWithLocationInfoSanitizedWithCoarseLocationPreQ() + public void testCreateWithLocationInfoSanitizedWithCoarseLocationPreQ() throws Exception { setupLocationPermissions(Build.VERSION_CODES.P, true, AppOpsManager.OPSTR_COARSE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION); - final int myUid = Process.myUid(); - assertEquals(myUid, getOwnerUidNetCapsForCallerPermission(myUid, myUid)); - - verifyWifiInfoCopyNetCapsForCallerPermission(myUid, - true /* shouldMakeCopyWithLocationSensitiveFieldsParcelable */); + verifyOwnerUidAndWifiInfoNetCapsPermission( + // Ensure that we owner UID if the request asks us to remove it even if the app + // has necessary permissions since targetSdk >= S. + true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ + true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ + false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */ + // Ensure that we remove location info if the request asks to remove it even if the + // app has necessary permissions. + true /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */ + ); } @Test - public void testCreateForCallerWithLocationInfoSanitizedLocationOff() throws Exception { + public void testCreateWithLocationInfoSanitizedLocationOff() throws Exception { // Test that even with fine location permission, and UIDs matching, the UID is sanitized. setupLocationPermissions(Build.VERSION_CODES.Q, false, AppOpsManager.OPSTR_FINE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION); - final int myUid = Process.myUid(); - assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid)); - - verifyWifiInfoCopyNetCapsForCallerPermission(myUid, - false/* shouldMakeCopyWithLocationSensitiveFieldsParcelable */); + verifyOwnerUidAndWifiInfoNetCapsPermission( + false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ + false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ + false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */ + false /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */ + ); } @Test - public void testCreateForCallerWithLocationInfoSanitizedWrongUid() throws Exception { + public void testCreateWithLocationInfoSanitizedWrongUid() throws Exception { // Test that even with fine location permission, not being the owner leads to sanitization. setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION); final int myUid = Process.myUid(); - assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid + 1, myUid)); - - verifyWifiInfoCopyNetCapsForCallerPermission(myUid, - true /* shouldMakeCopyWithLocationSensitiveFieldsParcelable */); + assertEquals(Process.INVALID_UID, + getOwnerUidNetCapsPermission(myUid + 1, myUid, + true /* includeLocationSensitiveInfo */)); } @Test - public void testCreateForCallerWithLocationInfoSanitizedWithCoarseLocationAfterQ() + public void testCreateWithLocationInfoSanitizedWithCoarseLocationAfterQ() throws Exception { // Test that not having fine location permission leads to sanitization. setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_COARSE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION); - // Test that without the location permission, the owner field is sanitized. - final int myUid = Process.myUid(); - assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid)); - - verifyWifiInfoCopyNetCapsForCallerPermission(myUid, - false/* shouldMakeCopyWithLocationSensitiveFieldsParcelable */); + verifyOwnerUidAndWifiInfoNetCapsPermission( + false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ + false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ + false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */ + false /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */ + ); } @Test - public void testCreateForCallerWithLocationInfoSanitizedWithoutLocationPermission() + public void testCreateWithLocationInfoSanitizedWithoutLocationPermission() throws Exception { + // Test that not having fine location permission leads to sanitization. setupLocationPermissions(Build.VERSION_CODES.Q, true, null /* op */, null /* perm */); - // Test that without the location permission, the owner field is sanitized. - final int myUid = Process.myUid(); - assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid)); - - verifyWifiInfoCopyNetCapsForCallerPermission(myUid, - false/* shouldMakeCopyWithLocationSensitiveFieldsParcelable */); + verifyOwnerUidAndWifiInfoNetCapsPermission( + false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ + false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ + false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */ + false /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */ + ); } private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType) @@ -8967,7 +9161,7 @@ public class ConnectivityServiceTest { TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_LTE)); return new NetworkAgentInfo(null, new Network(NET_ID), info, new LinkProperties(), nc, 0, mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0, - INVALID_UID, mQosCallbackTracker); + INVALID_UID, mQosCallbackTracker, new ConnectivityService.Dependencies()); } @Test @@ -9353,8 +9547,8 @@ public class ConnectivityServiceTest { assertThrows("Expect throws for invalid request type " + reqTypeInt, IllegalArgumentException.class, () -> mService.requestNetwork(nc, reqTypeInt, null, 0, null, - ConnectivityManager.TYPE_NONE, mContext.getPackageName(), - getAttributionTag()) + ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE, + mContext.getPackageName(), getAttributionTag()) ); } } @@ -10924,4 +11118,12 @@ public class ConnectivityServiceTest { verifyNoNetwork(); mCm.unregisterNetworkCallback(cellCb); } + + @Test + public void testRegisterBestMatchingNetworkCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + assertThrows(UnsupportedOperationException.class, + () -> mCm.registerBestMatchingNetworkCallback(request, new NetworkCallback(), + mCsHandlerThread.getThreadHandler())); + } } diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java index f97eabf6366d..6232423b4f9e 100644 --- a/tests/net/java/com/android/server/IpSecServiceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.net.ConnectivityManager; import android.net.INetd; import android.net.IpSecAlgorithm; import android.net.IpSecConfig; @@ -47,6 +48,7 @@ import android.os.Process; import android.system.ErrnoException; import android.system.Os; import android.system.StructStat; +import android.util.Range; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -647,9 +649,9 @@ public class IpSecServiceTest { @Test public void testReserveNetId() { - int start = mIpSecService.TUN_INTF_NETID_START; - for (int i = 0; i < mIpSecService.TUN_INTF_NETID_RANGE; i++) { - assertEquals(start + i, mIpSecService.reserveNetId()); + final Range<Integer> netIdRange = ConnectivityManager.getIpSecNetIdRange(); + for (int netId = netIdRange.getLower(); netId <= netIdRange.getUpper(); netId++) { + assertEquals(netId, mIpSecService.reserveNetId()); } // Check that resource exhaustion triggers an exception @@ -661,7 +663,7 @@ public class IpSecServiceTest { // Now release one and try again int releasedNetId = - mIpSecService.TUN_INTF_NETID_START + mIpSecService.TUN_INTF_NETID_RANGE / 2; + netIdRange.getLower() + (netIdRange.getUpper() - netIdRange.getLower()) / 2; mIpSecService.releaseNetId(releasedNetId); assertEquals(releasedNetId, mIpSecService.reserveNetId()); } diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index a913673c2a1e..1c0ba4f8d8f5 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -357,7 +357,7 @@ public class LingerMonitorTest { NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info, new LinkProperties(), caps, 50, mCtx, null, new NetworkAgentConfig() /* config */, mConnService, mNetd, mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), - mQosCallbackTracker); + mQosCallbackTracker, new ConnectivityService.Dependencies()); nai.everValidated = true; return nai; } diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java index 5f56e25356c2..9b2a638f8b39 100644 --- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -16,11 +16,15 @@ package com.android.server.connectivity; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -34,6 +38,7 @@ import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkAgentConfig; +import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.os.Handler; import android.os.test.TestLooper; @@ -72,11 +77,15 @@ public class Nat464XlatTest { Handler mHandler; NetworkAgentConfig mAgentConfig = new NetworkAgentConfig(); - Nat464Xlat makeNat464Xlat() { - return new Nat464Xlat(mNai, mNetd, mDnsResolver) { + Nat464Xlat makeNat464Xlat(boolean isCellular464XlatEnabled) { + return new Nat464Xlat(mNai, mNetd, mDnsResolver, new ConnectivityService.Dependencies()) { @Override protected int getNetId() { return NETID; } + + @Override protected boolean isCellular464XlatEnabled() { + return isCellular464XlatEnabled; + } }; } @@ -99,6 +108,7 @@ public class Nat464XlatTest { mNai.linkProperties.setInterfaceName(BASE_IFACE); mNai.networkInfo = new NetworkInfo(null); mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI); + mNai.networkCapabilities = new NetworkCapabilities(); markNetworkConnected(); when(mNai.connService()).thenReturn(mConnectivity); when(mNai.netAgentConfig()).thenReturn(mAgentConfig); @@ -110,21 +120,23 @@ public class Nat464XlatTest { } private void assertRequiresClat(boolean expected, NetworkAgentInfo nai) { + Nat464Xlat nat = makeNat464Xlat(true); String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b " + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), nai.networkInfo.getDetailedState(), mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), nai.linkProperties.getLinkAddresses()); - assertEquals(msg, expected, Nat464Xlat.requiresClat(nai)); + assertEquals(msg, expected, nat.requiresClat(nai)); } private void assertShouldStartClat(boolean expected, NetworkAgentInfo nai) { + Nat464Xlat nat = makeNat464Xlat(true); String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b " + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), nai.networkInfo.getDetailedState(), mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), nai.linkProperties.getLinkAddresses()); - assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai)); + assertEquals(msg, expected, nat.shouldStartClat(nai)); } @Test @@ -194,7 +206,7 @@ public class Nat464XlatTest { } private void checkNormalStartAndStop(boolean dueToDisconnect) throws Exception { - Nat464Xlat nat = makeNat464Xlat(); + Nat464Xlat nat = makeNat464Xlat(true); ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); mNai.linkProperties.addLinkAddress(V6ADDR); @@ -245,7 +257,7 @@ public class Nat464XlatTest { } private void checkStartStopStart(boolean interfaceRemovedFirst) throws Exception { - Nat464Xlat nat = makeNat464Xlat(); + Nat464Xlat nat = makeNat464Xlat(true); ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); InOrder inOrder = inOrder(mNetd, mConnectivity); @@ -335,7 +347,7 @@ public class Nat464XlatTest { @Test public void testClatdCrashWhileRunning() throws Exception { - Nat464Xlat nat = makeNat464Xlat(); + Nat464Xlat nat = makeNat464Xlat(true); ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); @@ -372,7 +384,7 @@ public class Nat464XlatTest { } private void checkStopBeforeClatdStarts(boolean dueToDisconnect) throws Exception { - Nat464Xlat nat = makeNat464Xlat(); + Nat464Xlat nat = makeNat464Xlat(true); mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); @@ -414,7 +426,7 @@ public class Nat464XlatTest { } private void checkStopAndClatdNeverStarts(boolean dueToDisconnect) throws Exception { - Nat464Xlat nat = makeNat464Xlat(); + Nat464Xlat nat = makeNat464Xlat(true); mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); @@ -450,7 +462,7 @@ public class Nat464XlatTest { final IpPrefix prefixFromDns = new IpPrefix(NAT64_PREFIX); final IpPrefix prefixFromRa = new IpPrefix(OTHER_NAT64_PREFIX); - Nat464Xlat nat = makeNat464Xlat(); + Nat464Xlat nat = makeNat464Xlat(true); final LinkProperties emptyLp = new LinkProperties(); LinkProperties fixedupLp; @@ -486,10 +498,57 @@ public class Nat464XlatTest { assertEquals(null, fixedupLp.getNat64Prefix()); } + private void checkClatDisabledOnCellular(boolean onCellular) throws Exception { + // Disable 464xlat on cellular networks. + Nat464Xlat nat = makeNat464Xlat(false); + mNai.linkProperties.addLinkAddress(V6ADDR); + mNai.networkCapabilities.setTransportType(TRANSPORT_CELLULAR, onCellular); + nat.update(); + + final IpPrefix nat64Prefix = new IpPrefix(NAT64_PREFIX); + if (onCellular) { + // Prefix discovery is never started. + verify(mDnsResolver, never()).startPrefix64Discovery(eq(NETID)); + assertIdle(nat); + + // If a NAT64 prefix comes in from an RA, clat is not started either. + mNai.linkProperties.setNat64Prefix(nat64Prefix); + nat.setNat64PrefixFromRa(nat64Prefix); + nat.update(); + verify(mNetd, never()).clatdStart(anyString(), anyString()); + assertIdle(nat); + } else { + // Prefix discovery is started. + verify(mDnsResolver).startPrefix64Discovery(eq(NETID)); + assertIdle(nat); + + // If a NAT64 prefix comes in from an RA, clat is started. + mNai.linkProperties.setNat64Prefix(nat64Prefix); + nat.setNat64PrefixFromRa(nat64Prefix); + nat.update(); + verify(mNetd).clatdStart(BASE_IFACE, NAT64_PREFIX); + assertStarting(nat); + } + } + + @Test + public void testClatDisabledOnCellular() throws Exception { + checkClatDisabledOnCellular(true); + } + + @Test + public void testClatDisabledOnNonCellular() throws Exception { + checkClatDisabledOnCellular(false); + } + static void assertIdle(Nat464Xlat nat) { assertTrue("Nat464Xlat was not IDLE", !nat.isStarted()); } + static void assertStarting(Nat464Xlat nat) { + assertTrue("Nat464Xlat was not STARTING", nat.isStarting()); + } + static void assertRunning(Nat464Xlat nat) { assertTrue("Nat464Xlat was not RUNNING", nat.isRunning()); } diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index e4e24b464838..fec5ef39374a 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -48,18 +48,22 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; 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 static org.mockito.Mockito.when; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; import android.net.INetd; import android.net.UidRange; +import android.net.Uri; import android.os.Build; import android.os.SystemConfigManager; import android.os.UserHandle; @@ -70,12 +74,11 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.server.LocalServices; -import com.android.server.pm.PackageList; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.AdditionalAnswers; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; @@ -112,7 +115,6 @@ public class PermissionMonitorTest { @Mock private Context mContext; @Mock private PackageManager mPackageManager; @Mock private INetd mNetdService; - @Mock private PackageManagerInternal mMockPmi; @Mock private UserManager mUserManager; @Mock private PermissionMonitor.Dependencies mDeps; @Mock private SystemConfigManager mSystemConfigManager; @@ -131,16 +133,14 @@ public class PermissionMonitorTest { when(mContext.getSystemService(Context.SYSTEM_CONFIG_SERVICE)) .thenReturn(mSystemConfigManager); when(mSystemConfigManager.getSystemPermissionUids(anyString())).thenReturn(new int[0]); + final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mContext)); + doReturn(UserHandle.ALL).when(asUserCtx).getUser(); + when(mContext.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx); mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps)); - LocalServices.removeServiceForTest(PackageManagerInternal.class); - LocalServices.addService(PackageManagerInternal.class, mMockPmi); - when(mMockPmi.getPackageList(any())).thenReturn(new PackageList(new ArrayList<String>(), - /* observer */ null)); when(mPackageManager.getInstalledPackages(anyInt())).thenReturn(/* empty app list */ null); mPermissionMonitor.startMonitoring(); - verify(mMockPmi).getPackageList(mPermissionMonitor); } private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion, int uid, @@ -770,4 +770,32 @@ public class PermissionMonitorTest { INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{ MOCK_UID2 }); } + + @Test + public void testIntentReceiver() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + final ArgumentCaptor<BroadcastReceiver> receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(), any(), any(), any()); + final BroadcastReceiver receiver = receiverCaptor.getValue(); + + // Verify receiving PACKAGE_ADDED intent. + final Intent addedIntent = new Intent(Intent.ACTION_PACKAGE_ADDED, + Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */)); + addedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID1); + setPackagePermissions(MOCK_PACKAGE1, MOCK_UID1, + new String[] { INTERNET, UPDATE_DEVICE_STATS }); + receiver.onReceive(mContext, addedIntent); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[] { MOCK_UID1 }); + + // Verify receiving PACKAGE_REMOVED intent. + when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(null); + final Intent removedIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED, + Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */)); + removedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID1); + receiver.onReceive(mContext, removedIntent); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[] { MOCK_UID1 }); + } + } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 11498dec8165..a02002752c38 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -593,6 +593,16 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); } + @Test(expected = SecurityException.class) + public void testRemoveVcnUnderlyingNetworkPolicyListenerInvalidPermission() { + doThrow(new SecurityException()) + .when(mMockContext) + .enforceCallingOrSelfPermission( + eq(android.Manifest.permission.NETWORK_FACTORY), any()); + + mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + } + @Test public void testRemoveVcnUnderlyingNetworkPolicyListenerNeverRegistered() { mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); |