diff options
author | Scott Lobdell <slobdell@google.com> | 2021-06-23 23:48:38 +0000 |
---|---|---|
committer | Daniel Norman <danielnorman@google.com> | 2021-06-23 21:36:25 -0700 |
commit | 4c3d4ac80fe9f0fb4fbb4fcffcd2ba3f4e6fca6c (patch) | |
tree | b694ee7c10ad78d944edccb1836b2e500cc9cf78 | |
parent | 30de082df40adce4137ff0e9cc24f2dfc16401d5 (diff) | |
parent | 4d8fc3bed423eccc15f2b32f534be35018c91f8a (diff) |
Merge SP1A.210618.002
Change-Id: I72d03812a8377fc014aebad8db4e68eeded2d531
283 files changed, 6836 insertions, 2538 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 80317e4634f3..748fe14cde3e 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: "7396576" + build_id: "7464984" target: "CtsShim" source_file: "aosp_arm64/CtsShimPriv.apk" } 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 3605b6d0433b..15103b1f175c 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: "7396576" + build_id: "7464984" target: "CtsShim" source_file: "aosp_arm64/CtsShim.apk" } 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 025ec3a9fdaf..2b9ed2fba339 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: "7396576" + build_id: "7464984" target: "CtsShim" source_file: "aosp_x86_64/CtsShimPriv.apk" } 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 e19235a12f8f..d4ff3b7336b5 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: "7396576" + build_id: "7464984" target: "CtsShim" source_file: "aosp_x86_64/CtsShim.apk" } diff --git a/apex/appsearch/Android.bp b/apex/appsearch/Android.bp index 9a420160d3fd..827842633942 100644 --- a/apex/appsearch/Android.bp +++ b/apex/appsearch/Android.bp @@ -29,6 +29,7 @@ apex { key: "com.android.appsearch.key", certificate: ":com.android.appsearch.certificate", updatable: false, + generate_hashtree: false, } apex_key { diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java index c7c1d68b90f1..e5244296b1f4 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -348,19 +348,19 @@ public class AppSearchManagerService extends SystemService { 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis); int totalLatencyMillis = (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis); - CallStats.Builder cBuilder = new CallStats.Builder(packageName, - databaseName) + logger.logStats(new CallStats.Builder() + .setPackageName(packageName) + .setDatabase(databaseName) + .setStatusCode(statusCode) + .setTotalLatencyMillis(totalLatencyMillis) .setCallType(CallStats.CALL_TYPE_SET_SCHEMA) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) .setNumOperationsSucceeded(operationSuccessCount) - .setNumOperationsFailed(operationFailureCount); - cBuilder.getGeneralStatsBuilder() - .setStatusCode(statusCode) - .setTotalLatencyMillis(totalLatencyMillis); - logger.logStats(cBuilder.build()); + .setNumOperationsFailed(operationFailureCount) + .build()); } } }); @@ -480,19 +480,19 @@ public class AppSearchManagerService extends SystemService { 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis); int totalLatencyMillis = (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis); - CallStats.Builder cBuilder = new CallStats.Builder(packageName, - databaseName) + logger.logStats(new CallStats.Builder() + .setPackageName(packageName) + .setDatabase(databaseName) + .setStatusCode(statusCode) + .setTotalLatencyMillis(totalLatencyMillis) .setCallType(CallStats.CALL_TYPE_PUT_DOCUMENTS) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) .setNumOperationsSucceeded(operationSuccessCount) - .setNumOperationsFailed(operationFailureCount); - cBuilder.getGeneralStatsBuilder() - .setStatusCode(statusCode) - .setTotalLatencyMillis(totalLatencyMillis); - logger.logStats(cBuilder.build()); + .setNumOperationsFailed(operationFailureCount) + .build()); } } }); @@ -563,19 +563,19 @@ public class AppSearchManagerService extends SystemService { 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis); int totalLatencyMillis = (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis); - CallStats.Builder cBuilder = new CallStats.Builder(packageName, - databaseName) + logger.logStats(new CallStats.Builder() + .setPackageName(packageName) + .setDatabase(databaseName) + .setStatusCode(statusCode) + .setTotalLatencyMillis(totalLatencyMillis) .setCallType(CallStats.CALL_TYPE_GET_DOCUMENTS) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) .setNumOperationsSucceeded(operationSuccessCount) - .setNumOperationsFailed(operationFailureCount); - cBuilder.getGeneralStatsBuilder() - .setStatusCode(statusCode) - .setTotalLatencyMillis(totalLatencyMillis); - logger.logStats(cBuilder.build()); + .setNumOperationsFailed(operationFailureCount) + .build()); } } }); @@ -631,19 +631,19 @@ public class AppSearchManagerService extends SystemService { 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis); int totalLatencyMillis = (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis); - CallStats.Builder cBuilder = new CallStats.Builder(packageName, - databaseName) + logger.logStats(new CallStats.Builder() + .setPackageName(packageName) + .setDatabase(databaseName) + .setStatusCode(statusCode) + .setTotalLatencyMillis(totalLatencyMillis) .setCallType(CallStats.CALL_TYPE_SEARCH) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) .setNumOperationsSucceeded(operationSuccessCount) - .setNumOperationsFailed(operationFailureCount); - cBuilder.getGeneralStatsBuilder() - .setStatusCode(statusCode) - .setTotalLatencyMillis(totalLatencyMillis); - logger.logStats(cBuilder.build()); + .setNumOperationsFailed(operationFailureCount) + .build()); } } }); @@ -697,20 +697,18 @@ public class AppSearchManagerService extends SystemService { 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis); int totalLatencyMillis = (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis); - // TODO(b/173532925) database would be nulluable once we remove generalStats - CallStats.Builder cBuilder = new CallStats.Builder(packageName, - /*database=*/ "") + logger.logStats(new CallStats.Builder() + .setPackageName(packageName) + .setStatusCode(statusCode) + .setTotalLatencyMillis(totalLatencyMillis) .setCallType(CallStats.CALL_TYPE_GLOBAL_SEARCH) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) .setNumOperationsSucceeded(operationSuccessCount) - .setNumOperationsFailed(operationFailureCount); - cBuilder.getGeneralStatsBuilder() - .setStatusCode(statusCode) - .setTotalLatencyMillis(totalLatencyMillis); - logger.logStats(cBuilder.build()); + .setNumOperationsFailed(operationFailureCount) + .build()); } } }); @@ -965,19 +963,19 @@ public class AppSearchManagerService extends SystemService { 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis); int totalLatencyMillis = (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis); - CallStats.Builder cBuilder = new CallStats.Builder(packageName, - databaseName) + logger.logStats(new CallStats.Builder() + .setPackageName(packageName) + .setDatabase(databaseName) + .setStatusCode(statusCode) + .setTotalLatencyMillis(totalLatencyMillis) .setCallType(CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_ID) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) .setNumOperationsSucceeded(operationSuccessCount) - .setNumOperationsFailed(operationFailureCount); - cBuilder.getGeneralStatsBuilder() - .setStatusCode(statusCode) - .setTotalLatencyMillis(totalLatencyMillis); - logger.logStats(cBuilder.build()); + .setNumOperationsFailed(operationFailureCount) + .build()); } } }); @@ -1033,19 +1031,19 @@ public class AppSearchManagerService extends SystemService { 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis); int totalLatencyMillis = (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis); - CallStats.Builder cBuilder = new CallStats.Builder(packageName, - databaseName) + logger.logStats(new CallStats.Builder() + .setPackageName(packageName) + .setDatabase(databaseName) + .setStatusCode(statusCode) + .setTotalLatencyMillis(totalLatencyMillis) .setCallType(CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) .setNumOperationsSucceeded(operationSuccessCount) - .setNumOperationsFailed(operationFailureCount); - cBuilder.getGeneralStatsBuilder() - .setStatusCode(statusCode) - .setTotalLatencyMillis(totalLatencyMillis); - logger.logStats(cBuilder.build()); + .setNumOperationsFailed(operationFailureCount) + .build()); } } }); @@ -1110,19 +1108,17 @@ public class AppSearchManagerService extends SystemService { 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis); int totalLatencyMillis = (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis); - CallStats.Builder cBuilder = new CallStats.Builder(/*packageName=*/ "", - /*databaseName=*/ "") + logger.logStats(new CallStats.Builder() + .setStatusCode(statusCode) + .setTotalLatencyMillis(totalLatencyMillis) .setCallType(CallStats.CALL_TYPE_FLUSH) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) .setNumOperationsSucceeded(operationSuccessCount) - .setNumOperationsFailed(operationFailureCount); - cBuilder.getGeneralStatsBuilder() - .setStatusCode(statusCode) - .setTotalLatencyMillis(totalLatencyMillis); - logger.logStats(cBuilder.build()); + .setNumOperationsFailed(operationFailureCount) + .build()); } } }); @@ -1162,21 +1158,17 @@ public class AppSearchManagerService extends SystemService { 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis); int totalLatencyMillis = (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis); - // TODO(b/173532925) make packageName and database nullable after - // removing generalStats - CallStats.Builder cBuilder = new CallStats.Builder(/*packageName=*/"", - /*database=*/ "") + logger.logStats(new CallStats.Builder() + .setStatusCode(statusCode) + .setTotalLatencyMillis(totalLatencyMillis) .setCallType(CallStats.CALL_TYPE_INITIALIZE) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) .setNumOperationsSucceeded(operationSuccessCount) - .setNumOperationsFailed(operationFailureCount); - cBuilder.getGeneralStatsBuilder() - .setStatusCode(statusCode) - .setTotalLatencyMillis(totalLatencyMillis); - logger.logStats(cBuilder.build()); + .setNumOperationsFailed(operationFailureCount) + .build()); } } }); diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java index e7845d51568d..2181dab90681 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java @@ -24,10 +24,12 @@ import android.content.Context; import android.os.Environment; import android.os.UserHandle; import android.util.ArrayMap; +import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.server.appsearch.external.localstorage.AppSearchImpl; import com.android.server.appsearch.external.localstorage.AppSearchLogger; +import com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy; import java.io.File; import java.util.Map; @@ -37,9 +39,10 @@ import java.util.Objects; * Manages the lifecycle of instances of {@link AppSearchImpl}. * * <p>These instances are managed per unique device-user. + * @hide */ public final class ImplInstanceManager { - private static final String APP_SEARCH_DIR = "appSearch"; + private static final String TAG = "AppSearchImplInstanceMa"; private static ImplInstanceManager sImplInstanceManager; @@ -70,8 +73,11 @@ public final class ImplInstanceManager { * <p>This folder should only be accessed after unlock. */ public static File getAppSearchDir(@NonNull UserHandle userHandle) { - return new File( - Environment.getDataSystemCeDirectory(userHandle.getIdentifier()), APP_SEARCH_DIR); + // Duplicates the implementation of Environment#getDataSystemCeDirectory + // TODO(b/191059409): Unhide Environment#getDataSystemCeDirectory and switch to it. + File systemCeDir = new File(Environment.getDataDirectory(), "system_ce"); + File systemCeUserDir = new File(systemCeDir, String.valueOf(userHandle.getIdentifier())); + return new File(systemCeUserDir, "appSearch"); } /** @@ -153,6 +159,12 @@ public final class ImplInstanceManager { @Nullable AppSearchLogger logger) throws AppSearchException { File appSearchDir = getAppSearchDir(userHandle); - return AppSearchImpl.create(appSearchDir, userContext, /*logger=*/ null); + File icingDir = new File(appSearchDir, "icing"); + Log.i(TAG, "Creating new AppSearch instance at: " + icingDir); + return AppSearchImpl.create( + icingDir, + userContext, + /*logger=*/ null, + new FrameworkOptimizeStrategy()); } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java index 29cb57c05eeb..4a1a9ae3546b 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java @@ -145,14 +145,14 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; public final class AppSearchImpl implements Closeable { private static final String TAG = "AppSearchImpl"; - @VisibleForTesting static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000; - @VisibleForTesting static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB @VisibleForTesting static final int CHECK_OPTIMIZE_INTERVAL = 100; private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(); private final LogUtil mLogUtil = new LogUtil(TAG); + private final OptimizeStrategy mOptimizeStrategy; + @GuardedBy("mReadWriteLock") @VisibleForTesting final IcingSearchEngine mIcingSearchEngineLocked; @@ -201,10 +201,12 @@ public final class AppSearchImpl implements Closeable { public static AppSearchImpl create( @NonNull File icingDir, @NonNull Context userContext, - @Nullable AppSearchLogger logger) + @Nullable AppSearchLogger logger, + @NonNull OptimizeStrategy optimizeStrategy) throws AppSearchException { Objects.requireNonNull(icingDir); Objects.requireNonNull(userContext); + Objects.requireNonNull(optimizeStrategy); long totalLatencyStartMillis = SystemClock.elapsedRealtime(); InitializeStats.Builder initStatsBuilder = null; @@ -212,7 +214,9 @@ public final class AppSearchImpl implements Closeable { initStatsBuilder = new InitializeStats.Builder(); } - AppSearchImpl appSearchImpl = new AppSearchImpl(icingDir, userContext, initStatsBuilder); + AppSearchImpl appSearchImpl = + new AppSearchImpl( + icingDir, userContext, initStatsBuilder, optimizeStrategy); long prepareVisibilityStoreLatencyStartMillis = SystemClock.elapsedRealtime(); appSearchImpl.initializeVisibilityStore(); @@ -236,7 +240,8 @@ public final class AppSearchImpl implements Closeable { private AppSearchImpl( @NonNull File icingDir, @NonNull Context userContext, - @Nullable InitializeStats.Builder initStatsBuilder) + @Nullable InitializeStats.Builder initStatsBuilder, + @NonNull OptimizeStrategy optimizeStrategy) throws AppSearchException { mReadWriteLock.writeLock().lock(); @@ -254,6 +259,7 @@ public final class AppSearchImpl implements Closeable { Objects.hashCode(mIcingSearchEngineLocked)); mVisibilityStoreLocked = new VisibilityStore(this, userContext); + mOptimizeStrategy = optimizeStrategy; // The core initialization procedure. If any part of this fails, we bail into // resetLocked(), deleting all data (but hopefully allowing AppSearchImpl to come up). @@ -646,9 +652,7 @@ public final class AppSearchImpl implements Closeable { // Logging stats if (pStatsBuilder != null) { pStatsBuilder - .getGeneralStatsBuilder() - .setStatusCode(statusProtoToResultCode(putResultProto.getStatus())); - pStatsBuilder + .setStatusCode(statusProtoToResultCode(putResultProto.getStatus())) .setGenerateDocumentProtoLatencyMillis( (int) (generateDocumentProtoEndTimeMillis @@ -667,9 +671,8 @@ public final class AppSearchImpl implements Closeable { if (logger != null) { long totalEndTimeMillis = SystemClock.elapsedRealtime(); - pStatsBuilder - .getGeneralStatsBuilder() - .setTotalLatencyMillis((int) (totalEndTimeMillis - totalStartTimeMillis)); + pStatsBuilder.setTotalLatencyMillis( + (int) (totalEndTimeMillis - totalStartTimeMillis)); logger.logStats(pStatsBuilder.build()); } } @@ -812,7 +815,8 @@ public final class AppSearchImpl implements Closeable { * * @param queryExpression Query String to search. * @param searchSpec Spec for setting filters, raw query etc. - * @param callerPackageName Package name of the caller, should belong to the {@code callerUid}. + * @param callerPackageName Package name of the caller, should belong to the {@code + * userContext}. * @param callerUid UID of the client making the globalQuery call. * @param logger logger to collect globalQuery stats * @return The results of performing this search. It may contain an empty list of results if no @@ -2001,7 +2005,7 @@ public final class AppSearchImpl implements Closeable { * resources that could be released. * * <p>{@link IcingSearchEngine#optimize()} should be called only if {@link - * GetOptimizeInfoResultProto} shows there is enough resources could be released. + * OptimizeStrategy#shouldOptimize(GetOptimizeInfoResultProto)} return true. */ public void checkForOptimize() throws AppSearchException { mReadWriteLock.writeLock().lock(); @@ -2009,9 +2013,7 @@ public final class AppSearchImpl implements Closeable { GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResultLocked(); checkSuccess(optimizeInfo.getStatus()); mOptimizeIntervalCountLocked = 0; - // Second threshold, decide when to call optimize(). - if (optimizeInfo.getOptimizableDocs() >= OPTIMIZE_THRESHOLD_DOC_COUNT - || optimizeInfo.getEstimatedOptimizableBytes() >= OPTIMIZE_THRESHOLD_BYTES) { + if (mOptimizeStrategy.shouldOptimize(optimizeInfo)) { optimize(); } } finally { @@ -2022,11 +2024,7 @@ public final class AppSearchImpl implements Closeable { // go/icing-library-apis. } - /** - * Triggers {@link IcingSearchEngine#optimize()} directly. - * - * <p>This method should be only called as a scheduled task in AppSearch Platform backend. - */ + /** Triggers {@link IcingSearchEngine#optimize()} directly. */ public void optimize() throws AppSearchException { mReadWriteLock.writeLock().lock(); try { diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategy.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategy.java new file mode 100644 index 000000000000..8ec30e186306 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategy.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.appsearch.external.localstorage; + +import android.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; + +import com.google.android.icing.proto.GetOptimizeInfoResultProto; + +/** + * An implementation of {@link OptimizeStrategy} will determine when to trigger {@link + * AppSearchImpl#optimize()} in Jetpack environment. + * + * @hide + */ +public class FrameworkOptimizeStrategy implements OptimizeStrategy { + + @VisibleForTesting static final int DOC_COUNT_OPTIMIZE_THRESHOLD = 100_000; + @VisibleForTesting static final int BYTES_OPTIMIZE_THRESHOLD = 1 * 1024 * 1024 * 1024; // 1GB + + @VisibleForTesting + static final long TIME_OPTIMIZE_THRESHOLD_MILLIS = 7 * 24 * 60 * 60 * 1000; // 1 week + + @Override + public boolean shouldOptimize(@NonNull GetOptimizeInfoResultProto optimizeInfo) { + return optimizeInfo.getOptimizableDocs() >= DOC_COUNT_OPTIMIZE_THRESHOLD + || optimizeInfo.getEstimatedOptimizableBytes() >= BYTES_OPTIMIZE_THRESHOLD + || optimizeInfo.getTimeSinceLastOptimizeMs() >= TIME_OPTIMIZE_THRESHOLD_MILLIS; + } +} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/OptimizeStrategy.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/OptimizeStrategy.java new file mode 100644 index 000000000000..6cb84bc64eb9 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/OptimizeStrategy.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appsearch.external.localstorage; + +import android.annotation.NonNull; + +import com.google.android.icing.proto.GetOptimizeInfoResultProto; + +/** + * An interface class for implementing a strategy to determine when to trigger {@link + * AppSearchImpl#optimize()}. + * + * @hide + */ +public interface OptimizeStrategy { + + /** + * Determines whether {@link AppSearchImpl#optimize()} need to be triggered to release garbage + * resources in AppSearch base on the given information. + * + * @param optimizeInfo The proto object indicates the number of garbage resources in AppSearch. + * @return {@code true} if {@link AppSearchImpl#optimize()} need to be triggered. + */ + boolean shouldOptimize(@NonNull GetOptimizeInfoResultProto optimizeInfo); +} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java index ea5263aa9aa5..81fb418a6a0a 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java @@ -18,6 +18,8 @@ package com.android.server.appsearch.external.localstorage.stats; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.appsearch.AppSearchResult; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -29,9 +31,9 @@ import java.util.Objects; * <p>This class can set which stats to log for both batch and non-batch {@link * android.app.appsearch.AppSearchSession} calls. * - * <p>Some function calls like {@link android.app.appsearch.AppSearchSession#setSchema} have their - * own detailed stats class {@link placeholder}. However, {@link CallStats} can still be used along - * with the detailed stats class for easy aggregation/analysis with other function calls. + * <p>Some function calls may have their own detailed stats class like {@link PutDocumentStats}. + * However, {@link CallStats} can still be used along with the detailed stats class for easy + * aggregation/analysis with other function calls. * * @hide */ @@ -73,7 +75,16 @@ public class CallStats { public static final int CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH = 13; public static final int CALL_TYPE_REMOVE_DOCUMENT_BY_SEARCH = 14; - @NonNull private final GeneralStats mGeneralStats; + @Nullable private final String mPackageName; + @Nullable private final String mDatabase; + /** + * The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal + * state. + */ + @AppSearchResult.ResultCode private final int mStatusCode; + + private final int mTotalLatencyMillis; + @CallType private final int mCallType; private final int mEstimatedBinderLatencyMillis; private final int mNumOperationsSucceeded; @@ -81,17 +92,37 @@ public class CallStats { CallStats(@NonNull Builder builder) { Objects.requireNonNull(builder); - mGeneralStats = Objects.requireNonNull(builder.mGeneralStatsBuilder).build(); + mPackageName = builder.mPackageName; + mDatabase = builder.mDatabase; + mStatusCode = builder.mStatusCode; + mTotalLatencyMillis = builder.mTotalLatencyMillis; mCallType = builder.mCallType; mEstimatedBinderLatencyMillis = builder.mEstimatedBinderLatencyMillis; mNumOperationsSucceeded = builder.mNumOperationsSucceeded; mNumOperationsFailed = builder.mNumOperationsFailed; } - /** Returns general information for the call. */ - @NonNull - public GeneralStats getGeneralStats() { - return mGeneralStats; + /** Returns calling package name. */ + @Nullable + public String getPackageName() { + return mPackageName; + } + + /** Returns calling database name. */ + @Nullable + public String getDatabase() { + return mDatabase; + } + + /** Returns status code for this api call. */ + @AppSearchResult.ResultCode + public int getStatusCode() { + return mStatusCode; + } + + /** Returns total latency of this api call in millis. */ + public int getTotalLatencyMillis() { + return mTotalLatencyMillis; } /** Returns type of the call. */ @@ -137,23 +168,41 @@ public class CallStats { /** Builder for {@link CallStats}. */ public static class Builder { - @NonNull final GeneralStats.Builder mGeneralStatsBuilder; + @Nullable String mPackageName; + @Nullable String mDatabase; + @AppSearchResult.ResultCode int mStatusCode; + int mTotalLatencyMillis; @CallType int mCallType; int mEstimatedBinderLatencyMillis; int mNumOperationsSucceeded; int mNumOperationsFailed; - /** Builder takes {@link GeneralStats.Builder}. */ - public Builder(@NonNull String packageName, @NonNull String database) { - Objects.requireNonNull(packageName); - Objects.requireNonNull(database); - mGeneralStatsBuilder = new GeneralStats.Builder(packageName, database); + /** Sets the PackageName used by the session. */ + @NonNull + public Builder setPackageName(@NonNull String packageName) { + mPackageName = Objects.requireNonNull(packageName); + return this; + } + + /** Sets the database used by the session. */ + @NonNull + public Builder setDatabase(@NonNull String database) { + mDatabase = Objects.requireNonNull(database); + return this; } - /** Returns {@link GeneralStats.Builder}. */ + /** Sets the status code. */ @NonNull - public GeneralStats.Builder getGeneralStatsBuilder() { - return mGeneralStatsBuilder; + public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) { + mStatusCode = statusCode; + return this; + } + + /** Sets total latency in millis. */ + @NonNull + public Builder setTotalLatencyMillis(int totalLatencyMillis) { + mTotalLatencyMillis = totalLatencyMillis; + return this; } /** Sets type of the call. */ diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java deleted file mode 100644 index 53c1ee3f675f..000000000000 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.appsearch.external.localstorage.stats; - -import android.annotation.NonNull; -import android.app.appsearch.AppSearchResult; - -import java.util.Objects; - -/** - * A class for holding general logging information. - * - * <p>This class cannot be logged by {@link - * com.android.server.appsearch.external.localstorage.AppSearchLogger} directly. It is used for - * defining general logging information that is shared across different stats classes. - * - * @see PutDocumentStats - * @see CallStats - * @hide - */ -public final class GeneralStats { - /** Package name of the application. */ - @NonNull private final String mPackageName; - - /** Database name within AppSearch. */ - @NonNull private final String mDatabase; - - /** - * The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal - * state. - */ - @AppSearchResult.ResultCode private final int mStatusCode; - - private final int mTotalLatencyMillis; - - GeneralStats(@NonNull Builder builder) { - Objects.requireNonNull(builder); - mPackageName = Objects.requireNonNull(builder.mPackageName); - mDatabase = Objects.requireNonNull(builder.mDatabase); - mStatusCode = builder.mStatusCode; - mTotalLatencyMillis = builder.mTotalLatencyMillis; - } - - /** Returns package name. */ - @NonNull - public String getPackageName() { - return mPackageName; - } - - /** Returns database name. */ - @NonNull - public String getDatabase() { - return mDatabase; - } - - /** Returns result code from {@link AppSearchResult#getResultCode()} */ - @AppSearchResult.ResultCode - public int getStatusCode() { - return mStatusCode; - } - - /** Returns total latency, in milliseconds. */ - public int getTotalLatencyMillis() { - return mTotalLatencyMillis; - } - - /** Builder for {@link GeneralStats}. */ - public static class Builder { - @NonNull final String mPackageName; - @NonNull final String mDatabase; - @AppSearchResult.ResultCode int mStatusCode = AppSearchResult.RESULT_UNKNOWN_ERROR; - int mTotalLatencyMillis; - - /** - * Constructor - * - * @param packageName name of the package logging stats - * @param database name of the database logging stats - */ - public Builder(@NonNull String packageName, @NonNull String database) { - mPackageName = Objects.requireNonNull(packageName); - mDatabase = Objects.requireNonNull(database); - } - - /** Sets status code returned from {@link AppSearchResult#getResultCode()} */ - @NonNull - public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) { - mStatusCode = statusCode; - return this; - } - - /** Sets total latency, in milliseconds. */ - @NonNull - public Builder setTotalLatencyMillis(int totalLatencyMillis) { - mTotalLatencyMillis = totalLatencyMillis; - return this; - } - - /** - * Creates a new {@link GeneralStats} object from the contents of this {@link Builder} - * instance. - */ - @NonNull - public GeneralStats build() { - return new GeneralStats(/* builder= */ this); - } - } -} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java index d031172d29c1..7ba181668bfd 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java @@ -17,6 +17,7 @@ package com.android.server.appsearch.external.localstorage.stats; import android.annotation.NonNull; +import android.app.appsearch.AppSearchResult; import java.util.Objects; @@ -27,8 +28,15 @@ import java.util.Objects; * @hide */ public final class PutDocumentStats { - /** {@link GeneralStats} holds the general stats. */ - @NonNull private final GeneralStats mGeneralStats; + @NonNull private final String mPackageName; + @NonNull private final String mDatabase; + /** + * The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal + * state. + */ + @AppSearchResult.ResultCode private final int mStatusCode; + + private final int mTotalLatencyMillis; /** Time used to generate a document proto from a Bundle. */ private final int mGenerateDocumentProtoLatencyMillis; @@ -61,7 +69,10 @@ public final class PutDocumentStats { PutDocumentStats(@NonNull Builder builder) { Objects.requireNonNull(builder); - mGeneralStats = Objects.requireNonNull(builder.mGeneralStatsBuilder).build(); + mPackageName = builder.mPackageName; + mDatabase = builder.mDatabase; + mStatusCode = builder.mStatusCode; + mTotalLatencyMillis = builder.mTotalLatencyMillis; mGenerateDocumentProtoLatencyMillis = builder.mGenerateDocumentProtoLatencyMillis; mRewriteDocumentTypesLatencyMillis = builder.mRewriteDocumentTypesLatencyMillis; mNativeLatencyMillis = builder.mNativeLatencyMillis; @@ -73,10 +84,27 @@ public final class PutDocumentStats { mNativeExceededMaxNumTokens = builder.mNativeExceededMaxNumTokens; } - /** Returns the {@link GeneralStats} object attached to this instance. */ + /** Returns calling package name. */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** Returns calling database name. */ @NonNull - public GeneralStats getGeneralStats() { - return mGeneralStats; + public String getDatabase() { + return mDatabase; + } + + /** Returns status code for this putDocument. */ + @AppSearchResult.ResultCode + public int getStatusCode() { + return mStatusCode; + } + + /** Returns total latency of this putDocument in millis. */ + public int getTotalLatencyMillis() { + return mTotalLatencyMillis; } /** Returns time spent on generating document proto, in milliseconds. */ @@ -129,7 +157,10 @@ public final class PutDocumentStats { /** Builder for {@link PutDocumentStats}. */ public static class Builder { - @NonNull final GeneralStats.Builder mGeneralStatsBuilder; + @NonNull final String mPackageName; + @NonNull final String mDatabase; + @AppSearchResult.ResultCode int mStatusCode; + int mTotalLatencyMillis; int mGenerateDocumentProtoLatencyMillis; int mRewriteDocumentTypesLatencyMillis; int mNativeLatencyMillis; @@ -140,17 +171,24 @@ public final class PutDocumentStats { int mNativeNumTokensIndexed; boolean mNativeExceededMaxNumTokens; - /** Builder takes {@link GeneralStats.Builder}. */ + /** Builder for {@link PutDocumentStats} */ public Builder(@NonNull String packageName, @NonNull String database) { - Objects.requireNonNull(packageName); - Objects.requireNonNull(database); - mGeneralStatsBuilder = new GeneralStats.Builder(packageName, database); + mPackageName = Objects.requireNonNull(packageName); + mDatabase = Objects.requireNonNull(database); } - /** Returns {@link GeneralStats.Builder}. */ + /** Sets the status code. */ @NonNull - public GeneralStats.Builder getGeneralStatsBuilder() { - return mGeneralStatsBuilder; + public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) { + mStatusCode = statusCode; + return this; + } + + /** Sets total latency in millis. */ + @NonNull + public Builder setTotalLatencyMillis(int totalLatencyMillis) { + mTotalLatencyMillis = totalLatencyMillis; + return this; } /** Sets how much time we spend for generating document proto, in milliseconds. */ diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java index 5a0199f8d78f..31fead5e6314 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java @@ -192,9 +192,8 @@ public final class PlatformLogger implements AppSearchLogger { @GuardedBy("mLock") private void logStatsImplLocked(@NonNull CallStats stats) { mLastPushTimeMillisLocked = SystemClock.elapsedRealtime(); - ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(), - stats.getCallType()); - String database = stats.getGeneralStats().getDatabase(); + ExtraStats extraStats = createExtraStatsLocked(stats.getPackageName(), stats.getCallType()); + String database = stats.getDatabase(); try { int hashCodeForDatabase = calculateHashCodeMd5(database); AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_CALL_STATS_REPORTED, @@ -202,8 +201,8 @@ public final class PlatformLogger implements AppSearchLogger { extraStats.mSkippedSampleCount, extraStats.mPackageUid, hashCodeForDatabase, - stats.getGeneralStats().getStatusCode(), - stats.getGeneralStats().getTotalLatencyMillis(), + stats.getStatusCode(), + stats.getTotalLatencyMillis(), stats.getCallType(), stats.getEstimatedBinderLatencyMillis(), stats.getNumOperationsSucceeded(), @@ -224,9 +223,9 @@ public final class PlatformLogger implements AppSearchLogger { @GuardedBy("mLock") private void logStatsImplLocked(@NonNull PutDocumentStats stats) { mLastPushTimeMillisLocked = SystemClock.elapsedRealtime(); - ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(), - CallStats.CALL_TYPE_PUT_DOCUMENT); - String database = stats.getGeneralStats().getDatabase(); + ExtraStats extraStats = createExtraStatsLocked( + stats.getPackageName(), CallStats.CALL_TYPE_PUT_DOCUMENT); + String database = stats.getDatabase(); try { int hashCodeForDatabase = calculateHashCodeMd5(database); AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_PUT_DOCUMENT_STATS_REPORTED, @@ -234,8 +233,8 @@ public final class PlatformLogger implements AppSearchLogger { extraStats.mSkippedSampleCount, extraStats.mPackageUid, hashCodeForDatabase, - stats.getGeneralStats().getStatusCode(), - stats.getGeneralStats().getTotalLatencyMillis(), + stats.getStatusCode(), + stats.getTotalLatencyMillis(), stats.getGenerateDocumentProtoLatencyMillis(), stats.getRewriteDocumentTypesLatencyMillis(), stats.getNativeLatencyMillis(), diff --git a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java index 95ed36898c73..af09210b2c18 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java @@ -66,8 +66,8 @@ import java.util.Set; * @hide */ public class VisibilityStore { - /** No-op user id that won't have any visibility settings. */ - public static final int NO_OP_USER_ID = -1; + /** No-op uid that won't have any visibility settings. */ + public static final int NO_OP_UID = -1; /** Version for the visibility schema */ private static final int SCHEMA_VERSION = 0; @@ -106,7 +106,7 @@ public class VisibilityStore { * @param userContext Context of the user that the call is being made as */ public VisibilityStore(@NonNull AppSearchImpl appSearchImpl, @NonNull Context userContext) { - mAppSearchImpl = appSearchImpl; + mAppSearchImpl = Objects.requireNonNull(appSearchImpl); mUserContext = Objects.requireNonNull(userContext); } diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt index 395292df120a..78d39cc6deac 100644 --- a/apex/appsearch/synced_jetpack_changeid.txt +++ b/apex/appsearch/synced_jetpack_changeid.txt @@ -1 +1 @@ -c35ced970a63a6c7b1d17f9706160579540850d6 +31a54dba5bda4d0109ea91eb1ac047c937cbaae3 diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index fe0c7f718bb0..fb5129f18417 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -2006,16 +2006,12 @@ public class AlarmManagerService extends SystemService { windowLength = INTERVAL_DAY; } else if ((flags & FLAG_PRIORITIZE) == 0 && windowLength < minAllowedWindow) { // Prioritized alarms are exempt from minimum window limits. - if (CompatChanges.isChangeEnabled( + if (!isExemptFromMinWindowRestrictions(callingUid) && CompatChanges.isChangeEnabled( AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, callingPackage, UserHandle.getUserHandleForUid(callingUid))) { Slog.w(TAG, "Window length " + windowLength + "ms too short; expanding to " + minAllowedWindow + "ms."); windowLength = minAllowedWindow; - } else { - // TODO (b/185199076): Remove temporary log to catch breaking apps. - Slog.wtf(TAG, "Short window " + windowLength + "ms specified by " - + callingPackage); } } maxElapsed = triggerElapsed + windowLength; @@ -2409,6 +2405,13 @@ public class AlarmManagerService extends SystemService { } /** + * Returns true if the given uid can set window to be as small as it wants. + */ + boolean isExemptFromMinWindowRestrictions(int uid) { + return isExemptFromExactAlarmPermission(uid); + } + + /** * Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact, * allow-while-idle alarms. */ diff --git a/api/Android.bp b/api/Android.bp index db1f64c57e2c..a84e6a6cb031 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -69,7 +69,10 @@ genrule { dest: "current.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/public/api", dest: "android.txt", }, @@ -151,7 +154,10 @@ genrule { dest: "removed.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/public/api", dest: "removed.txt", }, @@ -187,7 +193,10 @@ genrule { dest: "system-current.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system/api", dest: "android.txt", }, @@ -242,7 +251,10 @@ genrule { dest: "system-removed.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system/api", dest: "removed.txt", }, @@ -279,7 +291,10 @@ genrule { dest: "module-lib-current.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/module-lib/api", dest: "android.txt", }, @@ -336,7 +351,10 @@ genrule { dest: "module-lib-removed.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/module-lib/api", dest: "removed.txt", }, @@ -377,7 +395,10 @@ genrule { dest: "system-server-current.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system-server/api", dest: "android.txt", }, @@ -401,7 +422,10 @@ genrule { dest: "system-server-removed.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system-server/api", dest: "removed.txt", }, diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp index 6f37ef3959fd..42f0b3f4813d 100644 --- a/cmds/app_process/Android.bp +++ b/cmds/app_process/Android.bp @@ -29,7 +29,16 @@ cc_binary { }, }, - ldflags: ["-Wl,--export-dynamic"], + // Symbols exported from the executable in .dynsym interpose symbols in every + // linker namespace, including an app's classloader namespace. Provide this + // version script to prevent unwanted interposition. + // + // By default, the static linker doesn't export most of an executable's symbols, + // but it will export a symbol that appears to override a symbol in a needed DSO. + // This commonly happens with C++ vaguely-linked entities, such as template + // functions or type_info variables. Hence, a version script is needed even for + // an executable. + version_script: "version-script.txt", shared_libs: [ "libandroid_runtime", diff --git a/cmds/app_process/version-script.txt b/cmds/app_process/version-script.txt new file mode 100644 index 000000000000..a98066a67675 --- /dev/null +++ b/cmds/app_process/version-script.txt @@ -0,0 +1,4 @@ +{ + local: + *; +}; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 0bb12c84fe86..80664ed0816f 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2723,6 +2723,7 @@ package android.view { public final class InputDevice implements android.os.Parcelable { method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void disable(); method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void enable(); + field public static final int ACCESSIBILITY_DEVICE_ID = -2; // 0xfffffffe } public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable { @@ -3189,6 +3190,7 @@ package android.window { method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public android.window.WindowContainerToken getImeTarget(int); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getRootTasks(int, @NonNull int[]); + method @BinderThread public void onAppSplashScreenViewRemoved(int); method @BinderThread public void onBackPressedOnTaskRoot(@NonNull android.app.ActivityManager.RunningTaskInfo); method @BinderThread public void onTaskAppeared(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl); method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 1ce598b5fa18..8e1f263ebf03 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -419,7 +419,7 @@ public class ActivityOptions { private IBinder mLaunchCookie; private IRemoteTransition mRemoteTransition; private boolean mOverrideTaskTransition; - private int mSplashScreenThemeResId; + private String mSplashScreenThemeResName; @SplashScreen.SplashScreenStyle private int mSplashScreenStyle; private boolean mRemoveWithTaskOrganizer; @@ -1174,7 +1174,7 @@ public class ActivityOptions { mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder( KEY_REMOTE_TRANSITION)); mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION); - mSplashScreenThemeResId = opts.getInt(KEY_SPLASH_SCREEN_THEME); + mSplashScreenThemeResName = opts.getString(KEY_SPLASH_SCREEN_THEME); mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER); mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE); mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH); @@ -1368,8 +1368,9 @@ public class ActivityOptions { * Gets whether the activity want to be launched as other theme for the splash screen. * @hide */ - public int getSplashScreenThemeResId() { - return mSplashScreenThemeResId; + @Nullable + public String getSplashScreenThemeResName() { + return mSplashScreenThemeResName; } /** @@ -1945,8 +1946,8 @@ public class ActivityOptions { if (mOverrideTaskTransition) { b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition); } - if (mSplashScreenThemeResId != 0) { - b.putInt(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResId); + if (mSplashScreenThemeResName != null && !mSplashScreenThemeResName.isEmpty()) { + b.putString(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResName); } if (mRemoveWithTaskOrganizer) { b.putBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER, mRemoveWithTaskOrganizer); diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index c3272c1a770d..7a806bdf473d 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -45,6 +45,7 @@ public final class AutomaticZenRule implements Parcelable { private long creationTime; private ZenPolicy mZenPolicy; private boolean mModified = false; + private String mPkg; /** * Creates an automatic zen rule. @@ -123,6 +124,7 @@ public final class AutomaticZenRule implements Parcelable { creationTime = source.readLong(); mZenPolicy = source.readParcelable(null); mModified = source.readInt() == ENABLED; + mPkg = source.readString(); } /** @@ -244,6 +246,20 @@ public final class AutomaticZenRule implements Parcelable { this.configurationActivity = componentName; } + /** + * @hide + */ + public void setPackageName(String pkgName) { + mPkg = pkgName; + } + + /** + * @hide + */ + public String getPackageName() { + return mPkg; + } + @Override public int describeContents() { return 0; @@ -265,6 +281,7 @@ public final class AutomaticZenRule implements Parcelable { dest.writeLong(creationTime); dest.writeParcelable(mZenPolicy, 0); dest.writeInt(mModified ? ENABLED : DISABLED); + dest.writeString(mPkg); } @Override @@ -273,6 +290,7 @@ public final class AutomaticZenRule implements Parcelable { .append("enabled=").append(enabled) .append(",name=").append(name) .append(",interruptionFilter=").append(interruptionFilter) + .append(",pkg=").append(mPkg) .append(",conditionId=").append(conditionId) .append(",owner=").append(owner) .append(",configActivity=").append(configurationActivity) @@ -294,13 +312,14 @@ public final class AutomaticZenRule implements Parcelable { && Objects.equals(other.owner, owner) && Objects.equals(other.mZenPolicy, mZenPolicy) && Objects.equals(other.configurationActivity, configurationActivity) + && Objects.equals(other.mPkg, mPkg) && other.creationTime == creationTime; } @Override public int hashCode() { return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, - configurationActivity, mZenPolicy, mModified, creationTime); + configurationActivity, mZenPolicy, mModified, creationTime, mPkg); } public static final @android.annotation.NonNull Parcelable.Creator<AutomaticZenRule> CREATOR diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index f33adb3c01d1..098492c8234b 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -207,7 +207,7 @@ interface INotificationManager void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted); AutomaticZenRule getAutomaticZenRule(String id); List<ZenModeConfig.ZenRule> getZenRules(); - String addAutomaticZenRule(in AutomaticZenRule automaticZenRule); + String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg); boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule); boolean removeAutomaticZenRule(String id); boolean removeAutomaticZenRules(String packageName); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index da03a3da820a..ccf1edb3fecc 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1182,10 +1182,12 @@ public class NotificationManager { List<ZenModeConfig.ZenRule> rules = service.getZenRules(); Map<String, AutomaticZenRule> ruleMap = new HashMap<>(); for (ZenModeConfig.ZenRule rule : rules) { - ruleMap.put(rule.id, new AutomaticZenRule(rule.name, rule.component, + AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, rule.configurationActivity, rule.conditionId, rule.zenPolicy, zenModeToInterruptionFilter(rule.zenMode), rule.enabled, - rule.creationTime)); + rule.creationTime); + azr.setPackageName(rule.pkg); + ruleMap.put(rule.id, azr); } return ruleMap; } catch (RemoteException e) { @@ -1226,7 +1228,7 @@ public class NotificationManager { public String addAutomaticZenRule(AutomaticZenRule automaticZenRule) { INotificationManager service = getService(); try { - return service.addAutomaticZenRule(automaticZenRule); + return service.addAutomaticZenRule(automaticZenRule, mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 892ac85047d0..47c487bc283c 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -372,7 +372,6 @@ public final class PendingIntent implements Parcelable { } if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED) - && !"com.google.android.apps.gcs".equals(packageName) && !flagImmutableSet && !flagMutableSet) { String msg = packageName + ": Targeting S+ (version " + Build.VERSION_CODES.S + " and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE" diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index a9bec98ce405..a0d2977cf09a 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -18,7 +18,6 @@ package android.app.admin; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.content.ComponentName; import android.content.Intent; import android.os.UserHandle; @@ -256,13 +255,4 @@ public abstract class DevicePolicyManagerInternal { * {@link #supportsResetOp(int)} is true. */ public abstract void resetOp(int op, String packageName, @UserIdInt int userId); - - /** - * Notifies the system that an unsafe operation reason has changed. - * - * @throws IllegalArgumentException if {@code checker} is not the same as set on - * {@code DevicePolicyManagerService}. - */ - public abstract void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, - @OperationSafetyReason int reason, boolean isSafe); } diff --git a/core/java/android/app/admin/DevicePolicyManagerLiteInternal.java b/core/java/android/app/admin/DevicePolicyManagerLiteInternal.java new file mode 100644 index 000000000000..ccb99470d372 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyManagerLiteInternal.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import android.app.admin.DevicePolicyManager.OperationSafetyReason; + +/** + * Device policy manager local system service interface for methods that don't require the + * {@code device_admin} feature. + * + * Maintenance note: if you need to expose information from DPMS to lower level services such as + * PM/UM/AM/etc, then exposing it from DevicePolicyManagerInternal is not safe because it may cause + * lock order inversion. Consider using {@link DevicePolicyCache} instead. + * + * @hide Only for use within the system server. + */ +public interface DevicePolicyManagerLiteInternal { + + /** + * Notifies the system that an unsafe operation reason has changed. + * + * @throws IllegalArgumentException if {@code checker} is not the same as set on + * {@code DevicePolicyManagerService.setDevicePolicySafetyChecker()}. + */ + void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, + @OperationSafetyReason int reason, boolean isSafe); +} diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index 63f93bfa24e5..806091e2158d 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -423,7 +423,7 @@ public class AppSearchShortcutInfo extends GenericDocument { shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage, disabledMessageResId, disabledMessageResName, categoriesSet, intents, rank, extras, getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri, - disabledReason, persons, locusId, 0); + disabledReason, persons, locusId, null); si.setImplicitRank(implicitRank); if ((implicitRank & ShortcutInfo.RANK_CHANGED_BIT) != 0) { si.setRankChanged(); diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index 32fc74f60d15..b0ce6a55e9ba 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -63,4 +63,5 @@ interface IPackageInstaller { void bypassNextStagedInstallerCheck(boolean value); void setAllowUnlimitedSilentUpdates(String installerPackageName); + void setSilentUpdatesThrottleTime(long throttleTimeInSeconds); } diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 76712b5ce2dc..a264bebb5d88 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -449,7 +449,7 @@ public final class ShortcutInfo implements Parcelable { private int mDisabledReason; - private int mStartingThemeResId; + @Nullable private String mStartingThemeResName; private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); @@ -478,8 +478,9 @@ public final class ShortcutInfo implements Parcelable { mExtras = b.mExtras; mLocusId = b.mLocusId; + mStartingThemeResName = b.mStartingThemeResId != 0 + ? b.mContext.getResources().getResourceName(b.mStartingThemeResId) : null; updateTimestamp(); - mStartingThemeResId = b.mStartingThemeResId; } /** @@ -626,7 +627,7 @@ public final class ShortcutInfo implements Parcelable { // Set this bit. mFlags |= FLAG_KEY_FIELDS_ONLY; } - mStartingThemeResId = source.mStartingThemeResId; + mStartingThemeResName = source.mStartingThemeResName; } /** @@ -950,8 +951,8 @@ public final class ShortcutInfo implements Parcelable { if (source.mLocusId != null) { mLocusId = source.mLocusId; } - if (source.mStartingThemeResId != 0) { - mStartingThemeResId = source.mStartingThemeResId; + if (source.mStartingThemeResName != null && !source.mStartingThemeResName.isEmpty()) { + mStartingThemeResName = source.mStartingThemeResName; } } @@ -1454,11 +1455,12 @@ public final class ShortcutInfo implements Parcelable { } /** - * Returns the theme resource id used for the splash screen. + * Returns the theme resource name used for the splash screen. * @hide */ - public int getStartingThemeResId() { - return mStartingThemeResId; + @Nullable + public String getStartingThemeResName() { + return mStartingThemeResName; } /** @hide -- old signature, the internal code still uses it. */ @@ -2182,7 +2184,7 @@ public final class ShortcutInfo implements Parcelable { mPersons = source.readParcelableArray(cl, Person.class); mLocusId = source.readParcelable(cl); mIconUri = source.readString8(); - mStartingThemeResId = source.readInt(); + mStartingThemeResName = source.readString8(); } @Override @@ -2234,7 +2236,7 @@ public final class ShortcutInfo implements Parcelable { dest.writeParcelableArray(mPersons, flags); dest.writeParcelable(mLocusId, flags); dest.writeString8(mIconUri); - dest.writeInt(mStartingThemeResId); + dest.writeString8(mStartingThemeResName); } public static final @NonNull Creator<ShortcutInfo> CREATOR = @@ -2391,10 +2393,10 @@ public final class ShortcutInfo implements Parcelable { sb.append("disabledReason="); sb.append(getDisabledReasonDebugString(mDisabledReason)); - if (mStartingThemeResId != 0) { + if (mStartingThemeResName != null && !mStartingThemeResName.isEmpty()) { addIndentOrComma(sb, indent); - sb.append("SplashScreenThemeResId="); - sb.append(Integer.toHexString(mStartingThemeResId)); + sb.append("SplashScreenThemeResName="); + sb.append(mStartingThemeResName); } addIndentOrComma(sb, indent); @@ -2482,7 +2484,8 @@ public final class ShortcutInfo implements Parcelable { Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras, long lastChangedTimestamp, int flags, int iconResId, String iconResName, String bitmapPath, String iconUri, - int disabledReason, Person[] persons, LocusId locusId, int startingThemeResId) { + int disabledReason, Person[] persons, LocusId locusId, + @Nullable String startingThemeResName) { mUserId = userId; mId = id; mPackageName = packageName; @@ -2511,6 +2514,6 @@ public final class ShortcutInfo implements Parcelable { mDisabledReason = disabledReason; mPersons = persons; mLocusId = locusId; - mStartingThemeResId = startingThemeResId; + mStartingThemeResName = startingThemeResName; } } diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index 233abf36131b..3ed5c6457fa5 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -74,7 +74,7 @@ public abstract class ShortcutServiceInternal { /** * Get the theme res ID of the starting window, it can be 0 if not specified. */ - public abstract int getShortcutStartingThemeResId(int launcherUserId, + public abstract @Nullable String getShortcutStartingThemeResName(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId); diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 1e650a807cec..154d9234d00c 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -493,6 +493,7 @@ public class ApkLiteParseUtils { if (targetResult.isError()) { return input.error(targetResult); } + targetSdkVersion = targetResult.getResult(); ParseResult<Integer> minResult = ParsingPackageUtils.computeMinSdkVersion( minVer, minCode, ParsingPackageUtils.SDK_VERSION, @@ -500,8 +501,6 @@ public class ApkLiteParseUtils { if (minResult.isError()) { return input.error(minResult); } - - targetSdkVersion = targetResult.getResult(); minSdkVersion = minResult.getResult(); } } diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index a0027077e1a3..60a365835e2e 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -63,6 +63,11 @@ public interface BiometricAuthenticator { */ int TYPE_FACE = 1 << 3; + /** + * @hide + */ + int TYPE_ANY_BIOMETRIC = TYPE_FINGERPRINT | TYPE_IRIS | TYPE_FACE; + @IntDef(flag = true, value = { TYPE_NONE, TYPE_CREDENTIAL, diff --git a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java new file mode 100644 index 000000000000..4ec6f0d2509e --- /dev/null +++ b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java @@ -0,0 +1,97 @@ +/* + * 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.biometrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.os.Build; +import android.os.UserHandle; +import android.provider.Settings; + +/** + * "Base" functionality. For settings-specific functionality (which may rely on this base + * functionality), see {@link com.android.settings.biometrics.ParentalControlsUtils} + * @hide + */ +public class ParentalControlsUtilsInternal { + + private static final String TEST_ALWAYS_REQUIRE_CONSENT = + "android.hardware.biometrics.ParentalControlsUtilsInternal.always_require_consent"; + + public static boolean isTestModeEnabled(@NonNull Context context) { + if (Build.IS_USERDEBUG || Build.IS_ENG) { + return Settings.Secure.getInt(context.getContentResolver(), + TEST_ALWAYS_REQUIRE_CONSENT, 0) != 0; + } + return false; + } + + public static boolean parentConsentRequired(@NonNull Context context, + @NonNull DevicePolicyManager dpm, @BiometricAuthenticator.Modality int modality, + @NonNull UserHandle userHandle) { + if (isTestModeEnabled(context)) { + return true; + } + + return parentConsentRequired(dpm, modality, userHandle); + } + + /** + * @return true if parental consent is required in order for biometric sensors to be used. + */ + public static boolean parentConsentRequired(@NonNull DevicePolicyManager dpm, + @BiometricAuthenticator.Modality int modality, @NonNull UserHandle userHandle) { + final ComponentName cn = getSupervisionComponentName(dpm, userHandle); + if (cn == null) { + return false; + } + + final int keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(cn); + final boolean dpmFpDisabled = containsFlag(keyguardDisabledFeatures, + DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); + final boolean dpmFaceDisabled = containsFlag(keyguardDisabledFeatures, + DevicePolicyManager.KEYGUARD_DISABLE_FACE); + final boolean dpmIrisDisabled = containsFlag(keyguardDisabledFeatures, + DevicePolicyManager.KEYGUARD_DISABLE_IRIS); + + final boolean consentRequired; + if (containsFlag(modality, BiometricAuthenticator.TYPE_FINGERPRINT) && dpmFpDisabled) { + consentRequired = true; + } else if (containsFlag(modality, BiometricAuthenticator.TYPE_FACE) && dpmFaceDisabled) { + consentRequired = true; + } else if (containsFlag(modality, BiometricAuthenticator.TYPE_IRIS) && dpmIrisDisabled) { + consentRequired = true; + } else { + consentRequired = false; + } + + return consentRequired; + } + + @Nullable + public static ComponentName getSupervisionComponentName(@NonNull DevicePolicyManager dpm, + @NonNull UserHandle userHandle) { + return dpm.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle); + } + + private static boolean containsFlag(int haystack, int needle) { + return (haystack & needle) != 0; + } +} diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java index ee86265e81ad..802387c675b5 100644 --- a/core/java/android/os/AggregateBatteryConsumer.java +++ b/core/java/android/os/AggregateBatteryConsumer.java @@ -17,7 +17,13 @@ package android.os; import android.annotation.NonNull; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; /** @@ -72,6 +78,43 @@ public final class AggregateBatteryConsumer extends BatteryConsumer implements P return mConsumedPowerMah; } + /** Serializes this object to XML */ + void writeToXml(TypedXmlSerializer serializer, + @BatteryUsageStats.AggregateBatteryConsumerScope int scope) throws IOException { + serializer.startTag(null, BatteryUsageStats.XML_TAG_AGGREGATE); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_SCOPE, scope); + serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, mConsumedPowerMah); + mPowerComponents.writeToXml(serializer); + serializer.endTag(null, BatteryUsageStats.XML_TAG_AGGREGATE); + } + + /** Parses an XML representation and populates the BatteryUsageStats builder */ + static void parseXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder) + throws XmlPullParserException, IOException { + final int scope = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_SCOPE); + final Builder consumerBuilder = builder.getAggregateBatteryConsumerBuilder(scope); + + int eventType = parser.getEventType(); + if (eventType != XmlPullParser.START_TAG || !parser.getName().equals( + BatteryUsageStats.XML_TAG_AGGREGATE)) { + throw new XmlPullParserException("Invalid XML parser state"); + } + + consumerBuilder.setConsumedPower( + parser.getAttributeDouble(null, BatteryUsageStats.XML_ATTR_POWER)); + + while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals( + BatteryUsageStats.XML_TAG_AGGREGATE)) + && eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) { + PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder); + } + } + eventType = parser.next(); + } + } + /** * Builder for DeviceBatteryConsumer. */ @@ -91,6 +134,14 @@ public final class AggregateBatteryConsumer extends BatteryConsumer implements P } /** + * Adds power and usage duration from the supplied AggregateBatteryConsumer. + */ + public void add(AggregateBatteryConsumer aggregateBatteryConsumer) { + mConsumedPowerMah += aggregateBatteryConsumer.mConsumedPowerMah; + mPowerComponentsBuilder.addPowerAndDuration(aggregateBatteryConsumer.mPowerComponents); + } + + /** * Creates a read-only object out of the Builder values. */ @NonNull diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index 6c9f0f677db9..77f8a87cdcd1 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -20,16 +20,23 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.util.Range; import android.util.SparseArray; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; import android.util.proto.ProtoOutputStream; import com.android.internal.os.BatteryStatsHistory; import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.internal.os.PowerCalculator; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -77,6 +84,34 @@ public final class BatteryUsageStats implements Parcelable { private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000; + // XML tags and attributes for BatteryUsageStats persistence + static final String XML_TAG_BATTERY_USAGE_STATS = "battery_usage_stats"; + static final String XML_TAG_AGGREGATE = "aggregate"; + static final String XML_TAG_UID = "uid"; + static final String XML_TAG_USER = "user"; + static final String XML_TAG_POWER_COMPONENTS = "power_components"; + static final String XML_TAG_COMPONENT = "component"; + static final String XML_TAG_CUSTOM_COMPONENT = "custom_component"; + static final String XML_ATTR_ID = "id"; + static final String XML_ATTR_UID = "uid"; + static final String XML_ATTR_USER_ID = "user_id"; + static final String XML_ATTR_SCOPE = "scope"; + static final String XML_ATTR_PREFIX_CUSTOM_COMPONENT = "custom_component_"; + static final String XML_ATTR_START_TIMESTAMP = "start_timestamp"; + static final String XML_ATTR_END_TIMESTAMP = "end_timestamp"; + static final String XML_ATTR_POWER = "power"; + static final String XML_ATTR_DURATION = "duration"; + static final String XML_ATTR_MODEL = "model"; + static final String XML_ATTR_BATTERY_CAPACITY = "battery_capacity"; + static final String XML_ATTR_DISCHARGE_PERCENT = "discharge_pct"; + static final String XML_ATTR_DISCHARGE_LOWER = "discharge_lower"; + static final String XML_ATTR_DISCHARGE_UPPER = "discharge_upper"; + static final String XML_ATTR_BATTERY_REMAINING = "battery_remaining"; + static final String XML_ATTR_CHARGE_REMAINING = "charge_remaining"; + static final String XML_ATTR_HIGHEST_DRAIN_PACKAGE = "highest_drain_package"; + static final String XML_ATTR_TIME_IN_FOREGROUND = "time_in_foreground"; + static final String XML_ATTR_TIME_IN_BACKGROUND = "time_in_background"; + private final int mDischargePercentage; private final double mBatteryCapacityMah; private final long mStatsStartTimestampMs; @@ -96,11 +131,7 @@ public final class BatteryUsageStats implements Parcelable { private BatteryUsageStats(@NonNull Builder builder) { mStatsStartTimestampMs = builder.mStatsStartTimestampMs; mStatsEndTimestampMs = builder.mStatsEndTimestampMs; - if (builder.mStatsDurationMs != -1) { - mStatsDurationMs = builder.mStatsDurationMs; - } else { - mStatsDurationMs = mStatsEndTimestampMs - mStatsStartTimestampMs; - } + mStatsDurationMs = builder.getStatsDuration(); mBatteryCapacityMah = builder.mBatteryCapacityMah; mDischargePercentage = builder.mDischargePercentage; mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah; @@ -579,6 +610,109 @@ public final class BatteryUsageStats implements Parcelable { } } + /** Serializes this object to XML */ + public void writeXml(TypedXmlSerializer serializer) throws IOException { + serializer.startTag(null, XML_TAG_BATTERY_USAGE_STATS); + + for (int i = 0; i < mCustomPowerComponentNames.length; i++) { + serializer.attribute(null, XML_ATTR_PREFIX_CUSTOM_COMPONENT + i, + mCustomPowerComponentNames[i]); + } + + serializer.attributeLong(null, XML_ATTR_START_TIMESTAMP, mStatsStartTimestampMs); + serializer.attributeLong(null, XML_ATTR_END_TIMESTAMP, mStatsEndTimestampMs); + serializer.attributeLong(null, XML_ATTR_DURATION, mStatsDurationMs); + serializer.attributeDouble(null, XML_ATTR_BATTERY_CAPACITY, mBatteryCapacityMah); + serializer.attributeInt(null, XML_ATTR_DISCHARGE_PERCENT, mDischargePercentage); + serializer.attributeDouble(null, XML_ATTR_DISCHARGE_LOWER, mDischargedPowerLowerBound); + serializer.attributeDouble(null, XML_ATTR_DISCHARGE_UPPER, mDischargedPowerUpperBound); + serializer.attributeLong(null, XML_ATTR_BATTERY_REMAINING, mBatteryTimeRemainingMs); + serializer.attributeLong(null, XML_ATTR_CHARGE_REMAINING, mChargeTimeRemainingMs); + + for (int scope = 0; scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; + scope++) { + mAggregateBatteryConsumers[scope].writeToXml(serializer, scope); + } + for (UidBatteryConsumer consumer : mUidBatteryConsumers) { + consumer.writeToXml(serializer); + } + for (UserBatteryConsumer consumer : mUserBatteryConsumers) { + consumer.writeToXml(serializer); + } + serializer.endTag(null, XML_TAG_BATTERY_USAGE_STATS); + } + + /** Parses an XML representation of BatteryUsageStats */ + public static BatteryUsageStats createFromXml(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + Builder builder = null; + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG + && parser.getName().equals(XML_TAG_BATTERY_USAGE_STATS)) { + List<String> customComponentNames = new ArrayList<>(); + int i = 0; + while (true) { + int index = parser.getAttributeIndex(null, + XML_ATTR_PREFIX_CUSTOM_COMPONENT + i); + if (index == -1) { + break; + } + customComponentNames.add(parser.getAttributeValue(index)); + i++; + } + + builder = new Builder( + customComponentNames.toArray(new String[0]), true); + + builder.setStatsStartTimestamp( + parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP)); + builder.setStatsEndTimestamp( + parser.getAttributeLong(null, XML_ATTR_END_TIMESTAMP)); + builder.setStatsDuration( + parser.getAttributeLong(null, XML_ATTR_DURATION)); + builder.setBatteryCapacity( + parser.getAttributeDouble(null, XML_ATTR_BATTERY_CAPACITY)); + builder.setDischargePercentage( + parser.getAttributeInt(null, XML_ATTR_DISCHARGE_PERCENT)); + builder.setDischargedPowerRange( + parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_LOWER), + parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_UPPER)); + builder.setBatteryTimeRemainingMs( + parser.getAttributeLong(null, XML_ATTR_BATTERY_REMAINING)); + builder.setChargeTimeRemainingMs( + parser.getAttributeLong(null, XML_ATTR_CHARGE_REMAINING)); + + eventType = parser.next(); + break; + } + eventType = parser.next(); + } + + if (builder == null) { + throw new XmlPullParserException("No root element"); + } + + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + switch (parser.getName()) { + case XML_TAG_AGGREGATE: + AggregateBatteryConsumer.parseXml(parser, builder); + break; + case XML_TAG_UID: + UidBatteryConsumer.createFromXml(parser, builder); + break; + case XML_TAG_USER: + UserBatteryConsumer.createFromXml(parser, builder); + break; + } + } + eventType = parser.next(); + } + + return builder.build(); + } + /** * Builder for BatteryUsageStats. */ @@ -658,6 +792,14 @@ public final class BatteryUsageStats implements Parcelable { return this; } + private long getStatsDuration() { + if (mStatsDurationMs != -1) { + return mStatsDurationMs; + } else { + return mStatsEndTimestampMs - mStatsStartTimestampMs; + } + } + /** * Sets the battery discharge amount since BatteryStats reset as percentage of the full * charge. @@ -738,6 +880,22 @@ public final class BatteryUsageStats implements Parcelable { } /** + * Creates or returns a UidBatteryConsumer, which represents battery attribution + * data for an individual UID. This version of the method is not suitable for use + * with PowerCalculators. + */ + @NonNull + public UidBatteryConsumer.Builder getOrCreateUidBatteryConsumerBuilder(int uid) { + UidBatteryConsumer.Builder builder = mUidBatteryConsumerBuilders.get(uid); + if (builder == null) { + builder = new UidBatteryConsumer.Builder(mCustomPowerComponentNames, + mIncludePowerModels, uid); + mUidBatteryConsumerBuilders.put(uid, builder); + } + return builder; + } + + /** * Creates or returns a UserBatteryConsumer, which represents battery attribution * data for an individual {@link UserHandle}. */ @@ -756,5 +914,59 @@ public final class BatteryUsageStats implements Parcelable { public SparseArray<UidBatteryConsumer.Builder> getUidBatteryConsumerBuilders() { return mUidBatteryConsumerBuilders; } + + /** + * Adds battery usage stats from another snapshots. The two snapshots are assumed to be + * non-overlapping, meaning that the power consumption estimates and session durations + * can be simply summed across the two snapshots. This remains true even if the timestamps + * seem to indicate that the sessions are in fact overlapping: timestamps may be off as a + * result of realtime clock adjustments by the user or the system. + */ + @NonNull + public Builder add(BatteryUsageStats stats) { + if (!Arrays.equals(mCustomPowerComponentNames, stats.mCustomPowerComponentNames)) { + throw new IllegalArgumentException( + "BatteryUsageStats have different custom power components"); + } + + if (mUserBatteryConsumerBuilders.size() != 0 + || !stats.getUserBatteryConsumers().isEmpty()) { + throw new UnsupportedOperationException( + "Combining UserBatteryConsumers is not supported"); + } + + mDischargedPowerLowerBoundMah += stats.mDischargedPowerLowerBound; + mDischargedPowerUpperBoundMah += stats.mDischargedPowerUpperBound; + mDischargePercentage += stats.mDischargePercentage; + + mStatsDurationMs = getStatsDuration() + stats.getStatsDuration(); + + if (mStatsStartTimestampMs == 0 + || stats.mStatsStartTimestampMs < mStatsStartTimestampMs) { + mStatsStartTimestampMs = stats.mStatsStartTimestampMs; + } + + final boolean addingLaterSnapshot = stats.mStatsEndTimestampMs > mStatsEndTimestampMs; + if (addingLaterSnapshot) { + mStatsEndTimestampMs = stats.mStatsEndTimestampMs; + } + + for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) { + getAggregateBatteryConsumerBuilder(scope) + .add(stats.mAggregateBatteryConsumers[scope]); + } + + for (UidBatteryConsumer consumer : stats.getUidBatteryConsumers()) { + getOrCreateUidBatteryConsumerBuilder(consumer.getUid()).add(consumer); + } + + if (addingLaterSnapshot) { + mBatteryCapacityMah = stats.mBatteryCapacityMah; + mBatteryTimeRemainingMs = stats.mBatteryTimeRemainingMs; + mChargeTimeRemainingMs = stats.mChargeTimeRemainingMs; + } + + return this; + } } } diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index 50804422e92f..97f24ccecaee 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -72,12 +72,16 @@ public final class BatteryUsageStatsQuery implements Parcelable { @NonNull private final int[] mUserIds; private final long mMaxStatsAgeMs; + private long mFromTimestamp; + private long mToTimestamp; private BatteryUsageStatsQuery(@NonNull Builder builder) { mFlags = builder.mFlags; mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray() : new int[]{UserHandle.USER_ALL}; mMaxStatsAgeMs = builder.mMaxStatsAgeMs; + mFromTimestamp = builder.mFromTimestamp; + mToTimestamp = builder.mToTimestamp; } @BatteryUsageStatsFlags @@ -112,11 +116,30 @@ public final class BatteryUsageStatsQuery implements Parcelable { return mMaxStatsAgeMs; } + /** + * Returns the exclusive lower bound of the stored snapshot timestamps that should be included + * in the aggregation. Ignored if {@link #getToTimestamp()} is zero. + */ + public long getFromTimestamp() { + return mFromTimestamp; + } + + /** + * Returns the inclusive upper bound of the stored snapshot timestamps that should + * be included in the aggregation. The default is to include only the current stats + * accumulated since the latest battery reset. + */ + public long getToTimestamp() { + return mToTimestamp; + } + private BatteryUsageStatsQuery(Parcel in) { mFlags = in.readInt(); mUserIds = new int[in.readInt()]; in.readIntArray(mUserIds); mMaxStatsAgeMs = in.readLong(); + mFromTimestamp = in.readLong(); + mToTimestamp = in.readLong(); } @Override @@ -125,6 +148,8 @@ public final class BatteryUsageStatsQuery implements Parcelable { dest.writeInt(mUserIds.length); dest.writeIntArray(mUserIds); dest.writeLong(mMaxStatsAgeMs); + dest.writeLong(mFromTimestamp); + dest.writeLong(mToTimestamp); } @Override @@ -153,6 +178,8 @@ public final class BatteryUsageStatsQuery implements Parcelable { private int mFlags; private IntArray mUserIds; private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS; + private long mFromTimestamp; + private long mToTimestamp; /** * Builds a read-only BatteryUsageStatsQuery object. @@ -204,6 +231,17 @@ public final class BatteryUsageStatsQuery implements Parcelable { } /** + * Requests to aggregate stored snapshots between the two supplied timestamps + * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis() + * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis() + */ + public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) { + mFromTimestamp = fromTimestamp; + mToTimestamp = toTimestamp; + return this; + } + + /** * Set the client's tolerance for stale battery stats. The data may be up to * this many milliseconds out-of-date. */ diff --git a/core/java/android/os/PackageTagsList.java b/core/java/android/os/PackageTagsList.java index c94d3de33b6f..4a81dc6f592e 100644 --- a/core/java/android/os/PackageTagsList.java +++ b/core/java/android/os/PackageTagsList.java @@ -23,20 +23,26 @@ import android.annotation.TestApi; import android.util.ArrayMap; import android.util.ArraySet; +import com.android.internal.annotations.Immutable; + import java.io.PrintWriter; +import java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.Set; /** - * A list of packages and associated attribution tags that supports easy membership checks. + * A list of packages and associated attribution tags that supports easy membership checks. Supports + * "wildcard" attribution tags (ie, matching any attribution tag under a package) in additional to + * standard checks. * * @hide */ @TestApi +@Immutable public final class PackageTagsList implements Parcelable { - // an empty set value matches any attribution tag + // an empty set value matches any attribution tag (ie, wildcard) private final ArrayMap<String, ArraySet<String>> mPackageTags; private PackageTagsList(@NonNull ArrayMap<String, ArraySet<String>> packageTags) { @@ -51,15 +57,34 @@ public final class PackageTagsList implements Parcelable { } /** - * Returns true if the given package is represented within this instance. If this returns true - * this does not imply anything about whether any given attribution tag under the given package - * name is present. + * Returns true if the given package is found within this instance. If this returns true this + * does not imply anything about whether any given attribution tag under the given package name + * is present. */ public boolean includes(@NonNull String packageName) { return mPackageTags.containsKey(packageName); } /** + * Returns true if the given attribution tag is found within this instance under any package. + * Only returns true if the attribution tag literal is found, not if any package contains the + * set of all attribution tags. + * + * @hide + */ + public boolean includesTag(@NonNull String attributionTag) { + final int size = mPackageTags.size(); + for (int i = 0; i < size; i++) { + ArraySet<String> tags = mPackageTags.valueAt(i); + if (tags.contains(attributionTag)) { + return true; + } + } + + return false; + } + + /** * Returns true if all attribution tags under the given package are contained within this * instance. */ @@ -76,6 +101,7 @@ public final class PackageTagsList implements Parcelable { if (tags == null) { return false; } else if (tags.isEmpty()) { + // our tags are the full set, so we contain any attribution tag return true; } else { return tags.contains(attributionTag); @@ -98,10 +124,12 @@ public final class PackageTagsList implements Parcelable { return false; } if (tags.isEmpty()) { + // our tags are the full set, so we contain whatever the other tags are continue; } ArraySet<String> otherTags = packageTagsList.mPackageTags.valueAt(i); if (otherTags.isEmpty()) { + // other tags are the full set, so we can't contain them return false; } if (!tags.containsAll(otherTags)) { @@ -248,6 +276,31 @@ public final class PackageTagsList implements Parcelable { } /** + * Adds the specified package and set of attribution tags to the builder. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder add(@NonNull String packageName, + @NonNull Collection<String> attributionTags) { + if (attributionTags.isEmpty()) { + // the input is not allowed to specify a full set by passing in an empty collection + return this; + } + + ArraySet<String> tags = mPackageTags.get(packageName); + if (tags == null) { + tags = new ArraySet<>(attributionTags); + mPackageTags.put(packageName, tags); + } else if (!tags.isEmpty()) { + // if we contain the full set, already done, otherwise add all the tags + tags.addAll(attributionTags); + } + + return this; + } + + /** * Adds the specified {@link PackageTagsList} to the builder. */ @SuppressLint("MissingGetterMatchingBuilder") @@ -267,13 +320,92 @@ public final class PackageTagsList implements Parcelable { if (newTags.isEmpty()) { add(entry.getKey()); } else { - ArraySet<String> tags = mPackageTags.get(entry.getKey()); - if (tags == null) { - tags = new ArraySet<>(newTags); - mPackageTags.put(entry.getKey(), tags); - } else if (!tags.isEmpty()) { - tags.addAll(newTags); - } + add(entry.getKey(), newTags); + } + } + + return this; + } + + /** + * Removes all attribution tags under the specified package from the builder. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder remove(@NonNull String packageName) { + mPackageTags.remove(packageName); + return this; + } + + /** + * Removes the specified package and attribution tag from the builder if and only if the + * specified attribution tag is listed explicitly under the package. If the package contains + * all possible attribution tags, then nothing will be removed. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder remove(@NonNull String packageName, + @Nullable String attributionTag) { + ArraySet<String> tags = mPackageTags.get(packageName); + if (tags != null && tags.remove(attributionTag) && tags.isEmpty()) { + mPackageTags.remove(packageName); + } + return this; + } + + /** + * Removes the specified package and set of attribution tags from the builder if and only if + * the specified set of attribution tags are listed explicitly under the package. If the + * package contains all possible attribution tags, then nothing will be removed. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder remove(@NonNull String packageName, + @NonNull Collection<String> attributionTags) { + if (attributionTags.isEmpty()) { + // the input is not allowed to specify a full set by passing in an empty collection + return this; + } + + ArraySet<String> tags = mPackageTags.get(packageName); + if (tags != null && tags.removeAll(attributionTags) && tags.isEmpty()) { + mPackageTags.remove(packageName); + } + return this; + } + + /** + * Removes the specified {@link PackageTagsList} from the builder. If a package contains all + * possible attribution tags, it will only be removed if the package in the removed + * {@link PackageTagsList} also contains all possible attribution tags. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder remove(@NonNull PackageTagsList packageTagsList) { + return remove(packageTagsList.mPackageTags); + } + + /** + * Removes the given map of package to attribution tags to the builder. An empty set of + * attribution tags is interpreted to imply all attribution tags under that package. If a + * package contains all possible attribution tags, it will only be removed if the package in + * the removed map also contains all possible attribution tags. + * + * @hide + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder remove(@NonNull Map<String, ? extends Set<String>> packageTagsMap) { + for (Map.Entry<String, ? extends Set<String>> entry : packageTagsMap.entrySet()) { + Set<String> removedTags = entry.getValue(); + if (removedTags.isEmpty()) { + // if removing the full set, drop the package completely + remove(entry.getKey()); + } else { + remove(entry.getKey(), removedTags); } } diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java index a90ed20d54fc..db3d13bdb07b 100644 --- a/core/java/android/os/PowerComponents.java +++ b/core/java/android/os/PowerComponents.java @@ -19,11 +19,18 @@ import static android.os.BatteryConsumer.convertMahToDeciCoulombs; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; import android.util.proto.ProtoOutputStream; import com.android.internal.os.PowerCalculator; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; +import java.util.Arrays; /** * Contains details of battery attribution data broken down to individual power drain types @@ -36,9 +43,12 @@ class PowerComponents { - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; private final double mConsumedPowerMah; + @NonNull private final double[] mPowerComponentsMah; + @NonNull private final long[] mUsageDurationsMs; private final int mCustomPowerComponentCount; + @Nullable private final byte[] mPowerModels; // Not written to Parcel and must be explicitly restored during the parent object's unparceling private String[] mCustomPowerComponentNames; @@ -49,7 +59,7 @@ class PowerComponents { mPowerComponentsMah = builder.mPowerComponentsMah; mUsageDurationsMs = builder.mUsageDurationsMs; mConsumedPowerMah = builder.getTotalPower(); - mPowerModels = builder.mPowerModels; + mPowerModels = builder.getPowerModels(); } PowerComponents(@NonNull Parcel source) { @@ -146,9 +156,13 @@ class PowerComponents { } } + public boolean hasPowerModels() { + return mPowerModels != null; + } + @BatteryConsumer.PowerModel int getPowerModel(@BatteryConsumer.PowerComponent int component) { - if (mPowerModels == null) { + if (!hasPowerModels()) { throw new IllegalStateException( "Power model IDs were not requested in the BatteryUsageStatsQuery"); } @@ -294,10 +308,128 @@ class PowerComponents { return interestingData; } + void writeToXml(TypedXmlSerializer serializer) throws IOException { + serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS); + for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; + componentId++) { + final double powerMah = getConsumedPower(componentId); + final long durationMs = getUsageDurationMillis(componentId); + if (powerMah == 0 && durationMs == 0) { + continue; + } + + serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId); + if (powerMah != 0) { + serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah); + } + if (durationMs != 0) { + serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs); + } + if (mPowerModels != null) { + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL, + mPowerModels[componentId]); + } + serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT); + } + + final int customComponentEnd = + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + mCustomPowerComponentCount; + for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; + componentId < customComponentEnd; + componentId++) { + final double powerMah = getConsumedPowerForCustomComponent(componentId); + final long durationMs = getUsageDurationForCustomComponentMillis(componentId); + if (powerMah == 0 && durationMs == 0) { + continue; + } + + serializer.startTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId); + if (powerMah != 0) { + serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah); + } + if (durationMs != 0) { + serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs); + } + serializer.endTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT); + } + + serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS); + } + + + static void parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder) + throws XmlPullParserException, IOException { + int eventType = parser.getEventType(); + if (eventType != XmlPullParser.START_TAG || !parser.getName().equals( + BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) { + throw new XmlPullParserException("Invalid XML parser state"); + } + + while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals( + BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) + && eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + switch (parser.getName()) { + case BatteryUsageStats.XML_TAG_COMPONENT: { + int componentId = -1; + double powerMah = 0; + long durationMs = 0; + int model = BatteryConsumer.POWER_MODEL_UNDEFINED; + for (int i = 0; i < parser.getAttributeCount(); i++) { + switch (parser.getAttributeName(i)) { + case BatteryUsageStats.XML_ATTR_ID: + componentId = parser.getAttributeInt(i); + break; + case BatteryUsageStats.XML_ATTR_POWER: + powerMah = parser.getAttributeDouble(i); + break; + case BatteryUsageStats.XML_ATTR_DURATION: + durationMs = parser.getAttributeLong(i); + break; + case BatteryUsageStats.XML_ATTR_MODEL: + model = parser.getAttributeInt(i); + break; + } + } + builder.setConsumedPower(componentId, powerMah, model); + builder.setUsageDurationMillis(componentId, durationMs); + break; + } + case BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT: { + int componentId = -1; + double powerMah = 0; + long durationMs = 0; + for (int i = 0; i < parser.getAttributeCount(); i++) { + switch (parser.getAttributeName(i)) { + case BatteryUsageStats.XML_ATTR_ID: + componentId = parser.getAttributeInt(i); + break; + case BatteryUsageStats.XML_ATTR_POWER: + powerMah = parser.getAttributeDouble(i); + break; + case BatteryUsageStats.XML_ATTR_DURATION: + durationMs = parser.getAttributeLong(i); + break; + } + } + builder.setConsumedPowerForCustomComponent(componentId, powerMah); + builder.setUsageDurationForCustomComponentMillis(componentId, durationMs); + break; + } + } + } + eventType = parser.next(); + } + } + /** * Builder for PowerComponents. */ static final class Builder { + private static final byte POWER_MODEL_UNINITIALIZED = -1; + private final double[] mPowerComponentsMah; private final String[] mCustomPowerComponentNames; private final long[] mUsageDurationsMs; @@ -311,6 +443,7 @@ class PowerComponents { mUsageDurationsMs = new long[powerComponentCount]; if (includePowerModels) { mPowerModels = new byte[BatteryConsumer.POWER_COMPONENT_COUNT]; + Arrays.fill(mPowerModels, POWER_MODEL_UNINITIALIZED); } else { mPowerModels = null; } @@ -412,12 +545,39 @@ class PowerComponents { return this; } - public void addPowerAndDuration(Builder other) { + public void addPowerAndDuration(PowerComponents.Builder other) { + addPowerAndDuration(other.mPowerComponentsMah, other.mUsageDurationsMs, + other.mPowerModels); + } + + public void addPowerAndDuration(PowerComponents other) { + addPowerAndDuration(other.mPowerComponentsMah, other.mUsageDurationsMs, + other.mPowerModels); + } + + private void addPowerAndDuration(double[] powerComponentsMah, + long[] usageDurationsMs, byte[] powerModels) { + if (mPowerComponentsMah.length != powerComponentsMah.length) { + throw new IllegalArgumentException( + "Number of power components does not match: " + powerComponentsMah.length + + ", expected: " + mPowerComponentsMah.length); + } + for (int i = mPowerComponentsMah.length - 1; i >= 0; i--) { - mPowerComponentsMah[i] += other.mPowerComponentsMah[i]; + mPowerComponentsMah[i] += powerComponentsMah[i]; } for (int i = mUsageDurationsMs.length - 1; i >= 0; i--) { - mUsageDurationsMs[i] += other.mUsageDurationsMs[i]; + mUsageDurationsMs[i] += usageDurationsMs[i]; + } + if (mPowerModels != null && powerModels != null) { + for (int i = mPowerModels.length - 1; i >= 0; i--) { + if (mPowerModels[i] == POWER_MODEL_UNINITIALIZED) { + mPowerModels[i] = powerModels[i]; + } else if (mPowerModels[i] != powerModels[i] + && powerModels[i] != POWER_MODEL_UNINITIALIZED) { + mPowerModels[i] = BatteryConsumer.POWER_MODEL_UNDEFINED; + } + } } } @@ -433,6 +593,19 @@ class PowerComponents { return totalPowerMah; } + private byte[] getPowerModels() { + if (mPowerModels == null) { + return null; + } + + byte[] powerModels = new byte[mPowerModels.length]; + for (int i = mPowerModels.length - 1; i >= 0; i--) { + powerModels[i] = mPowerModels[i] != POWER_MODEL_UNINITIALIZED ? mPowerModels[i] + : BatteryConsumer.POWER_MODEL_UNDEFINED; + } + return powerModels; + } + /** * Creates a read-only object out of the Builder values. */ diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index 16a6c767da38..bfc4f73835d9 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -19,9 +19,16 @@ package android.os; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; import com.android.internal.os.PowerCalculator; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -143,13 +150,65 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela return 0; } + /** Serializes this object to XML */ + void writeToXml(TypedXmlSerializer serializer) throws IOException { + if (getConsumedPower() == 0) { + return; + } + + serializer.startTag(null, BatteryUsageStats.XML_TAG_UID); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_UID, getUid()); + if (!TextUtils.isEmpty(mPackageWithHighestDrain)) { + serializer.attribute(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE, + mPackageWithHighestDrain); + } + serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND, + mTimeInForegroundMs); + serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND, + mTimeInBackgroundMs); + mPowerComponents.writeToXml(serializer); + serializer.endTag(null, BatteryUsageStats.XML_TAG_UID); + } + + /** Parses an XML representation and populates the BatteryUsageStats builder */ + static void createFromXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder) + throws XmlPullParserException, IOException { + final int uid = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_UID); + final UidBatteryConsumer.Builder consumerBuilder = + builder.getOrCreateUidBatteryConsumerBuilder(uid); + + int eventType = parser.getEventType(); + if (eventType != XmlPullParser.START_TAG + || !parser.getName().equals(BatteryUsageStats.XML_TAG_UID)) { + throw new XmlPullParserException("Invalid XML parser state"); + } + + consumerBuilder.setPackageWithHighestDrain( + parser.getAttributeValue(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE)); + consumerBuilder.setTimeInStateMs(STATE_FOREGROUND, + parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND)); + consumerBuilder.setTimeInStateMs(STATE_BACKGROUND, + parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND)); + while (!(eventType == XmlPullParser.END_TAG + && parser.getName().equals(BatteryUsageStats.XML_TAG_UID)) + && eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) { + PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder); + } + } + eventType = parser.next(); + } + } + /** * Builder for UidBatteryConsumer. */ public static final class Builder extends BaseBuilder<Builder> { + private static final String PACKAGE_NAME_UNINITIALIZED = ""; private final BatteryStats.Uid mBatteryStatsUid; private final int mUid; - private String mPackageWithHighestDrain; + private String mPackageWithHighestDrain = PACKAGE_NAME_UNINITIALIZED; public long mTimeInForegroundMs; public long mTimeInBackgroundMs; private boolean mExcludeFromBatteryUsageStats; @@ -161,8 +220,19 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela mUid = batteryStatsUid.getUid(); } + public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels, + int uid) { + super(customPowerComponentNames, includePowerModels); + mBatteryStatsUid = null; + mUid = uid; + } + @NonNull public BatteryStats.Uid getBatteryStatsUid() { + if (mBatteryStatsUid == null) { + throw new IllegalStateException( + "UidBatteryConsumer.Builder was initialized without a BatteryStats.Uid"); + } return mBatteryStatsUid; } @@ -176,7 +246,7 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela */ @NonNull public Builder setPackageWithHighestDrain(@Nullable String packageName) { - mPackageWithHighestDrain = packageName; + mPackageWithHighestDrain = TextUtils.nullIfEmpty(packageName); return this; } @@ -208,6 +278,30 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela } /** + * Adds power and usage duration from the supplied UidBatteryConsumer. + */ + public Builder add(UidBatteryConsumer consumer) { + mPowerComponentsBuilder.addPowerAndDuration(consumer.mPowerComponents); + mTimeInBackgroundMs += consumer.mTimeInBackgroundMs; + mTimeInForegroundMs += consumer.mTimeInForegroundMs; + + if (mPackageWithHighestDrain == PACKAGE_NAME_UNINITIALIZED) { + mPackageWithHighestDrain = consumer.mPackageWithHighestDrain; + } else if (!TextUtils.equals(mPackageWithHighestDrain, + consumer.mPackageWithHighestDrain)) { + // Consider combining two UidBatteryConsumers with this distribution + // of power drain between packages: + // (package1=100, package2=10) and (package1=100, package2=101). + // Since we don't know the actual power distribution between packages at this + // point, we have no way to correctly declare package1 as the winner. + // The naive logic of picking the consumer with the higher total consumed + // power would produce an incorrect result. + mPackageWithHighestDrain = null; + } + return this; + } + + /** * Returns true if this UidBatteryConsumer must be excluded from the * BatteryUsageStats. */ @@ -220,6 +314,9 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela */ @NonNull public UidBatteryConsumer build() { + if (mPackageWithHighestDrain == PACKAGE_NAME_UNINITIALIZED) { + mPackageWithHighestDrain = null; + } return new UidBatteryConsumer(this); } } diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java index 429d2c53a836..b508a8cd98ae 100644 --- a/core/java/android/os/UserBatteryConsumer.java +++ b/core/java/android/os/UserBatteryConsumer.java @@ -17,9 +17,15 @@ package android.os; import android.annotation.NonNull; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; import com.android.internal.os.PowerCalculator; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -85,6 +91,42 @@ public class UserBatteryConsumer extends BatteryConsumer implements Parcelable { return 0; } + /** Serializes this object to XML */ + void writeToXml(TypedXmlSerializer serializer) throws IOException { + if (getConsumedPower() == 0) { + return; + } + + serializer.startTag(null, BatteryUsageStats.XML_TAG_USER); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_USER_ID, getUserId()); + mPowerComponents.writeToXml(serializer); + serializer.endTag(null, BatteryUsageStats.XML_TAG_USER); + } + + /** Parses an XML representation and populates the BatteryUsageStats builder */ + static void createFromXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder) + throws XmlPullParserException, IOException { + final int userId = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_USER_ID); + final UserBatteryConsumer.Builder consumerBuilder = + builder.getOrCreateUserBatteryConsumerBuilder(userId); + + int eventType = parser.getEventType(); + if (eventType != XmlPullParser.START_TAG + || !parser.getName().equals(BatteryUsageStats.XML_TAG_USER)) { + throw new XmlPullParserException("Invalid XML parser state"); + } + while (!(eventType == XmlPullParser.END_TAG + && parser.getName().equals(BatteryUsageStats.XML_TAG_USER)) + && eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) { + PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder); + } + } + eventType = parser.next(); + } + } + /** * Builder for UserBatteryConsumer. */ diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index c22224dd0da2..7edd6e6597b2 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1322,6 +1322,24 @@ public class UserManager { "disallow_camera_toggle"; /** + * This is really not a user restriction in the normal sense. This can't be set to a user, + * via UserManager nor via DevicePolicyManager. This is not even set in UserSettingsUtils. + * This is defined here purely for convenience within the settings app. + * + * TODO(b/191306258): Refactor the Settings app to remove the need for this field, and delete it + * + * Specifies whether biometrics are available to the user. This is used internally only, + * as a means of communications between biometric settings and + * {@link com.android.settingslib.enterprise.ActionDisabledByAdminControllerFactory}. + * + * @see {@link android.hardware.biometrics.ParentalControlsUtilsInternal} + * @see {@link com.android.settings.biometrics.ParentalControlsUtils} + * + * @hide + */ + public static final String DISALLOW_BIOMETRIC = "disallow_biometric"; + + /** * Application restriction key that is used to indicate the pending arrival * of real restrictions for the app. * @@ -1415,6 +1433,7 @@ public class UserManager { DISALLOW_MICROPHONE_TOGGLE, DISALLOW_CAMERA_TOGGLE, KEY_RESTRICTIONS_PENDING, + DISALLOW_BIOMETRIC, }) @Retention(RetentionPolicy.SOURCE) public @interface UserRestrictionKey {} diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 12d905588e1e..ff692818863a 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -50,6 +50,7 @@ import android.util.TypedXmlSerializer; import android.util.proto.ProtoOutputStream; import com.android.internal.R; +import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -162,6 +163,7 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_ENABLED = "enabled"; private static final String RULE_ATT_SNOOZING = "snoozing"; private static final String RULE_ATT_NAME = "name"; + private static final String RULE_ATT_PKG = "pkg"; private static final String RULE_ATT_COMPONENT = "component"; private static final String RULE_ATT_CONFIG_ACTIVITY = "configActivity"; private static final String RULE_ATT_ZEN = "zen"; @@ -671,11 +673,11 @@ public class ZenModeConfig implements Parcelable { rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID); rt.component = safeComponentName(parser, RULE_ATT_COMPONENT); rt.configurationActivity = safeComponentName(parser, RULE_ATT_CONFIG_ACTIVITY); - rt.pkg = (rt.component != null) - ? rt.component.getPackageName() - : (rt.configurationActivity != null) - ? rt.configurationActivity.getPackageName() - : null; + rt.pkg = XmlUtils.readStringAttribute(parser, RULE_ATT_PKG); + if (rt.pkg == null) { + // backfill from component, if present. configActivity is not safe to backfill from + rt.pkg = rt.component != null ? rt.component.getPackageName() : null; + } rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0); rt.enabler = parser.getAttributeValue(null, RULE_ATT_ENABLER); rt.condition = readConditionXml(parser); @@ -697,6 +699,9 @@ public class ZenModeConfig implements Parcelable { out.attribute(null, RULE_ATT_NAME, rule.name); } out.attributeInt(null, RULE_ATT_ZEN, rule.zenMode); + if (rule.pkg != null) { + out.attribute(null, RULE_ATT_PKG, rule.pkg); + } if (rule.component != null) { out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString()); } diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 4f1354d7eee6..782a992d28e5 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -444,6 +444,13 @@ public final class InputDevice implements Parcelable { private static final int VIBRATOR_ID_ALL = -1; + /** + * The device id of input events generated inside accessibility service. + * @hide + */ + @TestApi + public static final int ACCESSIBILITY_DEVICE_ID = -2; + public static final @android.annotation.NonNull Parcelable.Creator<InputDevice> CREATOR = new Parcelable.Creator<InputDevice>() { public InputDevice createFromParcel(Parcel in) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index c1e394d7456a..5964f632da1a 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2371,6 +2371,14 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000; /** + * Flag to prevent the window from being magnified by the accessibility magnifier. + * + * TODO(b/190623172): This is a temporary solution and need to find out another way instead. + * @hide + */ + public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 0x00400000; + + /** * Flag to indicate that the status bar window is in a state such that it forces showing * the navigation bar unless the navigation bar window is explicitly set to * {@link View#GONE}. @@ -2473,6 +2481,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE, SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, + PRIVATE_FLAG_NOT_MAGNIFIABLE, PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION, PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, PRIVATE_FLAG_USE_BLAST, @@ -2553,6 +2562,10 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, name = "IS_ROUNDED_CORNERS_OVERLAY"), @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_NOT_MAGNIFIABLE, + equals = PRIVATE_FLAG_NOT_MAGNIFIABLE, + name = "NOT_MAGNIFIABLE"), + @ViewDebug.FlagToString( mask = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION, equals = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION, name = "STATUS_FORCE_SHOW_NAVIGATION"), diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 7668d80d3cb1..81c934dffa47 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -16,6 +16,9 @@ package android.view; +import static android.os.IInputConstants.POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY; +import static android.os.IInputConstants.POLICY_FLAG_INPUTFILTER_TRUSTED; + import android.annotation.IntDef; import android.os.PowerManager; @@ -27,10 +30,13 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public interface WindowManagerPolicyConstants { - // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h. + // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h and + // frameworks/native/libs/input/android/os/IInputConstants.aidl int FLAG_WAKE = 0x00000001; int FLAG_VIRTUAL = 0x00000002; + int FLAG_INPUTFILTER_TRUSTED = POLICY_FLAG_INPUTFILTER_TRUSTED; + int FLAG_INJECTED_FROM_ACCESSIBILITY = POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY; int FLAG_INJECTED = 0x01000000; int FLAG_TRUSTED = 0x02000000; int FLAG_FILTERED = 0x04000000; diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index 1b1dc4a709ca..10ae69118f54 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -16,6 +16,7 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString; +import static android.view.contentcapture.ContentCaptureManager.DEBUG; import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; import android.annotation.IntDef; @@ -509,11 +510,13 @@ public final class ContentCaptureEvent implements Parcelable { string.append(", class=").append(className); string.append(", id=").append(mNode.getAutofillId()); if (mNode.getText() != null) { - string.append(", text=").append(getSanitizedString(mNode.getText())); + string.append(", text=") + .append(DEBUG ? mNode.getText() : getSanitizedString(mNode.getText())); } } if (mText != null) { - string.append(", text=").append(getSanitizedString(mText)); + string.append(", text=") + .append(DEBUG ? mText : getSanitizedString(mText)); } if (mClientContext != null) { string.append(", context=").append(mClientContext); @@ -522,10 +525,13 @@ public final class ContentCaptureEvent implements Parcelable { string.append(", insets=").append(mInsets); } if (mComposingStart > MAX_INVALID_VALUE) { - string.append(", hasComposing"); + string.append(", composing=[") + .append(mComposingStart).append(",").append(mComposingEnd).append("]"); } if (mSelectionStartIndex > MAX_INVALID_VALUE) { - string.append(", hasSelection"); + string.append(", selection=[") + .append(mSelectionStartIndex).append(",") + .append(mSelectionEndIndex).append("]"); } return string.append(']').toString(); } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index ed840ce3061b..9241c3074ddd 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -212,6 +212,9 @@ public final class ContentCaptureManager { private static final String TAG = ContentCaptureManager.class.getSimpleName(); + /** @hide */ + public static final boolean DEBUG = false; + /** Error happened during the data sharing session. */ public static final int DATA_SHARE_ERROR_UNKNOWN = 1; diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index 3eb35c2c5e8a..8b8dba89ea67 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -52,6 +52,11 @@ oneway interface ITaskOrganizer { void copySplashScreenView(int taskId); /** + * Called when the Task removed the splash screen. + */ + void onAppSplashScreenViewRemoved(int taskId); + + /** * A callback when the Task is available for the registered organizer. The client is responsible * for releasing the SurfaceControl in the callback. For non-root tasks, the leash may initially * be hidden so it is up to the organizer to show this task. diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 6772afeb0270..4a3bf91645f2 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -34,8 +34,10 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.os.RemoteCallback; import android.os.Trace; import android.util.AttributeSet; import android.util.Log; @@ -76,7 +78,7 @@ import java.time.Instant; */ public final class SplashScreenView extends FrameLayout { private static final String TAG = SplashScreenView.class.getSimpleName(); - private static final boolean DEBUG = false; + private static final boolean DEBUG = Build.IS_DEBUGGABLE; private static final int LIGHT_BARS_MASK = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS @@ -85,6 +87,7 @@ public final class SplashScreenView extends FrameLayout { | FLAG_TRANSLUCENT_NAVIGATION | FLAG_TRANSLUCENT_STATUS; private boolean mNotCopyable; + private boolean mIsCopied; private int mInitBackgroundColor; private int mInitIconBackgroundColor; private View mIconView; @@ -103,6 +106,10 @@ public final class SplashScreenView extends FrameLayout { private SurfaceControlViewHost.SurfacePackage mSurfacePackage; @Nullable private SurfaceView mSurfaceView; + @Nullable + private SurfaceControlViewHost mSurfaceHost; + @Nullable + private RemoteCallback mClientCallback; // cache original window and status private Window mWindow; @@ -127,6 +134,7 @@ public final class SplashScreenView extends FrameLayout { private Bitmap mParceledIconBitmap; private Drawable mIconDrawable; private SurfaceControlViewHost.SurfacePackage mSurfacePackage; + private RemoteCallback mClientCallback; private int mBrandingImageWidth; private int mBrandingImageHeight; private Drawable mBrandingDrawable; @@ -161,6 +169,7 @@ public final class SplashScreenView extends FrameLayout { } mIconAnimationStart = Instant.ofEpochMilli(parcelable.mIconAnimationStartMillis); mIconAnimationDuration = Duration.ofMillis(parcelable.mIconAnimationDurationMillis); + mClientCallback = parcelable.mClientCallback; if (DEBUG) { Log.d(TAG, String.format("Building from parcel drawable: %s", mIconDrawable)); } @@ -228,6 +237,7 @@ public final class SplashScreenView extends FrameLayout { view.mInitBackgroundColor = mBackgroundColor; view.mInitIconBackgroundColor = mIconBackground; view.setBackgroundColor(mBackgroundColor); + view.mClientCallback = mClientCallback; view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view); @@ -285,7 +295,8 @@ public final class SplashScreenView extends FrameLayout { if (mSurfacePackage == null) { if (DEBUG) { Log.d(TAG, - "Creating Original SurfacePackage. SurfaceView: " + surfaceView); + "SurfaceControlViewHost created on thread " + + Thread.currentThread().getId()); } SurfaceControlViewHost viewHost = new SurfaceControlViewHost(mContext, @@ -297,6 +308,7 @@ public final class SplashScreenView extends FrameLayout { SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage(); surfaceView.setChildSurfacePackage(surfacePackage); view.mSurfacePackage = surfacePackage; + view.mSurfaceHost = viewHost; view.mSurfacePackageCopy = new SurfaceControlViewHost.SurfacePackage( surfacePackage); } else { @@ -357,6 +369,7 @@ public final class SplashScreenView extends FrameLayout { * @hide */ public void onCopied() { + mIsCopied = true; if (mSurfaceView == null) { return; } @@ -369,6 +382,12 @@ public final class SplashScreenView extends FrameLayout { mSurfacePackage = null; } + /** @hide **/ + @Nullable + public SurfaceControlViewHost getSurfaceHost() { + return mSurfaceHost; + } + @Override public void setAlpha(float alpha) { super.setAlpha(alpha); @@ -407,8 +426,7 @@ public final class SplashScreenView extends FrameLayout { if (DEBUG) { mSurfacePackage.getSurfaceControl().addOnReparentListener( (transaction, parent) -> Log.e(TAG, - String.format("SurfacePackage'surface reparented.\n Parent: %s", - parent), new Throwable())); + String.format("SurfacePackage'surface reparented to %s", parent))); Log.d(TAG, "Transferring surface " + mSurfaceView.toString()); } mSurfaceView.setChildSurfacePackage(mSurfacePackage); @@ -466,9 +484,36 @@ public final class SplashScreenView extends FrameLayout { mHasRemoved = true; } + /** @hide **/ + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + releaseAnimationSurfaceHost(); + } + + private void releaseAnimationSurfaceHost() { + if (mSurfaceHost != null && !mIsCopied) { + final SurfaceControlViewHost finalSurfaceHost = mSurfaceHost; + mSurfaceHost = null; + finalSurfaceHost.getView().post(() -> { + if (DEBUG) { + Log.d(TAG, + "Shell removed splash screen." + + " Releasing SurfaceControlViewHost on thread #" + + Thread.currentThread().getId()); + } + finalSurfaceHost.release(); + }); + } else if (mSurfacePackage != null && mSurfaceHost == null) { + mSurfacePackage = null; + mClientCallback.sendResult(null); + } + } + /** * Called when this view is attached to an activity. This also makes SystemUI colors * transparent so the content of splash screen view can draw fully. + * * @hide */ public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) { @@ -585,6 +630,7 @@ public final class SplashScreenView extends FrameLayout { private long mIconAnimationDurationMillis; private SurfaceControlViewHost.SurfacePackage mSurfacePackage; + private RemoteCallback mClientCallback; public SplashScreenViewParcelable(SplashScreenView view) { mIconSize = view.mIconView.getWidth(); @@ -641,6 +687,7 @@ public final class SplashScreenView extends FrameLayout { mIconAnimationDurationMillis = source.readLong(); mIconBackground = source.readInt(); mSurfacePackage = source.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR); + mClientCallback = source.readTypedObject(RemoteCallback.CREATOR); } @Override @@ -660,6 +707,7 @@ public final class SplashScreenView extends FrameLayout { dest.writeLong(mIconAnimationDurationMillis); dest.writeInt(mIconBackground); dest.writeTypedObject(mSurfacePackage, flags); + dest.writeTypedObject(mClientCallback, flags); } public static final @NonNull Parcelable.Creator<SplashScreenViewParcelable> CREATOR = @@ -697,5 +745,13 @@ public final class SplashScreenView extends FrameLayout { int getIconBackground() { return mIconBackground; } + + /** + * Sets the {@link RemoteCallback} that will be called by the client to notify the shell + * of the removal of the {@link SplashScreenView}. + */ + public void setClientCallback(@NonNull RemoteCallback clientCallback) { + mClientCallback = clientCallback; + } } } diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index 3340cf4fb707..73995491668a 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -117,6 +117,16 @@ public class TaskOrganizer extends WindowOrganizer { public void copySplashScreenView(int taskId) {} /** + * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} that the client has + * removed the splash screen view. + * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int) + * @see SplashScreenView#remove() + */ + @BinderThread + public void onAppSplashScreenViewRemoved(int taskId) { + } + + /** * Called when a task with the registered windowing mode can be controlled by this task * organizer. For non-root tasks, the leash may initially be hidden so it is up to the organizer * to show this task. @@ -236,11 +246,16 @@ public class TaskOrganizer extends WindowOrganizer { } @Override - public void copySplashScreenView(int taskId) { + public void copySplashScreenView(int taskId) { mExecutor.execute(() -> TaskOrganizer.this.copySplashScreenView(taskId)); } @Override + public void onAppSplashScreenViewRemoved(int taskId) { + mExecutor.execute(() -> TaskOrganizer.this.onAppSplashScreenViewRemoved(taskId)); + } + + @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { mExecutor.execute(() -> TaskOrganizer.this.onTaskAppeared(taskInfo, leash)); } diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java index bd908900fccc..19183b8eb9e7 100644 --- a/core/java/com/android/internal/display/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java @@ -31,9 +31,6 @@ import android.provider.Settings; import android.util.MathUtils; import android.view.Display; -import java.util.LinkedList; -import java.util.Queue; - /** * BrightnessSynchronizer helps convert between the int (old) system and float * (new) system for storing the brightness. It has methods to convert between the two and also @@ -43,12 +40,11 @@ public class BrightnessSynchronizer { private static final int MSG_UPDATE_FLOAT = 1; private static final int MSG_UPDATE_INT = 2; + private static final int MSG_UPDATE_BOTH = 3; private static final String TAG = "BrightnessSynchronizer"; private static final Uri BRIGHTNESS_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS); - private static final Uri BRIGHTNESS_FLOAT_URI = - Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT); // The tolerance within which we consider brightness values approximately equal to eachother. // This value is approximately 1/3 of the smallest possible brightness value. @@ -57,8 +53,6 @@ public class BrightnessSynchronizer { private DisplayManager mDisplayManager; private final Context mContext; - private final Queue<Object> mWriteHistory = new LinkedList<>(); - private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { @@ -69,6 +63,9 @@ public class BrightnessSynchronizer { case MSG_UPDATE_INT: updateBrightnessIntFromFloat(Float.intBitsToFloat(msg.arg1)); break; + case MSG_UPDATE_BOTH: + updateBoth(Float.intBitsToFloat(msg.arg1)); + break; default: super.handleMessage(msg); } @@ -139,7 +136,7 @@ public class BrightnessSynchronizer { /** * Translates specified value from the float brightness system to the int brightness system, - * given the min/max of each range. Accounts for special values such as OFF and invalid values. + * given the min/max of each range. Accounts for special values such as OFF and invalid values. * Value returned as a float primitive (to preserve precision), but is a value within the * int-system range. */ @@ -168,49 +165,63 @@ public class BrightnessSynchronizer { } /** - * Updates the float setting based on a passed in int value. This is called whenever the int - * setting changes. mWriteHistory keeps a record of the values that been written to the settings - * from either this method or updateBrightnessIntFromFloat. This is to ensure that the value - * being set is due to an external value being set, rather than the updateBrightness* methods. - * The intention of this is to avoid race conditions when the setting is being changed - * frequently and to ensure we are not reacting to settings changes from this file. + * Updates the settings based on a passed in int value. This is called whenever the int + * setting changes. mPreferredSettingValue holds the most recently updated brightness value + * as a float that we would like the display to be set to. + * + * We then schedule an update to both the int and float settings, but, remove all the other + * messages to update all, to prevent us getting stuck in a loop. + * * @param value Brightness value as int to store in the float setting. */ private void updateBrightnessFloatFromInt(int value) { - Object topOfQueue = mWriteHistory.peek(); - if (topOfQueue != null && topOfQueue.equals(value)) { - mWriteHistory.poll(); - } else { - if (brightnessFloatToInt(mPreferredSettingValue) == value) { - return; - } - float newBrightnessFloat = brightnessIntToFloat(value); - mWriteHistory.offer(newBrightnessFloat); - mPreferredSettingValue = newBrightnessFloat; - mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, newBrightnessFloat); + if (brightnessFloatToInt(mPreferredSettingValue) == value) { + return; } + + mPreferredSettingValue = brightnessIntToFloat(value); + final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue); + mHandler.removeMessages(MSG_UPDATE_BOTH); + mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget(); } /** - * Updates the int setting based on a passed in float value. This is called whenever the float - * setting changes. mWriteHistory keeps a record of the values that been written to the settings - * from either this method or updateBrightnessFloatFromInt. This is to ensure that the value - * being set is due to an external value being set, rather than the updateBrightness* methods. - * The intention of this is to avoid race conditions when the setting is being changed - * frequently and to ensure we are not reacting to settings changes from this file. + * Updates the settings based on a passed in float value. This is called whenever the float + * setting changes. mPreferredSettingValue holds the most recently updated brightness value + * as a float that we would like the display to be set to. + * + * We then schedule an update to both the int and float settings, but, remove all the other + * messages to update all, to prevent us getting stuck in a loop. + * * @param value Brightness setting as float to store in int setting. */ private void updateBrightnessIntFromFloat(float value) { - int newBrightnessInt = brightnessFloatToInt(value); - Object topOfQueue = mWriteHistory.peek(); - if (topOfQueue != null && topOfQueue.equals(value)) { - mWriteHistory.poll(); - } else { - mWriteHistory.offer(newBrightnessInt); - mPreferredSettingValue = value; + if (floatEquals(mPreferredSettingValue, value)) { + return; + } + + mPreferredSettingValue = value; + final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue); + mHandler.removeMessages(MSG_UPDATE_BOTH); + mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget(); + } + + + /** + * Updates both setting values if they have changed + * mDisplayManager.setBrightness automatically checks for changes + * Settings.System.putIntForUser needs to be checked, to prevent an extra callback to this class + * + * @param newBrightnessFloat Brightness setting as float to store in both settings + */ + private void updateBoth(float newBrightnessFloat) { + int newBrightnessInt = brightnessFloatToInt(newBrightnessFloat); + mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, newBrightnessFloat); + if (getScreenBrightnessInt(mContext) != newBrightnessInt) { Settings.System.putIntForUser(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, newBrightnessInt, UserHandle.USER_CURRENT); } + } /** diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 26d6a0c74b08..aabcd7f82ac7 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -43,6 +43,10 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR; @@ -53,6 +57,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; import android.annotation.IntDef; import android.annotation.NonNull; @@ -153,6 +158,11 @@ public class InteractionJankMonitor { public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27; public static final int CUJ_SETTINGS_PAGE_SCROLL = 28; public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33; + public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34; private static final int NO_STATSD_LOGGING = -1; @@ -191,6 +201,11 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, }; private static volatile InteractionJankMonitor sInstance; @@ -240,6 +255,11 @@ public class InteractionJankMonitor { CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET, CUJ_SETTINGS_PAGE_SCROLL, CUJ_LOCKSCREEN_UNLOCK_ANIMATION, + CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON, + CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER, + CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, + CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, + CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -578,6 +598,16 @@ public class InteractionJankMonitor { return "SETTINGS_PAGE_SCROLL"; case CUJ_LOCKSCREEN_UNLOCK_ANIMATION: return "LOCKSCREEN_UNLOCK_ANIMATION"; + case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON: + return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON"; + case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER: + return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER"; + case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE: + return "SHADE_APP_LAUNCH_FROM_QS_TILE"; + case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON: + return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON"; + case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP: + return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 5dfc5faead43..945a6ab11856 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -341,6 +341,19 @@ public class BatteryStatsImpl extends BatteryStats { } } + /** + * Listener for the battery stats reset. + */ + public interface BatteryResetListener { + + /** + * Callback invoked immediately prior to resetting battery stats. + */ + void prepareForBatteryStatsReset(); + } + + private BatteryResetListener mBatteryResetListener; + public interface BatteryCallback { public void batteryNeedsCpuUpdate(); public void batteryPowerChanged(boolean onBattery); @@ -10736,6 +10749,10 @@ public class BatteryStatsImpl extends BatteryStats { } } + PowerProfile getPowerProfile() { + return mPowerProfile; + } + /** * Starts tracking CPU time-in-state for threads of the system server process, * keeping a separate account of threads receiving incoming binder calls. @@ -11184,6 +11201,10 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.reset(false, elapsedRealtimeUs); } + public void setBatteryResetListener(BatteryResetListener batteryResetListener) { + mBatteryResetListener = batteryResetListener; + } + public void resetAllStatsCmdLocked() { final long mSecUptime = mClocks.uptimeMillis(); long uptimeUs = mSecUptime * 1000; @@ -11219,6 +11240,10 @@ public class BatteryStatsImpl extends BatteryStats { } private void resetAllStatsLocked(long uptimeMillis, long elapsedRealtimeMillis) { + if (mBatteryResetListener != null) { + mBatteryResetListener.prepareForBatteryStatsReset(); + } + final long uptimeUs = uptimeMillis * 1000; final long elapsedRealtimeUs = elapsedRealtimeMillis * 1000; mStartCount = 0; diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index 3aaccdd71844..8943db6c8a1e 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -38,14 +38,24 @@ import java.util.Map; public class BatteryUsageStatsProvider { private final Context mContext; private final BatteryStats mStats; + private final BatteryUsageStatsStore mBatteryUsageStatsStore; private final PowerProfile mPowerProfile; private final Object mLock = new Object(); private List<PowerCalculator> mPowerCalculators; public BatteryUsageStatsProvider(Context context, BatteryStats stats) { + this(context, stats, null); + } + + @VisibleForTesting + public BatteryUsageStatsProvider(Context context, BatteryStats stats, + BatteryUsageStatsStore batteryUsageStatsStore) { mContext = context; mStats = stats; - mPowerProfile = new PowerProfile(mContext); + mBatteryUsageStatsStore = batteryUsageStatsStore; + mPowerProfile = stats instanceof BatteryStatsImpl + ? ((BatteryStatsImpl) stats).getPowerProfile() + : new PowerProfile(context); } private List<PowerCalculator> getPowerCalculators() { @@ -126,6 +136,15 @@ public class BatteryUsageStatsProvider { private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query, long currentTimeMs) { + if (query.getToTimestamp() == 0) { + return getCurrentBatteryUsageStats(query, currentTimeMs); + } else { + return getAggregatedBatteryUsageStats(query); + } + } + + private BatteryUsageStats getCurrentBatteryUsageStats(BatteryUsageStatsQuery query, + long currentTimeMs) { final long realtimeUs = elapsedRealtime() * 1000; final long uptimeUs = uptimeMillis() * 1000; @@ -209,6 +228,25 @@ public class BatteryUsageStatsProvider { BatteryStats.STATS_SINCE_CHARGED) / 1000; } + private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) { + final boolean includePowerModels = (query.getFlags() + & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; + + final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( + mStats.getCustomEnergyConsumerNames(), includePowerModels); + final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); + for (long timestamp : timestamps) { + if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) { + final BatteryUsageStats snapshot = + mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp); + if (snapshot != null) { + builder.add(snapshot); + } + } + } + return builder.build(); + } + private long elapsedRealtime() { if (mStats instanceof BatteryStatsImpl) { return ((BatteryStatsImpl) mStats).mClocks.elapsedRealtime(); diff --git a/core/java/com/android/internal/os/BatteryUsageStatsStore.java b/core/java/com/android/internal/os/BatteryUsageStatsStore.java new file mode 100644 index 000000000000..5c976025d39d --- /dev/null +++ b/core/java/com/android/internal/os/BatteryUsageStatsStore.java @@ -0,0 +1,285 @@ +/* + * 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.content.Context; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.Handler; +import android.util.AtomicFile; +import android.util.LongArray; +import android.util.Slog; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.TreeMap; + +/** + * A storage mechanism for BatteryUsageStats snapshots. + */ +public class BatteryUsageStatsStore { + private static final String TAG = "BatteryUsageStatsStore"; + + private static final List<BatteryUsageStatsQuery> BATTERY_USAGE_STATS_QUERY = List.of( + new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0) + .includePowerModels() + .build()); + private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats"; + private static final String SNAPSHOT_FILE_EXTENSION = ".bus"; + private static final String DIR_LOCK_FILENAME = ".lock"; + private static final String CONFIG_FILENAME = "config"; + private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY = + "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP"; + private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 100 * 1024; + + private final Context mContext; + private final BatteryStatsImpl mBatteryStats; + private final File mStoreDir; + private final File mLockFile; + private final AtomicFile mConfigFile; + private final long mMaxStorageBytes; + private final Handler mHandler; + private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; + + public BatteryUsageStatsStore(Context context, BatteryStatsImpl stats, File systemDir, + Handler handler) { + this(context, stats, systemDir, handler, MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); + } + + @VisibleForTesting + public BatteryUsageStatsStore(Context context, BatteryStatsImpl batteryStats, File systemDir, + Handler handler, long maxStorageBytes) { + mContext = context; + mBatteryStats = batteryStats; + mStoreDir = new File(systemDir, BATTERY_USAGE_STATS_DIR); + mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME); + mConfigFile = new AtomicFile(new File(mStoreDir, CONFIG_FILENAME)); + mHandler = handler; + mMaxStorageBytes = maxStorageBytes; + mBatteryStats.setBatteryResetListener(this::prepareForBatteryStatsReset); + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(mContext, mBatteryStats); + } + + private void prepareForBatteryStatsReset() { + final List<BatteryUsageStats> stats = + mBatteryUsageStatsProvider.getBatteryUsageStats(BATTERY_USAGE_STATS_QUERY); + if (stats.isEmpty()) { + Slog.wtf(TAG, "No battery usage stats generated"); + return; + } + + mHandler.post(() -> storeBatteryUsageStats(stats.get(0))); + } + + private void storeBatteryUsageStats(BatteryUsageStats stats) { + try (FileLock lock = lockSnapshotDirectory()) { + if (!mStoreDir.exists()) { + if (!mStoreDir.mkdirs()) { + Slog.e(TAG, + "Could not create a directory for battery usage stats snapshots"); + return; + } + } + File file = makeSnapshotFilename(stats.getStatsEndTimestamp()); + try { + writeXmlFileLocked(stats, file); + } catch (Exception e) { + Slog.e(TAG, "Cannot save battery usage stats", e); + } + + removeOldSnapshotsLocked(); + } catch (IOException e) { + Slog.e(TAG, "Cannot lock battery usage stats directory", e); + } + } + + /** + * Returns the timestamps of the stored BatteryUsageStats snapshots. The timestamp corresponds + * to the time the snapshot was taken {@link BatteryUsageStats#getStatsEndTimestamp()}. + */ + public long[] listBatteryUsageStatsTimestamps() { + LongArray timestamps = new LongArray(100); + try (FileLock lock = lockSnapshotDirectory()) { + for (File file : mStoreDir.listFiles()) { + String fileName = file.getName(); + if (fileName.endsWith(SNAPSHOT_FILE_EXTENSION)) { + try { + String fileNameWithoutExtension = fileName.substring(0, + fileName.length() - SNAPSHOT_FILE_EXTENSION.length()); + timestamps.add(Long.parseLong(fileNameWithoutExtension)); + } catch (NumberFormatException e) { + Slog.wtf(TAG, "Invalid format of BatteryUsageStats snapshot file name: " + + fileName); + } + } + } + } catch (IOException e) { + Slog.e(TAG, "Cannot lock battery usage stats directory", e); + } + return timestamps.toArray(); + } + + /** + * Reads the specified snapshot of BatteryUsageStats. Returns null if the snapshot + * does not exist. + */ + @Nullable + public BatteryUsageStats loadBatteryUsageStats(long timestamp) { + try (FileLock lock = lockSnapshotDirectory()) { + File file = makeSnapshotFilename(timestamp); + try { + return readXmlFileLocked(file); + } catch (Exception e) { + Slog.e(TAG, "Cannot read battery usage stats", e); + } + } catch (IOException e) { + Slog.e(TAG, "Cannot lock battery usage stats directory", e); + } + return null; + } + + /** + * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull + * in persistent file. + */ + public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) { + Properties props = new Properties(); + try (FileLock lock = lockSnapshotDirectory()) { + try (InputStream in = mConfigFile.openRead()) { + props.load(in); + } catch (IOException e) { + Slog.e(TAG, "Cannot load config file " + mConfigFile, e); + } + props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, + String.valueOf(timestamp)); + FileOutputStream out = null; + try { + out = mConfigFile.startWrite(); + props.store(out, "Statsd atom pull timestamps"); + mConfigFile.finishWrite(out); + } catch (IOException e) { + mConfigFile.failWrite(out); + Slog.e(TAG, "Cannot save config file " + mConfigFile, e); + } + } catch (IOException e) { + Slog.e(TAG, "Cannot lock battery usage stats directory", e); + } + } + + /** + * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET + * statsd atom pull. + */ + public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() { + Properties props = new Properties(); + try (FileLock lock = lockSnapshotDirectory()) { + try (InputStream in = mConfigFile.openRead()) { + props.load(in); + } catch (IOException e) { + Slog.e(TAG, "Cannot load config file " + mConfigFile, e); + } + } catch (IOException e) { + Slog.e(TAG, "Cannot lock battery usage stats directory", e); + } + return Long.parseLong( + props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0")); + } + + private FileLock lockSnapshotDirectory() throws IOException { + mLockFile.getParentFile().mkdirs(); + mLockFile.createNewFile(); + return FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock(); + } + + /** + * Creates a file name by formatting the timestamp as 19-digit zero-padded number. + * This ensures that sorted directory list follows the chronological order. + */ + private File makeSnapshotFilename(long statsEndTimestamp) { + return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", statsEndTimestamp) + + SNAPSHOT_FILE_EXTENSION); + } + + private void writeXmlFileLocked(BatteryUsageStats stats, File file) throws IOException { + try (OutputStream out = new FileOutputStream(file)) { + TypedXmlSerializer serializer = Xml.newBinarySerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + stats.writeXml(serializer); + serializer.endDocument(); + } + } + + private BatteryUsageStats readXmlFileLocked(File file) + throws IOException, XmlPullParserException { + try (InputStream in = new FileInputStream(file)) { + TypedXmlPullParser parser = Xml.newBinaryPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + return BatteryUsageStats.createFromXml(parser); + } + } + + private void removeOldSnapshotsLocked() { + // Read the directory list into a _sorted_ map. The alphanumeric ordering + // corresponds to the historical order of snapshots because the file names + // are timestamps zero-padded to the same length. + long totalSize = 0; + TreeMap<File, Long> mFileSizes = new TreeMap<>(); + for (File file : mStoreDir.listFiles()) { + final long fileSize = file.length(); + totalSize += fileSize; + if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) { + mFileSizes.put(file, fileSize); + } + } + + while (totalSize > mMaxStorageBytes) { + final Map.Entry<File, Long> entry = mFileSizes.firstEntry(); + if (entry == null) { + break; + } + + File file = entry.getKey(); + if (!file.delete()) { + Slog.e(TAG, "Cannot delete battery usage stats " + file); + } + totalSize -= entry.getValue(); + mFileSizes.remove(file); + } + } +} diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java index 5ec8b30d6a7b..25fa678d0507 100644 --- a/core/java/com/android/internal/view/inline/InlineTooltipUi.java +++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java @@ -213,6 +213,8 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable if (!mShowing) { params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + params.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE; mContentContainer.addOnLayoutChangeListener(mAnchoredOnLayoutChangeListener); mWm.addView(mContentContainer, params); mShowing = true; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 497846c51bac..6c1ba1afc073 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3360,6 +3360,15 @@ (e.g. accessibility, alarms). This is mainly for Wear devices that don't have speakers. --> <bool name="config_allowPriorityVibrationsInLowPowerMode">false</bool> + <!-- The duration (in milliseconds) that should be used to convert vibration ramps to a sequence + of fixed amplitude steps on devices without PWLE support. --> + <integer name="config_vibrationWaveformRampStepDuration">5</integer> + + <!-- The duration (in milliseconds) that should be applied to waveform vibrations that ends in + non-zero amplitudes, . The waveform will + be played as a PWLE instead of on/off calls if this value is set. --> + <integer name="config_vibrationWaveformRampDownDuration">0</integer> + <!-- Number of retries Cell Data should attempt for a given error code before restarting the modem. Error codes not listed will not lead to modem restarts. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5ef70800e1e5..b0153583c081 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2028,6 +2028,8 @@ <java-symbol type="integer" name="config_notificationServiceArchiveSize" /> <java-symbol type="integer" name="config_previousVibrationsDumpLimit" /> <java-symbol type="integer" name="config_defaultVibrationAmplitude" /> + <java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" /> + <java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" /> <java-symbol type="integer" name="config_radioScanningTimeout" /> <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" /> <java-symbol type="integer" name="config_screenBrightnessSettingMaximum" /> diff --git a/core/tests/coretests/src/android/os/PackageTagsListTest.java b/core/tests/coretests/src/android/os/PackageTagsListTest.java index 518e02e44b06..9034a5ccdd7f 100644 --- a/core/tests/coretests/src/android/os/PackageTagsListTest.java +++ b/core/tests/coretests/src/android/os/PackageTagsListTest.java @@ -30,6 +30,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; +import java.util.Collections; @Presubmit @RunWith(AndroidJUnit4.class) @@ -40,7 +41,8 @@ public class PackageTagsListTest { PackageTagsList.Builder builder = new PackageTagsList.Builder() .add("package1", "attr1") .add("package1", "attr2") - .add("package2"); + .add("package2") + .add("package4", Arrays.asList("attr1", "attr2")); PackageTagsList list = builder.build(); assertTrue(list.contains(builder.build())); @@ -49,10 +51,13 @@ public class PackageTagsListTest { assertTrue(list.contains("package2", "attr1")); assertTrue(list.contains("package2", "attr2")); assertTrue(list.contains("package2", "attr3")); + assertTrue(list.contains("package4", "attr1")); + assertTrue(list.contains("package4", "attr2")); assertTrue(list.containsAll("package2")); assertTrue(list.includes("package1")); assertTrue(list.includes("package2")); assertFalse(list.contains("package1", "attr3")); + assertFalse(list.contains("package4", "attr3")); assertFalse(list.containsAll("package1")); assertFalse(list.includes("package3")); @@ -92,6 +97,51 @@ public class PackageTagsListTest { } @Test + public void testPackageTagsList_Remove() { + PackageTagsList.Builder builder = new PackageTagsList.Builder() + .add("package1", "attr1") + .add("package1", "attr2") + .add("package2") + .add("package4", Arrays.asList("attr1", "attr2", "attr3")) + .add("package3", "attr1") + .remove("package1", "attr1") + .remove("package1", "attr2") + .remove("package2", "attr1") + .remove("package4", Arrays.asList("attr1", "attr2")) + .remove("package3"); + PackageTagsList list = builder.build(); + + assertTrue(list.contains(builder.build())); + assertFalse(list.contains("package1", "attr1")); + assertFalse(list.contains("package1", "attr2")); + assertTrue(list.contains("package2", "attr1")); + assertTrue(list.contains("package2", "attr2")); + assertTrue(list.contains("package2", "attr3")); + assertFalse(list.contains("package3", "attr1")); + assertFalse(list.contains("package4", "attr1")); + assertFalse(list.contains("package4", "attr2")); + assertTrue(list.contains("package4", "attr3")); + assertTrue(list.containsAll("package2")); + assertFalse(list.includes("package1")); + assertTrue(list.includes("package2")); + assertFalse(list.includes("package3")); + assertTrue(list.includes("package4")); + } + + @Test + public void testPackageTagsList_EmptyCollections() { + PackageTagsList.Builder builder = new PackageTagsList.Builder() + .add("package1", Collections.emptyList()) + .add("package2") + .remove("package2", Collections.emptyList()); + PackageTagsList list = builder.build(); + + assertTrue(list.contains(builder.build())); + assertFalse(list.contains("package1", "attr1")); + assertTrue(list.contains("package2", "attr2")); + } + + @Test public void testWriteToParcel() { PackageTagsList list = new PackageTagsList.Builder() .add("package1", "attr1") diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index 46e2772b30ca..90a9572b5560 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -44,6 +44,7 @@ import org.junit.runners.Suite; BatteryStatsUidTest.class, BatteryUsageStatsProviderTest.class, BatteryUsageStatsTest.class, + BatteryUsageStatsStoreTest.class, BatteryStatsUserLifecycleTests.class, BluetoothPowerCalculatorTest.class, BstatsCpuTimesValidationTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java index d83645d6e0a5..cbd67c8324f4 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java @@ -20,10 +20,14 @@ import static com.google.common.truth.Truth.assertThat; import android.app.ActivityManager; import android.content.Context; +import android.os.BatteryConsumer; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.Parcel; import android.os.Process; import android.os.UidBatteryConsumer; @@ -36,6 +40,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; import java.util.List; @SmallTest @@ -45,7 +50,8 @@ public class BatteryUsageStatsProviderTest { private static final long MINUTE_IN_MS = 60 * 1000; @Rule - public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(12345); + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(12345) + .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0); @Test public void test_getBatteryUsageStats() { @@ -187,4 +193,84 @@ public class BatteryUsageStatsProviderTest { mStatsRule.setTime(11500, 0); assertThat(provider.shouldUpdateStats(queries, 10000)).isTrue(); } + + @Test + public void testAggregateBatteryStats() { + Context context = InstrumentationRegistry.getContext(); + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + mStatsRule.setCurrentTime(5 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context, + batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"), + new TestHandler(), Integer.MAX_VALUE); + + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, + batteryStats, batteryUsageStatsStore); + + batteryStats.noteFlashlightOnLocked(APP_UID, + 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(25 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + batteryStats.noteFlashlightOnLocked(APP_UID, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(55 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + // This section should be ignored because the timestamp is out or range + batteryStats.noteFlashlightOnLocked(APP_UID, + 60 * MINUTE_IN_MS, 60 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(75 * MINUTE_IN_MS); + batteryStats.resetAllStatsCmdLocked(); + + // This section should be ignored because it represents the current stats session + batteryStats.noteFlashlightOnLocked(APP_UID, + 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + batteryStats.noteFlashlightOffLocked(APP_UID, + 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS); + mStatsRule.setCurrentTime(95 * MINUTE_IN_MS); + + // Include the first and the second snapshot, but not the third or current + BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() + .aggregateSnapshots(20 * MINUTE_IN_MS, 60 * MINUTE_IN_MS) + .build(); + final BatteryUsageStats stats = provider.getBatteryUsageStats(query); + + assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); + assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * MINUTE_IN_MS); + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isWithin(0.0001) + .of(180.0); // 360 mA * 0.5 hours + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isEqualTo((10 + 20) * MINUTE_IN_MS); + final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream() + .filter(uid -> uid.getUid() == APP_UID).findFirst().get(); + assertThat(uidBatteryConsumer + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isWithin(0.1) + .of(180.0); + } + + private static class TestHandler extends Handler { + TestHandler() { + super(Looper.getMainLooper()); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + msg.getCallback().run(); + return true; + } + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java new file mode 100644 index 000000000000..141a9fa30c85 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java @@ -0,0 +1,196 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.os.BatteryManager; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +public class BatteryUsageStatsStoreTest { + private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024; + + private final MockClocks mMockClocks = new MockClocks(); + private MockBatteryStatsImpl mBatteryStats; + private BatteryUsageStatsStore mBatteryUsageStatsStore; + private BatteryUsageStatsProvider mBatteryUsageStatsProvider; + private File mStoreDirectory; + + @Before + public void setup() { + mMockClocks.currentTime = 123; + mBatteryStats = new MockBatteryStatsImpl(mMockClocks); + mBatteryStats.setNoAutoReset(true); + mBatteryStats.setPowerProfile(mock(PowerProfile.class)); + + Context context = InstrumentationRegistry.getContext(); + + mStoreDirectory = new File(context.getCacheDir(), "BatteryUsageStatsStoreTest"); + clearDirectory(mStoreDirectory); + + mBatteryUsageStatsStore = new BatteryUsageStatsStore(context, mBatteryStats, + mStoreDirectory, new TestHandler(), MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); + + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats); + } + + @Test + public void testStoreSnapshot() { + mMockClocks.currentTime = 1_600_000; + + prepareBatteryStats(); + mBatteryStats.resetAllStatsCmdLocked(); + + final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); + assertThat(timestamps).hasLength(1); + assertThat(timestamps[0]).isEqualTo(1_600_000); + + final BatteryUsageStats batteryUsageStats = mBatteryUsageStatsStore.loadBatteryUsageStats( + 1_600_000); + assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(123); + assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000); + assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000); + assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5); + assertThat(batteryUsageStats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower()) + .isEqualTo(600); // (3_600_000 - 3_000_000) / 1000 + } + + @Test + public void testGarbageCollectOldSnapshots() throws Exception { + prepareBatteryStats(); + + mMockClocks.realtime = 10_000_000; + mMockClocks.uptime = 10_000_000; + mMockClocks.currentTime = 10_000_000; + + final int snapshotFileSize = getSnapshotFileSize(); + final int numberOfSnapshots = + (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / snapshotFileSize); + for (int i = 0; i < numberOfSnapshots + 2; i++) { + mBatteryStats.resetAllStatsCmdLocked(); + + mMockClocks.realtime += 10_000_000; + mMockClocks.uptime += 10_000_000; + mMockClocks.currentTime += 10_000_000; + prepareBatteryStats(); + } + + final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); + Arrays.sort(timestamps); + assertThat(timestamps).hasLength(numberOfSnapshots); + // Two snapshots (10_000_000 and 20_000_000) should have been discarded + assertThat(timestamps[0]).isEqualTo(30_000_000); + assertThat(getDirectorySize(mStoreDirectory)) + .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); + } + + @Test + public void testSavingStatsdAtomPullTimestamp() { + mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234); + assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) + .isEqualTo(1234); + mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478); + assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) + .isEqualTo(5478); + } + + private void prepareBatteryStats() { + mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, + /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, + mMockClocks.realtime, mMockClocks.uptime, mMockClocks.currentTime); + mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, + /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0, + mMockClocks.realtime + 500_000, mMockClocks.uptime + 500_000, + mMockClocks.currentTime + 500_000); + } + + private void clearDirectory(File dir) { + if (dir.exists()) { + for (File child : dir.listFiles()) { + if (child.isDirectory()) { + clearDirectory(child); + } + child.delete(); + } + } + } + + private long getDirectorySize(File dir) { + long size = 0; + if (dir.exists()) { + for (File child : dir.listFiles()) { + if (child.isDirectory()) { + size += getDirectorySize(child); + } else { + size += child.length(); + } + } + } + return size; + } + + private int getSnapshotFileSize() throws IOException { + BatteryUsageStats stats = mBatteryUsageStatsProvider.getBatteryUsageStats( + new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0) + .includePowerModels() + .build()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TypedXmlSerializer serializer = Xml.newBinarySerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + stats.writeXml(serializer); + serializer.endDocument(); + return out.toByteArray().length; + } + + private static class TestHandler extends Handler { + TestHandler() { + super(Looper.getMainLooper()); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + msg.getCallback().run(); + return true; + } + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java index 380b4ae7e748..3e620c2bbec6 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java @@ -16,8 +16,13 @@ package com.android.internal.os; +import static android.os.BatteryConsumer.POWER_MODEL_MEASURED_ENERGY; +import static android.os.BatteryConsumer.POWER_MODEL_POWER_PROFILE; +import static android.os.BatteryConsumer.POWER_MODEL_UNDEFINED; + import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import android.os.BatteryConsumer; @@ -25,6 +30,9 @@ import android.os.BatteryUsageStats; import android.os.Parcel; import android.os.UidBatteryConsumer; import android.os.UserBatteryConsumer; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -32,8 +40,11 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -42,15 +53,19 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) public class BatteryUsageStatsTest { + private static final int USER_ID = 42; + private static final int APP_UID1 = 271; + private static final int APP_UID2 = 314; + @Test public void testBuilder() { - BatteryUsageStats batteryUsageStats = buildBatteryUsageStats().build(); - validateBatteryUsageStats(batteryUsageStats); + BatteryUsageStats batteryUsageStats = buildBatteryUsageStats1(true).build(); + assertBatteryUsageStats1(batteryUsageStats, true); } @Test public void testParcelability() { - final BatteryUsageStats outBatteryUsageStats = buildBatteryUsageStats().build(); + final BatteryUsageStats outBatteryUsageStats = buildBatteryUsageStats1(true).build(); final Parcel outParcel = Parcel.obtain(); outParcel.writeParcelable(outBatteryUsageStats, 0); final byte[] bytes = outParcel.marshall(); @@ -62,20 +77,20 @@ public class BatteryUsageStatsTest { final BatteryUsageStats inBatteryUsageStats = inParcel.readParcelable(getClass().getClassLoader()); assertThat(inBatteryUsageStats).isNotNull(); - validateBatteryUsageStats(inBatteryUsageStats); + assertBatteryUsageStats1(inBatteryUsageStats, true); } @Test public void testDefaultSessionDuration() { final BatteryUsageStats stats = - buildBatteryUsageStats().setStatsDuration(10000).build(); + buildBatteryUsageStats1(true).setStatsDuration(10000).build(); assertThat(stats.getStatsDuration()).isEqualTo(10000); } @Test public void testDump() { - final BatteryUsageStats stats = buildBatteryUsageStats().build(); + final BatteryUsageStats stats = buildBatteryUsageStats1(true).build(); final StringWriter out = new StringWriter(); try (PrintWriter pw = new PrintWriter(out)) { stats.dump(pw, " "); @@ -87,7 +102,7 @@ public class BatteryUsageStatsTest { assertThat(dump).contains("actual drain: 1000-2000"); assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms"); assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms"); - assertThat(dump).contains("UID 2000: 1200 ( screen=300 cpu=400 FOO=500 )"); + assertThat(dump).contains("UID 271: 1200 ( screen=300 cpu=400 FOO=500 )"); assertThat(dump).contains("User 42: 30.0 ( cpu=10.0 FOO=20.0 )"); } @@ -101,154 +116,297 @@ public class BatteryUsageStatsTest { assertThat(allNames).hasSize(BatteryConsumer.POWER_COMPONENT_COUNT); } - private BatteryUsageStats.Builder buildBatteryUsageStats() { + @Test + public void testAdd() { + final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build(); + final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[] {"FOO"}).build(); + + final BatteryUsageStats sum = + new BatteryUsageStats.Builder(new String[] {"FOO"}, true) + .add(stats1) + .add(stats2) + .build(); + + assertBatteryUsageStats(sum, 42345, 50, 2234, 4345, 1000, 5000, 5000); + + final List<UidBatteryConsumer> uidBatteryConsumers = + sum.getUidBatteryConsumers(); + for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) { + if (uidBatteryConsumer.getUid() == APP_UID1) { + assertUidBatteryConsumer(uidBatteryConsumer, 2124, null, + 5321, 7432, 423, POWER_MODEL_POWER_PROFILE, 745, POWER_MODEL_UNDEFINED, + 956, 1167, 1478); + } else if (uidBatteryConsumer.getUid() == APP_UID2) { + assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar", + 1111, 2222, 333, POWER_MODEL_POWER_PROFILE, 444, POWER_MODEL_POWER_PROFILE, + 555, 666, 777); + } else { + fail("Unexpected UID " + uidBatteryConsumer.getUid()); + } + } + + assertAggregateBatteryConsumer(sum, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, + 20223, 20434, 20645, 20856); + + assertAggregateBatteryConsumer(sum, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, + 40211, 40422, 40633, 40844); + } + + @Test + public void testAdd_customComponentMismatch() { + final BatteryUsageStats.Builder builder = + new BatteryUsageStats.Builder(new String[] {"FOO"}, true); + final BatteryUsageStats stats = buildBatteryUsageStats2(new String[] {"BAR"}).build(); + + assertThrows(IllegalArgumentException.class, () -> builder.add(stats)); + } + + @Test + public void testXml() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TypedXmlSerializer serializer = Xml.newBinarySerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + final BatteryUsageStats stats = buildBatteryUsageStats1(true).build(); + stats.writeXml(serializer); + serializer.endDocument(); + + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + TypedXmlPullParser parser = Xml.newBinaryPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + final BatteryUsageStats fromXml = BatteryUsageStats.createFromXml(parser); + + assertBatteryUsageStats1(fromXml, true); + } + + private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer) { final MockClocks clocks = new MockClocks(); final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks); - final BatteryStatsImpl.Uid batteryStatsUid = batteryStats.getUidStatsLocked(2000); final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true) + new BatteryUsageStats.Builder(new String[] {"FOO"}, true) .setBatteryCapacity(4000) .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) .setStatsStartTimestamp(1000) .setStatsEndTimestamp(3000); - builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid) - .setPackageWithHighestDrain("foo") - .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000) - .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, 2000) - .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_SCREEN, 300) - .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 400) - .setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 500) - .setUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU, 600) - .setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 800); - builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) - .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 10100) - .setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200) - .setUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU, 10300) - .setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400); + addUidBatteryConsumer(builder, batteryStats, APP_UID1, "foo", + 1000, 2000, + 300, POWER_MODEL_POWER_PROFILE, 400, POWER_MODEL_POWER_PROFILE, 500, 600, 800); + + addAggregateBatteryConsumer(builder, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, 0, + 10100, 10200, 10300, 10400); + + addAggregateBatteryConsumer(builder, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, 30000, + 20100, 20200, 20300, 20400); + + + if (includeUserBatteryConsumer) { + builder.getOrCreateUserBatteryConsumerBuilder(USER_ID) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU, 10) + .setConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20) + .setUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_CPU, 30) + .setUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 40); + } + return builder; + } + + private BatteryUsageStats.Builder buildBatteryUsageStats2(String[] customPowerComponentNames) { + final MockClocks clocks = new MockClocks(); + final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks); + + final BatteryUsageStats.Builder builder = + new BatteryUsageStats.Builder(customPowerComponentNames, true) + .setDischargePercentage(30) + .setDischargedPowerRange(1234, 2345) + .setStatsStartTimestamp(2000) + .setStatsEndTimestamp(5000); - builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) - .setConsumedPower(30000) + addUidBatteryConsumer(builder, batteryStats, APP_UID1, null, + 4321, 5432, + 123, POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_MEASURED_ENERGY, 456, 567, 678); + + addUidBatteryConsumer(builder, batteryStats, APP_UID2, "bar", + 1111, 2222, + 333, POWER_MODEL_POWER_PROFILE, 444, POWER_MODEL_POWER_PROFILE, 555, 666, 777); + + addAggregateBatteryConsumer(builder, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, 0, + 10123, 10234, 10345, 10456); + + addAggregateBatteryConsumer(builder, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, 12345, + 20111, 20222, 20333, 20444); + + return builder; + } + + private void addUidBatteryConsumer(BatteryUsageStats.Builder builder, + MockBatteryStatsImpl batteryStats, int uid, String packageWithHighestDrain, + int timeInStateForeground, int timeInStateBackground, int screenPower, + int screenPowerModel, int cpuPower, int cpuPowerModel, int customComponentPower, + int cpuDuration, int customComponentDuration) { + final BatteryStatsImpl.Uid batteryStatsUid = batteryStats.getUidStatsLocked(uid); + builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid) + .setPackageWithHighestDrain(packageWithHighestDrain) + .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, timeInStateForeground) + .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, timeInStateBackground) .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 20100) + BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower, screenPowerModel) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU, cpuPower, cpuPowerModel) .setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200) + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower) .setUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU, 20300) + BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration) .setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20400); + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration); + } - builder.getOrCreateUserBatteryConsumerBuilder(42) + private void addAggregateBatteryConsumer(BatteryUsageStats.Builder builder, int scope, + double consumedPower, int cpuPower, int customComponentPower, int cpuDuration, + int customComponentDuration) { + builder.getAggregateBatteryConsumerBuilder(scope) + .setConsumedPower(consumedPower) .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 10) + BatteryConsumer.POWER_COMPONENT_CPU, cpuPower) .setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20) + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower) .setUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU, 30) + BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration) .setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 40); - - return builder; + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration); } - public void validateBatteryUsageStats(BatteryUsageStats batteryUsageStats) { - assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(30000); - assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000); - assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(20); - assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(1000); - assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(2000); - assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(1000); - assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(3000); - assertThat(batteryUsageStats.getStatsDuration()).isEqualTo(2000); + public void assertBatteryUsageStats1(BatteryUsageStats batteryUsageStats, + boolean includesUserBatteryConsumers) { + assertBatteryUsageStats(batteryUsageStats, 30000, 20, 1000, 2000, 1000, 3000, 2000); final List<UidBatteryConsumer> uidBatteryConsumers = batteryUsageStats.getUidBatteryConsumers(); + assertThat(uidBatteryConsumers).hasSize(1); for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) { - if (uidBatteryConsumer.getUid() == 2000) { - assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo("foo"); - assertThat(uidBatteryConsumer.getTimeInStateMs( - UidBatteryConsumer.STATE_FOREGROUND)).isEqualTo(1000); - assertThat(uidBatteryConsumer.getTimeInStateMs( - UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(2000); - assertThat(uidBatteryConsumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(300); - assertThat(uidBatteryConsumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(400); - assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(500); - assertThat(uidBatteryConsumer.getUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(600); - assertThat(uidBatteryConsumer.getUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(800); - assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(1200); - assertThat(uidBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); - assertThat(uidBatteryConsumer.getCustomPowerComponentName( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); + if (uidBatteryConsumer.getUid() == APP_UID1) { + assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo", + 1000, 2000, 300, POWER_MODEL_POWER_PROFILE, 400, POWER_MODEL_POWER_PROFILE, + 500, 600, 800); } else { fail("Unexpected UID " + uidBatteryConsumer.getUid()); } } + final List<UserBatteryConsumer> userBatteryConsumers = + batteryUsageStats.getUserBatteryConsumers(); + if (includesUserBatteryConsumers) { + assertThat(userBatteryConsumers).hasSize(1); + for (UserBatteryConsumer userBatteryConsumer : userBatteryConsumers) { + if (userBatteryConsumer.getUserId() == USER_ID) { + assertUserBatteryConsumer(userBatteryConsumer, 42, 10, 20, 30, 40); + } else { + fail("Unexpected User ID " + userBatteryConsumer.getUserId()); + } + } + } else { + assertThat(userBatteryConsumers).isEmpty(); + } + + assertAggregateBatteryConsumer(batteryUsageStats, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, + 10100, 10200, 10300, 10400); + + assertAggregateBatteryConsumer(batteryUsageStats, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, + 20100, 20200, 20300, 20400); + } + + private void assertBatteryUsageStats(BatteryUsageStats batteryUsageStats, int consumedPower, + int dischargePercentage, int dischagePowerLower, int dischargePowerUpper, + int statsStartTimestamp, int statsEndTimestamp, int statsDuration) { + assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(consumedPower); + assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(dischargePercentage); + assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo( + dischagePowerLower); + assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo( + dischargePowerUpper); + assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(statsStartTimestamp); + assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(statsEndTimestamp); + assertThat(batteryUsageStats.getStatsDuration()).isEqualTo(statsDuration); + } + + private void assertUidBatteryConsumer(UidBatteryConsumer uidBatteryConsumer, + int consumedPower, String packageWithHighestDrain, int timeInStateForeground, + int timeInStateBackground, int screenPower, int screenPowerModel, int cpuPower, + int cpuPowerModel, int customComponentPower, int cpuDuration, + int customComponentDuration) { + assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(consumedPower); + assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo( + packageWithHighestDrain); + assertThat(uidBatteryConsumer.getTimeInStateMs( + UidBatteryConsumer.STATE_FOREGROUND)).isEqualTo(timeInStateForeground); + assertThat(uidBatteryConsumer.getTimeInStateMs( + UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(timeInStateBackground); + assertThat(uidBatteryConsumer.getConsumedPower( + BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPower); + assertThat(uidBatteryConsumer.getPowerModel( + BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPowerModel); + assertThat(uidBatteryConsumer.getConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower); + assertThat(uidBatteryConsumer.getPowerModel( + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPowerModel); + assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower); + assertThat(uidBatteryConsumer.getUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuDuration); + assertThat(uidBatteryConsumer.getUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo( + customComponentDuration); + assertThat(uidBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); + assertThat(uidBatteryConsumer.getCustomPowerComponentName( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); + } + + private void assertUserBatteryConsumer(UserBatteryConsumer userBatteryConsumer, + int userId, int cpuPower, int customComponentPower, + int cpuDuration, int customComponentDuration) { + assertThat(userBatteryConsumer.getConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower); + assertThat(userBatteryConsumer.getConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower); + assertThat(userBatteryConsumer.getUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuDuration); + assertThat(userBatteryConsumer.getUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo( + customComponentDuration); + assertThat(userBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); + assertThat(userBatteryConsumer.getCustomPowerComponentName( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); + } + + private void assertAggregateBatteryConsumer(BatteryUsageStats batteryUsageStats, + int aggregateBatteryConsumerScopeAllApps, int cpuPower, int customComponentPower, + int cpuDuration, int customComponentDuration) { final BatteryConsumer appsBatteryConsumer = batteryUsageStats.getAggregateBatteryConsumer( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); + aggregateBatteryConsumerScopeAllApps); assertThat(appsBatteryConsumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10100); + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower); assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10200); + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower); assertThat(appsBatteryConsumer.getUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10300); + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuDuration); assertThat(appsBatteryConsumer.getUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10400); + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo( + customComponentDuration); assertThat(appsBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); assertThat(appsBatteryConsumer.getCustomPowerComponentName( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); - - final BatteryConsumer deviceBatteryConsumer = batteryUsageStats.getAggregateBatteryConsumer( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); - assertThat(deviceBatteryConsumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(20100); - assertThat(deviceBatteryConsumer.getConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(20200); - assertThat(deviceBatteryConsumer.getUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(20300); - assertThat(deviceBatteryConsumer.getUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(20400); - assertThat(deviceBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); - assertThat(deviceBatteryConsumer.getCustomPowerComponentName( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); - - final List<UserBatteryConsumer> userBatteryConsumers = - batteryUsageStats.getUserBatteryConsumers(); - for (UserBatteryConsumer userBatteryConsumer : userBatteryConsumers) { - if (userBatteryConsumer.getUserId() == 42) { - assertThat(userBatteryConsumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10); - assertThat(userBatteryConsumer.getConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(20); - assertThat(userBatteryConsumer.getUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(30); - assertThat(userBatteryConsumer.getUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(40); - assertThat(userBatteryConsumer.getConsumedPower()).isEqualTo(30); - assertThat(userBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1); - assertThat(userBatteryConsumer.getCustomPowerComponentName( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO"); - } else { - fail("Unexpected user ID " + userBatteryConsumer.getUserId()); - } - } } } diff --git a/data/etc/car/com.android.car.cluster.home.xml b/data/etc/car/com.android.car.cluster.home.xml index e1d2b18d7167..a3d0fcffc813 100644 --- a/data/etc/car/com.android.car.cluster.home.xml +++ b/data/etc/car/com.android.car.cluster.home.xml @@ -18,5 +18,6 @@ <privapp-permissions package="com.android.car.cluster.home"> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/> + <permission name="android.car.permission.CAR_MONITOR_INPUT"/> </privapp-permissions> </permissions> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 3c9086dde021..ac5e2d0fcacb 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -961,6 +961,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "-1003678883": { + "message": "Cleaning splash screen token=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_STARTING_WINDOW", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "-1003060523": { "message": "Finish needs to pause: %s", "level": "VERBOSE", diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 954d062b55e9..6aa74cb415f9 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -753,8 +753,12 @@ public class HardwareRenderer { nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater()); } + private ASurfaceTransactionCallback mASurfaceTransactionCallback; + /** @hide */ public void setASurfaceTransactionCallback(ASurfaceTransactionCallback callback) { + // ensure callback is kept alive on the java side since weak ref is used in native code + mASurfaceTransactionCallback = callback; nSetASurfaceTransactionCallback(mNativeProxy, callback); } diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index fe80b5845bf5..1651a8cdcad5 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -221,6 +221,7 @@ public class RippleDrawable extends LayerDrawable { private boolean mForceSoftware; // Patterned + private boolean mAddRipple = false; private float mTargetBackgroundOpacity; private ValueAnimator mBackgroundAnimation; private float mBackgroundOpacity; @@ -716,6 +717,7 @@ public class RippleDrawable extends LayerDrawable { } cancelExitingRipples(); + exitPatternedAnimation(); } @Override @@ -807,7 +809,7 @@ public class RippleDrawable extends LayerDrawable { } private void startPatternedAnimation() { - mRippleActive = true; + mAddRipple = true; invalidateSelf(false); } @@ -862,17 +864,17 @@ public class RippleDrawable extends LayerDrawable { h = bounds.height(); w = bounds.width(); } - boolean shouldAnimate = mRippleActive; + boolean addRipple = mAddRipple; boolean shouldExit = mExitingAnimation; - mRippleActive = false; mExitingAnimation = false; - if (mRunningAnimations.size() > 0 && !shouldAnimate) { + mAddRipple = false; + if (mRunningAnimations.size() > 0 && !addRipple) { // update paint when view is invalidated getRipplePaint(); } drawContent(canvas); drawPatternedBackground(canvas, cx, cy); - if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) { + if (addRipple && mRunningAnimations.size() <= MAX_RIPPLES) { RippleAnimationSession.AnimationProperties<Float, Paint> properties = createAnimationProperties(x, y, cx, cy, w, h); mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps) diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 4c2863e4f594..8cea869aea34 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -40,7 +40,7 @@ <integer name="long_press_dock_anim_duration">250</integer> <!-- Animation duration for translating of one handed when trigger / dismiss. --> - <integer name="config_one_handed_translate_animation_duration">800</integer> + <integer name="config_one_handed_translate_animation_duration">600</integer> <!-- One handed mode default offset % of display size --> <fraction name="config_one_handed_offset">40%</fraction> 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 f7fb63d9ab98..4b1955e56a6c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -325,6 +325,13 @@ public class ShellTaskOrganizer extends TaskOrganizer { } @Override + public void onAppSplashScreenViewRemoved(int taskId) { + if (mStartingWindow != null) { + mStartingWindow.onAppSplashScreenViewRemoved(taskId); + } + } + + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (mLock) { onTaskAppeared(new TaskAppearedInfo(taskInfo, leash)); 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 8dc05de9bb8f..a525c2c0219c 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 @@ -68,6 +68,11 @@ public interface OneHanded { void setLockedDisabled(boolean locked, boolean enabled); /** + * Registers callback to notify WMShell when user tap shortcut to expand notification. + */ + void registerEventCallback(OneHandedEventCallback callback); + + /** * Registers callback to be notified after {@link OneHandedDisplayAreaOrganizer} * transition start or finish */ 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 c275d50a5d56..b43daa0da2c0 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 @@ -43,6 +43,7 @@ import android.util.Slog; import android.view.Surface; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -72,6 +73,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> private static final String ONE_HANDED_MODE_GESTURAL_OVERLAY = "com.android.internal.systemui.onehanded.gestural"; private static final int OVERLAY_ENABLED_DELAY_MS = 250; + private static final int DISPLAY_AREA_READY_RETRY_MS = 10; static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode"; @@ -99,6 +101,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> private final Handler mMainHandler; private final OneHandedImpl mImpl = new OneHandedImpl(); + private OneHandedEventCallback mEventCallback; private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer; private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; @@ -288,7 +291,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> mTimeoutObserver = getObserver(this::onTimeoutSettingChanged); mTaskChangeExitObserver = getObserver(this::onTaskChangeExitSettingChanged); mSwipeToNotificationEnabledObserver = - getObserver(this::onSwipeToNotificationEnabledSettingChanged); + getObserver(this::onSwipeToNotificationEnabledChanged); mDisplayController.addDisplayChangingController(mRotationController); setupCallback(); @@ -358,14 +361,23 @@ public class OneHandedController implements RemoteCallable<OneHandedController> Slog.d(TAG, "Temporary lock disabled"); return; } + + if (!mDisplayAreaOrganizer.isReady()) { + // Must wait until DisplayAreaOrganizer is ready for transitioning. + mMainExecutor.executeDelayed(this::startOneHanded, DISPLAY_AREA_READY_RETRY_MS); + return; + } + if (mState.isTransitioning() || mState.isInOneHanded()) { return; } + final int currentRotation = mDisplayAreaOrganizer.getDisplayLayout().rotation(); if (currentRotation != Surface.ROTATION_0 && currentRotation != Surface.ROTATION_180) { Slog.w(TAG, "One handed mode only support portrait mode"); return; } + mState.setState(STATE_ENTERING); final int yOffSet = Math.round( mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction); @@ -394,6 +406,10 @@ public class OneHandedController implements RemoteCallable<OneHandedController> mOneHandedUiEventLogger.writeEvent(uiEvent); } + void registerEventCallback(OneHandedEventCallback callback) { + mEventCallback = callback; + } + @VisibleForTesting void registerTransitionCallback(OneHandedTransitionCallback callback) { mDisplayAreaOrganizer.registerTransitionCallback(callback); @@ -464,8 +480,29 @@ public class OneHandedController implements RemoteCallable<OneHandedController> } @VisibleForTesting + void notifyExpandNotification() { + mMainExecutor.execute(() -> mEventCallback.notifyExpandNotification()); + } + + @VisibleForTesting + void notifyUserConfigChanged(boolean success) { + if (!success) { + return; + } + // TODO Check UX if popup Toast to notify user when auto-enabled one-handed is good option. + Toast.makeText(mContext, R.string.one_handed_tutorial_title, Toast.LENGTH_LONG).show(); + } + + @VisibleForTesting void onActivatedActionChanged() { - if (mState.isTransitioning() || !isOneHandedEnabled()) { + if (!isOneHandedEnabled()) { + final boolean success = mOneHandedSettingsUtil.setOneHandedModeEnabled( + mContext.getContentResolver(), 1 /* Enabled for shortcut */, mUserId); + notifyUserConfigChanged(success); + } + + if (isSwipeToNotificationEnabled()) { + notifyExpandNotification(); return; } @@ -494,11 +531,9 @@ public class OneHandedController implements RemoteCallable<OneHandedController> setOneHandedEnabled(enabled); // Also checks swipe to notification settings since they all need gesture overlay. - // Enabled overlay package may affect the current animation(e.g:Settings switch), - // so we delay 250ms to enabled overlay after switch animation finish - mMainExecutor.executeDelayed(() -> setEnabledGesturalOverlay( + setEnabledGesturalOverlay( enabled || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( - mContext.getContentResolver(), mUserId)), OVERLAY_ENABLED_DELAY_MS); + mContext.getContentResolver(), mUserId), true /* DelayExecute */); } @VisibleForTesting @@ -542,7 +577,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> } @VisibleForTesting - void onSwipeToNotificationEnabledSettingChanged() { + void onSwipeToNotificationEnabledChanged() { final boolean enabled = mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( mContext.getContentResolver(), mUserId); @@ -551,7 +586,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> // Also checks one handed mode settings since they all need gesture overlay. setEnabledGesturalOverlay( enabled || mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( - mContext.getContentResolver(), mUserId)); + mContext.getContentResolver(), mUserId), true /* DelayExecute */); } private void setupTimeoutListener() { @@ -569,11 +604,19 @@ public class OneHandedController implements RemoteCallable<OneHandedController> return mIsOneHandedEnabled; } + @VisibleForTesting + boolean isSwipeToNotificationEnabled() { + return mIsSwipeToNotificationEnabled; + } + private void updateOneHandedEnabled() { if (mState.getState() == STATE_ENTERING || mState.getState() == STATE_ACTIVE) { mMainExecutor.execute(() -> stopOneHanded()); } + // Reset and align shortcut one_handed_mode_activated status with current mState + notifyShortcutState(mState.getState()); + mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled); if (!mIsOneHandedEnabled) { @@ -608,12 +651,19 @@ public class OneHandedController implements RemoteCallable<OneHandedController> if (info != null && !info.isEnabled()) { // Enable the default gestural one handed overlay. - setEnabledGesturalOverlay(true); + setEnabledGesturalOverlay(true /* enabled */, false /* delayExecute */); } } @VisibleForTesting - private void setEnabledGesturalOverlay(boolean enabled) { + private void setEnabledGesturalOverlay(boolean enabled, boolean delayExecute) { + if (mState.isTransitioning() || delayExecute) { + // Enabled overlay package may affect the current animation(e.g:Settings switch), + // so we delay 250ms to enabled overlay after switch animation finish, only delay once. + mMainExecutor.executeDelayed(() -> setEnabledGesturalOverlay(enabled, false), + OVERLAY_ENABLED_DELAY_MS); + return; + } try { mOverlayManager.setEnabled(ONE_HANDED_MODE_GESTURAL_OVERLAY, enabled, USER_CURRENT); } catch (RemoteException e) { @@ -628,6 +678,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> if (enabled == isFeatureEnabled) { return; } + mLockedDisabled = locked && !enabled; } @@ -761,6 +812,13 @@ public class OneHandedController implements RemoteCallable<OneHandedController> } @Override + public void registerEventCallback(OneHandedEventCallback callback) { + mMainExecutor.execute(() -> { + OneHandedController.this.registerEventCallback(callback); + }); + } + + @Override public void registerTransitionCallback(OneHandedTransitionCallback callback) { mMainExecutor.execute(() -> { OneHandedController.this.registerTransitionCallback(callback); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index b8da37fd0c25..d749c320bf94 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -61,11 +61,12 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { private DisplayLayout mDisplayLayout = new DisplayLayout(); - private float mLastVisualOffset = 0; private final Rect mLastVisualDisplayBounds = new Rect(); private final Rect mDefaultDisplayBounds = new Rect(); private final OneHandedSettingsUtil mOneHandedSettingsUtil; + private boolean mIsReady; + private float mLastVisualOffset = 0; private int mEnterExitAnimationDurationMs; private ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap = new ArrayMap(); @@ -157,6 +158,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { final DisplayAreaAppearedInfo info = displayAreaInfos.get(i); onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash()); } + mIsReady = true; updateDisplayBounds(); return displayAreaInfos; } @@ -164,9 +166,14 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { @Override public void unregisterOrganizer() { super.unregisterOrganizer(); + mIsReady = false; resetWindowsOffset(); } + boolean isReady() { + return mIsReady; + } + /** * Handler for display rotation changes by {@link DisplayLayout} * @@ -312,6 +319,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { pw.println(mDisplayAreaTokenMap); pw.print(innerPrefix + "mDefaultDisplayBounds="); pw.println(mDefaultDisplayBounds); + pw.print(innerPrefix + "mIsReady="); + pw.println(mIsReady); pw.print(innerPrefix + "mLastVisualDisplayBounds="); pw.println(mLastVisualDisplayBounds); pw.print(innerPrefix + "mLastVisualOffset="); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedEventCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedEventCallback.java new file mode 100644 index 000000000000..d07eea271eac --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedEventCallback.java @@ -0,0 +1,28 @@ +/* + * 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; + +/** + * Additional callback interface for OneHanded events. + */ +public interface OneHandedEventCallback { + /** + * Called to notify expand notification shade. + */ + default void notifyExpandNotification() { + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java index 90fc823fb574..da53b359a304 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java @@ -105,6 +105,17 @@ public final class OneHandedSettingsUtil { } /** + * Sets one handed enable or disable flag from Settings provider. + * + * @return true if the value was set, false on database errors + */ + public boolean setOneHandedModeEnabled(ContentResolver resolver, int enabled, int userId) { + return Settings.Secure.putIntForUser(resolver, + Settings.Secure.ONE_HANDED_MODE_ENABLED, enabled, userId); + } + + + /** * Queries taps app to exit config from Settings provider. * * @return enable or disable taps app exit. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 841edef9172f..f0bd8a2846ed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -235,15 +235,20 @@ public class PipResizeGestureHandler { @VisibleForTesting void onInputEvent(InputEvent ev) { + if (!mEnableDragCornerResize && !mEnablePinchResize) { + // No need to handle anything if neither form of resizing is enabled. + return; + } + // Don't allow resize when PiP is stashed. if (mPipBoundsState.isStashed()) { return; } if (ev instanceof MotionEvent) { - if (mOngoingPinchToResize) { + if (mEnablePinchResize && mOngoingPinchToResize) { onPinchResize((MotionEvent) ev); - } else { + } else if (mEnableDragCornerResize) { onDragCornerResize((MotionEvent) ev); } } @@ -318,8 +323,8 @@ public class PipResizeGestureHandler { case MotionEvent.ACTION_POINTER_DOWN: if (mEnablePinchResize && ev.getPointerCount() == 2) { onPinchResize(ev); - mOngoingPinchToResize = true; - return true; + mOngoingPinchToResize = mAllowGesture; + return mAllowGesture; } break; 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 4d33cb0452dc..46db35a6e29f 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 @@ -35,6 +35,7 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.hardware.display.DisplayManager; import android.os.IBinder; +import android.os.RemoteCallback; import android.os.Trace; import android.os.UserHandle; import android.util.Slog; @@ -42,6 +43,7 @@ import android.util.SparseArray; import android.view.Choreographer; import android.view.Display; import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.View; import android.view.WindowManager; import android.widget.FrameLayout; @@ -121,6 +123,13 @@ public class StartingSurfaceDrawer { private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>(); + /** + * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is + * rendered and that have not yet been removed by their client. + */ + private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts = + new SparseArray<>(1); + /** Obtain proper context for showing splash screen on the provided display. */ private Context getDisplayContext(Context context, int displayId) { if (displayId == DEFAULT_DISPLAY) { @@ -386,25 +395,58 @@ public class StartingSurfaceDrawer { /** * Called when the Task wants to copy the splash screen. - * @param taskId */ public void copySplashScreenView(int taskId) { final StartingWindowRecord preView = mStartingWindowRecords.get(taskId); SplashScreenViewParcelable parcelable; - if (preView != null && preView.mContentView != null - && preView.mContentView.isCopyable()) { - parcelable = new SplashScreenViewParcelable(preView.mContentView); - preView.mContentView.onCopied(); + SplashScreenView splashScreenView = preView != null ? preView.mContentView : null; + if (splashScreenView != null && splashScreenView.isCopyable()) { + parcelable = new SplashScreenViewParcelable(splashScreenView); + parcelable.setClientCallback( + new RemoteCallback((bundle) -> mSplashScreenExecutor.execute( + () -> onAppSplashScreenViewRemoved(taskId, false)))); + splashScreenView.onCopied(); + mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost()); } else { parcelable = null; } if (DEBUG_SPLASH_SCREEN) { Slog.v(TAG, "Copying splash screen window view for task: " + taskId - + " parcelable? " + parcelable); + + " parcelable: " + parcelable); } ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable); } + /** + * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy + * or when the Activity is clean up. + * + * @param taskId The Task id on which the splash screen was attached + */ + public void onAppSplashScreenViewRemoved(int taskId) { + onAppSplashScreenViewRemoved(taskId, true /* fromServer */); + } + + /** + * @param fromServer If true, this means the removal was notified by the server. This is only + * used for debugging purposes. + * @see #onAppSplashScreenViewRemoved(int) + */ + private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) { + SurfaceControlViewHost viewHost = + mAnimatedSplashScreenSurfaceHosts.get(taskId); + if (viewHost == null) { + return; + } + mAnimatedSplashScreenSurfaceHosts.remove(taskId); + if (DEBUG_SPLASH_SCREEN) { + String reason = fromServer ? "Server cleaned up" : "App removed"; + Slog.v(TAG, reason + "the splash screen. Releasing SurfaceControlViewHost for task:" + + taskId); + } + viewHost.getView().post(viewHost::release); + } + protected boolean addWindow(int taskId, IBinder appToken, View view, WindowManager wm, WindowManager.LayoutParams params) { boolean shouldSaveView = true; 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 cffc789106cb..9c1dde925762 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 @@ -150,6 +150,14 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo } /** + * @see StartingSurfaceDrawer#onAppSplashScreenViewRemoved(int) + */ + public void onAppSplashScreenViewRemoved(int taskId) { + mSplashScreenExecutor.execute( + () -> mStartingSurfaceDrawer.onAppSplashScreenViewRemoved(taskId)); + } + + /** * Called when the content of a task is ready to show, starting window can be removed. */ public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 1852279ee96c..47789b7490ee 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -74,6 +74,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Mock OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; @Mock + OneHandedEventCallback mMockEventCallback; + @Mock OneHandedTouchHandler mMockTouchHandler; @Mock OneHandedTutorialHandler mMockTutorialHandler; @@ -106,6 +108,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); + when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true); when(mMockBackgroundOrganizer.getBackgroundSurface()).thenReturn(mMockLeash); when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn( mDefaultEnabled); @@ -241,7 +244,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Test public void testSettingsObserverUpdateSwipeToNotification() { - mSpiedOneHandedController.onSwipeToNotificationEnabledSettingChanged(); + mSpiedOneHandedController.onSwipeToNotificationEnabledChanged(); verify(mSpiedOneHandedController).setSwipeToNotificationEnabled(anyBoolean()); } @@ -311,6 +314,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { final DisplayLayout testDisplayLayout = new DisplayLayout(mDisplayLayout); testDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180); mSpiedTransitionState.setState(STATE_NONE); + when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true); when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(testDisplayLayout); mSpiedOneHandedController.setOneHandedEnabled(true); mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */); @@ -372,8 +376,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); mSpiedOneHandedController.onActivatedActionChanged(); - verify(mSpiedOneHandedController, never()).startOneHanded(); - verify(mSpiedOneHandedController, never()).stopOneHanded(); + verify(mSpiedTransitionState, never()).setState(STATE_EXITING); } @Test @@ -383,20 +386,20 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); mSpiedOneHandedController.onActivatedActionChanged(); - verify(mSpiedOneHandedController, never()).startOneHanded(); - verify(mSpiedOneHandedController, never()).stopOneHanded(); + verify(mSpiedTransitionState, never()).setState(STATE_ENTERING); } @Test - public void testOneHandedDisabled_shortcutEnabled_skipActions() { + public void testOneHandedDisabled_shortcutTrigger_thenAutoEnabled() { when(mSpiedOneHandedController.isOneHandedEnabled()).thenReturn(false); when(mSpiedTransitionState.getState()).thenReturn(STATE_NONE); when(mSpiedTransitionState.isTransitioning()).thenReturn(false); - when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); + when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(false); + when(mMockSettingsUitl.setOneHandedModeEnabled(any(), anyInt(), anyInt())).thenReturn( + false); mSpiedOneHandedController.onActivatedActionChanged(); - verify(mSpiedOneHandedController, never()).startOneHanded(); - verify(mSpiedOneHandedController, never()).stopOneHanded(); + verify(mSpiedOneHandedController).notifyUserConfigChanged(anyBoolean()); } @Test @@ -408,4 +411,28 @@ public class OneHandedControllerTest extends OneHandedTestCase { verify(mSpiedTransitionState).addSListeners(mMockTutorialHandler); } + + @Test + public void testNotifyEventCallbackWithMainExecutor() { + when(mSpiedOneHandedController.isOneHandedEnabled()).thenReturn(true); + when(mSpiedTransitionState.getState()).thenReturn(STATE_NONE); + when(mSpiedTransitionState.isTransitioning()).thenReturn(false); + when(mSpiedOneHandedController.isSwipeToNotificationEnabled()).thenReturn(true); + mSpiedOneHandedController.registerEventCallback(mMockEventCallback); + mSpiedOneHandedController.onActivatedActionChanged(); + + verify(mMockShellMainExecutor).execute(any()); + } + + @Test + public void testNotifyShortcutState_whenUpdateOneHandedEnabled() { + when(mSpiedOneHandedController.isOneHandedEnabled()).thenReturn(false); + when(mSpiedTransitionState.getState()).thenReturn(STATE_NONE); + when(mSpiedTransitionState.isTransitioning()).thenReturn(false); + when(mSpiedOneHandedController.isSwipeToNotificationEnabled()).thenReturn(true); + mSpiedOneHandedController.registerEventCallback(mMockEventCallback); + mSpiedOneHandedController.setOneHandedEnabled(true); + + verify(mSpiedOneHandedController).notifyShortcutState(anyInt()); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java index a27ed114de70..ef16fd391235 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -418,4 +418,18 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(); } + + @Test + public void testDisplayArea_notReadyForTransition() { + OneHandedDisplayAreaOrganizer testSpiedDisplayAreaOrganizer = spy( + new OneHandedDisplayAreaOrganizer(mContext, + mDisplayLayout, + mMockSettingsUitl, + mMockAnimationController, + mTutorialHandler, + mMockBackgroundOrganizer, + mMockShellMainExecutor)); + + assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse(); + } } diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 602c32a966d3..819a34b21a05 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -500,6 +500,28 @@ private: jobject mObject; }; +class JWeakGlobalRefHolder { +public: + JWeakGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm) { + mWeakRef = getenv(vm)->NewWeakGlobalRef(object); + } + + virtual ~JWeakGlobalRefHolder() { + if (mWeakRef != nullptr) getenv(mVm)->DeleteWeakGlobalRef(mWeakRef); + mWeakRef = nullptr; + } + + jobject ref() { return mWeakRef; } + JavaVM* vm() { return mVm; } + +private: + JWeakGlobalRefHolder(const JWeakGlobalRefHolder&) = delete; + void operator=(const JWeakGlobalRefHolder&) = delete; + + JavaVM* mVm; + jobject mWeakRef; +}; + using TextureMap = std::unordered_map<uint32_t, sk_sp<SkImage>>; struct PictureCaptureState { @@ -633,15 +655,19 @@ static void android_view_ThreadedRenderer_setASurfaceTransactionCallback( } else { JavaVM* vm = nullptr; LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); - auto globalCallbackRef = std::make_shared<JGlobalRefHolder>( - vm, env->NewGlobalRef(aSurfaceTransactionCallback)); + auto globalCallbackRef = + std::make_shared<JWeakGlobalRefHolder>(vm, aSurfaceTransactionCallback); proxy->setASurfaceTransactionCallback( [globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) { JNIEnv* env = getenv(globalCallbackRef->vm()); - env->CallVoidMethod(globalCallbackRef->object(), - gASurfaceTransactionCallback.onMergeTransaction, + jobject localref = env->NewLocalRef(globalCallbackRef->ref()); + if (CC_UNLIKELY(!localref)) { + return; + } + env->CallVoidMethod(localref, gASurfaceTransactionCallback.onMergeTransaction, static_cast<jlong>(transObj), static_cast<jlong>(scObj), static_cast<jlong>(frameNr)); + env->DeleteLocalRef(localref); }); } } diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java index 763835c9cbe2..d59756d02348 100644 --- a/location/java/android/location/LocationManagerInternal.java +++ b/location/java/android/location/LocationManagerInternal.java @@ -16,14 +16,10 @@ package android.location; - import android.annotation.NonNull; import android.annotation.Nullable; import android.location.util.identity.CallerIdentity; - -import com.android.internal.annotations.Immutable; - -import java.util.Set; +import android.os.PackageTagsList; /** * Location manager local system service interface. @@ -43,18 +39,14 @@ public abstract class LocationManagerInternal { } /** - * Interface for getting callbacks when a location provider's location tags change. - * - * @see LocationTagInfo + * Interface for getting callbacks when an app id's location provider package tags change. */ - public interface OnProviderLocationTagsChangeListener { + public interface LocationPackageTagsListener { /** - * Called when the location tags for a provider change. - * - * @param providerLocationTagInfo The tag info for a provider. + * Called when the package tags for a location provider change for a uid. */ - void onLocationTagsChanged(@NonNull LocationTagInfo providerLocationTagInfo); + void onLocationPackageTagsChanged(int uid, @NonNull PackageTagsList packageTagsList); } /** @@ -109,58 +101,9 @@ public abstract class LocationManagerInternal { public abstract @Nullable LocationTime getGnssTimeMillis(); /** - * Sets a listener for changes in the location providers' tags. Passing + * Sets a listener for changes in an app id's location provider package tags. Passing * {@code null} clears the current listener. - * - * @param listener The listener. */ - public abstract void setOnProviderLocationTagsChangeListener( - @Nullable OnProviderLocationTagsChangeListener listener); - - /** - * This class represents the location permission tags used by the location provider - * packages in a given UID. These tags are strictly used for accessing state guarded - * by the location permission(s) by a location provider which are required for the - * provider to fulfill its function as being a location provider. - */ - @Immutable - public static class LocationTagInfo { - private final int mUid; - - @NonNull - private final String mPackageName; - - @Nullable - private final Set<String> mLocationTags; - - public LocationTagInfo(int uid, @NonNull String packageName, - @Nullable Set<String> locationTags) { - mUid = uid; - mPackageName = packageName; - mLocationTags = locationTags; - } - - /** - * @return The UID for which tags are related. - */ - public int getUid() { - return mUid; - } - - /** - * @return The package for which tags are related. - */ - @NonNull - public String getPackageName() { - return mPackageName; - } - - /** - * @return The tags for the package used for location related accesses. - */ - @Nullable - public Set<String> getTags() { - return mLocationTags; - } - } + public abstract void setLocationPackageTagsListener( + @Nullable LocationPackageTagsListener listener); } diff --git a/packages/CtsShim/apk/arm/CtsShim.apk b/packages/CtsShim/apk/arm/CtsShim.apk Binary files differindex bb6dfa3a4ede..18915039b6a1 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 2835d57474d9..eb2202586753 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 bb6dfa3a4ede..18915039b6a1 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 2e1a78989b70..b3f923c4176f 100644 --- a/packages/CtsShim/apk/x86/CtsShimPriv.apk +++ b/packages/CtsShim/apk/x86/CtsShimPriv.apk diff --git a/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml b/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml index 120b0859a70a..73163fca5362 100644 --- a/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml +++ b/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml @@ -18,6 +18,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.settingslib.widget"> - <uses-sdk android:minSdkVersion="21" /> + <uses-sdk android:minSdkVersion="28" /> </manifest> diff --git a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml index 3f8439c2db47..efcd41c2778b 100644 --- a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml +++ b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml @@ -20,35 +20,38 @@ android:layout_height="wrap_content" android:layout_width="match_parent" android:background="?android:attr/colorBackground" + android:importantForAccessibility="noHideDescendants" android:gravity="center" android:orientation="horizontal"> - <LinearLayout - android:layout_width="match_parent" + <FrameLayout + android:id="@+id/illustration_frame" + android:layout_width="wrap_content" android:layout_height="@dimen/settingslib_illustration_height" android:layout_gravity="center" android:gravity="center_vertical" android:padding="@dimen/settingslib_illustration_padding" android:orientation="vertical"> + + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/protection_background"/> + <com.airbnb.lottie.LottieAnimationView android:id="@+id/lottie_view" - android:adjustViewBounds="true" - android:maxWidth="@dimen/settingslib_illustration_width" - android:layout_width="wrap_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" /> + + <FrameLayout + android:id="@+id/middleground_layout" + android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@android:color/transparent" android:layout_gravity="center" - android:clipToOutline="true" - android:background="@drawable/protection_background" - android:importantForAccessibility="no"/> - </LinearLayout> + android:visibility="gone"/> + </FrameLayout> - <FrameLayout - android:id="@+id/middleground_layout" - android:layout_width="@dimen/settingslib_illustration_width" - android:layout_height="@dimen/settingslib_illustration_height" - android:padding="@dimen/settingslib_illustration_padding" - android:background="@android:color/transparent" - android:layout_gravity="center" - android:visibility="gone"/> </FrameLayout> diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java index cd2ddebe6367..07102d55ef7e 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java @@ -39,13 +39,6 @@ import java.util.HashMap; public class ColorUtils { private static HashMap<String, Integer> sSysColors; - static { - sSysColors = new HashMap<>(); - sSysColors.put(".colorAccent", android.R.attr.colorAccent); - sSysColors.put(".colorPrimary", android.R.attr.colorPrimary); - sSysColors.put(".colorPrimaryDark", android.R.attr.colorPrimaryDark); - sSysColors.put(".colorSecondary", android.R.attr.colorSecondary); - } private static HashMap<String, Pair<Integer, Integer>> sFixedColors; static { @@ -110,19 +103,6 @@ public class ColorUtils { * Apply the color of tags to the animation. */ public static void applyDynamicColors(Context context, LottieAnimationView animationView) { - for (String key : sSysColors.keySet()) { - final int color = sSysColors.get(key); - animationView.addValueCallback( - new KeyPath("**", key, "**"), - LottieProperty.COLOR_FILTER, - new SimpleLottieValueCallback<ColorFilter>() { - @Override - public ColorFilter getValue(LottieFrameInfo<ColorFilter> frameInfo) { - return new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN); - } - } - ); - } for (String key : sFixedColors.keySet()) { final Pair<Integer, Integer> fixedColorPair = sFixedColors.get(key); final int color = isDarkMode(context) ? fixedColorPair.second : fixedColorPair.first; diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index 1370eef2743b..e91dd94c715d 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -21,6 +21,7 @@ import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.Log; import android.view.View; +import android.view.ViewGroup.LayoutParams; import android.widget.FrameLayout; import android.widget.ImageView; @@ -35,7 +36,9 @@ import com.airbnb.lottie.LottieAnimationView; */ public class IllustrationPreference extends Preference { - static final String TAG = "IllustrationPreference"; + private static final String TAG = "IllustrationPreference"; + + private static final boolean IS_ENABLED_LOTTIE_ADAPTIVE_COLOR = false; private int mAnimationId; private boolean mIsAutoScale; @@ -66,11 +69,22 @@ public class IllustrationPreference extends Preference { Log.w(TAG, "Invalid illustration resource id."); return; } + + // To solve the problem of non-compliant illustrations, we set the frame height + // to 300dp and set the length of the short side of the screen to + // the width of the frame. + final int screenWidth = getContext().getResources().getDisplayMetrics().widthPixels; + final int screenHeight = getContext().getResources().getDisplayMetrics().heightPixels; + final FrameLayout illustrationFrame = (FrameLayout) holder.findViewById( + R.id.illustration_frame); + final LayoutParams lp = (LayoutParams) illustrationFrame.getLayoutParams(); + lp.width = screenWidth < screenHeight ? screenWidth : screenHeight; + illustrationFrame.setLayoutParams(lp); + mMiddleGroundLayout = (FrameLayout) holder.findViewById(R.id.middleground_layout); mIllustrationView = (LottieAnimationView) holder.findViewById(R.id.lottie_view); mIllustrationView.setAnimation(mAnimationId); mIllustrationView.loop(true); - ColorUtils.applyDynamicColors(getContext(), mIllustrationView); mIllustrationView.playAnimation(); if (mIsAutoScale) { enableAnimationAutoScale(mIsAutoScale); @@ -78,6 +92,9 @@ public class IllustrationPreference extends Preference { if (mMiddleGroundView != null) { enableMiddleGroundView(); } + if (IS_ENABLED_LOTTIE_ADAPTIVE_COLOR) { + ColorUtils.applyDynamicColors(getContext(), mIllustrationView); + } } @VisibleForTesting @@ -120,6 +137,13 @@ public class IllustrationPreference extends Preference { mIsAutoScale ? ImageView.ScaleType.CENTER_CROP : ImageView.ScaleType.CENTER_INSIDE); } + /** + * Set the lottie illustration resource id. + */ + public void setLottieAnimationResId(int resId) { + mAnimationId = resId; + } + private void enableMiddleGroundView() { mMiddleGroundLayout.removeAllViews(); mMiddleGroundLayout.addView(mMiddleGroundView); diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml index b299061ce591..906ff2cc09d5 100644 --- a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml +++ b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml @@ -104,7 +104,7 @@ android:gravity="center_vertical"> <View android:layout_width=".75dp" - android:layout_height="match_parent" + android:layout_height="32dp" android:layout_marginTop="16dp" android:layout_marginBottom="16dp" android:background="?android:attr/dividerVertical" /> diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java index a5373944474c..0b3a519cd919 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java @@ -16,8 +16,10 @@ package com.android.settingslib.enterprise; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.Context; +import android.content.DialogInterface; import androidx.annotation.Nullable; @@ -54,4 +56,13 @@ public interface ActionDisabledByAdminController { * Updates the enforced admin */ void updateEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin, @UserIdInt int adminUserId); + + /** + * Returns a listener for handling positive button clicks + */ + @Nullable + default DialogInterface.OnClickListener getPositiveButtonListener(@NonNull Context context, + @NonNull RestrictedLockUtils.EnforcedAdmin enforcedAdmin) { + return null; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java index da42e330b8b4..44cafb17e1d8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java @@ -20,6 +20,11 @@ import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.ParentalControlsUtilsInternal; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.TextUtils; /** * A factory that returns the relevant instance of {@link ActionDisabledByAdminController}. @@ -30,10 +35,28 @@ public final class ActionDisabledByAdminControllerFactory { * Returns the relevant instance of {@link ActionDisabledByAdminController}. */ public static ActionDisabledByAdminController createInstance(Context context, - DeviceAdminStringProvider stringProvider) { - return isFinancedDevice(context) - ? new FinancedDeviceActionDisabledByAdminController(stringProvider) - : new ManagedDeviceActionDisabledByAdminController(stringProvider); + String restriction, DeviceAdminStringProvider stringProvider) { + if (doesBiometricRequireParentalConsent(context, restriction)) { + return new BiometricActionDisabledByAdminController(stringProvider); + } else if (isFinancedDevice(context)) { + return new FinancedDeviceActionDisabledByAdminController(stringProvider); + } else { + return new ManagedDeviceActionDisabledByAdminController(stringProvider); + } + } + + /** + * @return true if the restriction == UserManager.DISALLOW_BIOMETRIC and parental consent + * is required. + */ + private static boolean doesBiometricRequireParentalConsent(Context context, + String restriction) { + if (!TextUtils.equals(UserManager.DISALLOW_BIOMETRIC, restriction)) { + return false; + } + DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + return ParentalControlsUtilsInternal.parentConsentRequired(context, dpm, + BiometricAuthenticator.TYPE_ANY_BIOMETRIC, new UserHandle(UserHandle.myUserId())); } private static boolean isFinancedDevice(Context context) { diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java new file mode 100644 index 000000000000..814d5d23f458 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java @@ -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. + */ + +package com.android.settingslib.enterprise; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.android.settingslib.RestrictedLockUtils; + +public class BiometricActionDisabledByAdminController extends BaseActionDisabledByAdminController { + + private static final String TAG = "BiometricActionDisabledByAdminController"; + + // These MUST not change, as they are the stable API between here and device admin specified + // by the component below. + private static final String ACTION_LEARN_MORE = "android.settings.LEARN_MORE"; + private static final String EXTRA_FROM_BIOMETRIC_SETUP = "from_biometric_setup"; + + BiometricActionDisabledByAdminController( + DeviceAdminStringProvider stringProvider) { + super(stringProvider); + } + + @Override + public void setupLearnMoreButton(Context context) { + + } + + @Override + public String getAdminSupportTitle(@Nullable String restriction) { + return mStringProvider.getDisabledBiometricsParentConsentTitle(); + } + + @Override + public CharSequence getAdminSupportContentString(Context context, + @Nullable CharSequence supportMessage) { + return mStringProvider.getDisabledBiometricsParentConsentContent(); + } + + @Override + public DialogInterface.OnClickListener getPositiveButtonListener(@NonNull Context context, + @NonNull RestrictedLockUtils.EnforcedAdmin enforcedAdmin) { + return (dialog, which) -> { + Log.d(TAG, "Positive button clicked, component: " + enforcedAdmin.component); + final Intent intent = new Intent(ACTION_LEARN_MORE) + .setComponent(enforcedAdmin.component) + .putExtra(EXTRA_FROM_BIOMETRIC_SETUP, true) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + }; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java index c47d789a514d..b83837e6caf6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java +++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/DeviceAdminStringProvider.java @@ -72,4 +72,14 @@ public interface DeviceAdminStringProvider { * a financed device. */ String getDisabledByPolicyTitleForFinancedDevice(); + + /** + * Returns the dialog title for when biometrics require parental consent. + */ + String getDisabledBiometricsParentConsentTitle(); + + /** + * Returns the dialog contents for when biometrics require parental consent. + */ + String getDisabledBiometricsParentConsentContent(); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java new file mode 100644 index 000000000000..766c2f5f9872 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.enterprise; + +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN; +import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID; +import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DEVICE_ADMIN_STRING_PROVIDER; + +import static junit.framework.Assert.assertNotNull; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.UserHandle; + +import com.android.settingslib.RestrictedLockUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class BiometricActionDisabledByAdminControllerTest { + + private final ActionDisabledByAdminControllerTestUtils mTestUtils = + new ActionDisabledByAdminControllerTestUtils(); + private final BiometricActionDisabledByAdminController mController = + new BiometricActionDisabledByAdminController(DEFAULT_DEVICE_ADMIN_STRING_PROVIDER); + + @Before + public void setUp() { + mController.initialize(mTestUtils.createLearnMoreButtonLauncher()); + mController.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID); + } + + @Test + public void buttonClicked() { + Context context = mock(Context.class); + ComponentName componentName = mock(ComponentName.class); + RestrictedLockUtils.EnforcedAdmin enforcedAdmin = new RestrictedLockUtils.EnforcedAdmin( + componentName, new UserHandle(UserHandle.myUserId())); + + DialogInterface.OnClickListener listener = + mController.getPositiveButtonListener(context, enforcedAdmin); + assertNotNull("Biometric Controller must supply a non-null listener", listener); + listener.onClick(mock(DialogInterface.class), 0 /* which */); + + ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(context).startActivity(intentCaptor.capture()); + assertEquals("android.settings.LEARN_MORE", + intentCaptor.getValue().getAction()); + assertTrue("from_biometric_setup", intentCaptor.getValue() + .getBooleanExtra("from_biometric_setup", false)); + assertEquals(componentName, intentCaptor.getValue().getComponent()); + } + +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java index be3e9fcf45ea..99e13c325472 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java @@ -30,6 +30,8 @@ class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider { static final String DEFAULT_DISABLED_BY_POLICY_CONTENT = "default_disabled_by_policy_content"; static final String DEFAULT_DISABLED_BY_POLICY_TITLE_FINANCED_DEVICE = "default_disabled_by_policy_title_financed_device"; + static final String DEFAULT_BIOMETRIC_TITLE = "biometric_title"; + static final String DEFAULT_BIOMETRIC_CONTENTS = "biometric_contents"; static final DeviceAdminStringProvider DEFAULT_DEVICE_ADMIN_STRING_PROVIDER = new FakeDeviceAdminStringProvider(/* url = */ null); @@ -88,4 +90,15 @@ class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider { public String getDisabledByPolicyTitleForFinancedDevice() { return DEFAULT_DISABLED_BY_POLICY_TITLE_FINANCED_DEVICE; } + + @Override + public String getDisabledBiometricsParentConsentTitle() { + return DEFAULT_BIOMETRIC_TITLE; + } + + @Override + public String getDisabledBiometricsParentConsentContent() { + return DEFAULT_BIOMETRIC_CONTENTS; + } + } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 8bc3d228f7a8..2579e7084e08 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -238,7 +238,9 @@ class ActivityLaunchAnimator( * during the animation. */ @JvmStatic - fun fromView(view: View): Controller = GhostedViewLaunchAnimatorController(view) + fun fromView(view: View, cujType: Int? = null): Controller { + return GhostedViewLaunchAnimatorController(view, cujType) + } } /** diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 4b655a1a1b02..ffb7ab4eff7c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -14,6 +14,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroupOverlay import android.widget.FrameLayout +import com.android.internal.jank.InteractionJankMonitor import kotlin.math.min /** @@ -29,7 +30,10 @@ import kotlin.math.min */ open class GhostedViewLaunchAnimatorController( /** The view that will be ghosted and from which the background will be extracted. */ - private val ghostedView: View + private val ghostedView: View, + + /** The [InteractionJankMonitor.CujType] associated to this animation. */ + private val cujType: Int? = null ) : ActivityLaunchAnimator.Controller { /** The container to which we will add the ghost view and expanding background. */ override var launchContainer = ghostedView.rootView as ViewGroup @@ -125,6 +129,8 @@ open class GhostedViewLaunchAnimatorController( val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX matrix.getValues(initialGhostViewMatrixValues) + + cujType?.let { InteractionJankMonitor.getInstance().begin(ghostedView, it) } } override fun onLaunchAnimationProgress( @@ -167,6 +173,8 @@ open class GhostedViewLaunchAnimatorController( } override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + cujType?.let { InteractionJankMonitor.getInstance().end(it) } + backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha GhostView.removeGhost(ghostedView) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 298b7c38066b..7c81325d685f 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -92,5 +92,12 @@ public interface ActivityStarter { * *after* returning to start hiding the keyguard. */ boolean onDismiss(); + + /** + * Whether running this action when we are locked will start an animation on the keyguard. + */ + default boolean willRunAnimationOnKeyguard() { + return false; + } } } diff --git a/packages/SystemUI/res/layout/global_actions_change_panel.xml b/packages/SystemUI/res/layout/global_actions_change_panel.xml index dffb0f011bb5..bc9c203b299a 100644 --- a/packages/SystemUI/res/layout/global_actions_change_panel.xml +++ b/packages/SystemUI/res/layout/global_actions_change_panel.xml @@ -14,8 +14,18 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<ImageView +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/global_actions_change_button" android:layout_width="wrap_content" - android:layout_height="wrap_content"/> + android:layout_height="wrap_content"> + <TextView + android:id="@+id/global_actions_change_message" + android:layout_width="wrap_content" + android:visibility="gone" + android:layout_height="wrap_content" + android:text="@string/global_actions_change_description" /> + <ImageView + android:id="@+id/global_actions_change_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml index 4765b44d78c8..3f4baaf27b84 100644 --- a/packages/SystemUI/res/layout/long_screenshot.xml +++ b/packages/SystemUI/res/layout/long_screenshot.xml @@ -99,6 +99,7 @@ app:handleThickness="@dimen/screenshot_crop_handle_thickness" app:handleColor="?androidprv:attr/colorAccentPrimary" app:scrimColor="@color/screenshot_crop_scrim" + app:containerBackgroundColor="?android:colorBackgroundFloating" tools:background="?android:colorBackground" tools:minHeight="100dp" tools:minWidth="100dp" /> diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index 1d93f5d69da8..536b0423ce16 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -33,6 +33,7 @@ android:gravity="start" android:textDirection="locale" android:ellipsize="marquee" + android:marqueeRepeatLimit="1" android:singleLine="true" android:textAppearance="@style/TextAppearance.QS.TileLabel"/> @@ -43,6 +44,7 @@ android:gravity="start" android:textDirection="locale" android:ellipsize="marquee" + android:marqueeRepeatLimit="1" android:singleLine="true" android:visibility="gone" android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary" diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml index c88703dabeea..5b9ca1b26158 100644 --- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml +++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml @@ -59,11 +59,16 @@ android:visibility="gone" /> - <LinearLayout + <FrameLayout android:id="@+id/rightLayout" android:layout_width="wrap_content" android:layout_height="match_parent" - android:gravity="center_vertical|end" + android:gravity="end" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="center_vertical|end" > <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" @@ -80,4 +85,6 @@ android:paddingEnd="2dp" /> </LinearLayout> + </FrameLayout> + </LinearLayout> diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml index 70bd85036198..2f0957caaaae 100644 --- a/packages/SystemUI/res/values-television/config.xml +++ b/packages/SystemUI/res/values-television/config.xml @@ -33,6 +33,7 @@ <item>com.android.systemui.statusbar.tv.notifications.TvNotificationPanel</item> <item>com.android.systemui.statusbar.tv.notifications.TvNotificationHandler</item> <item>com.android.systemui.statusbar.tv.VpnStatusObserver</item> + <item>com.android.systemui.globalactions.GlobalActionsComponent</item> <item>com.android.systemui.usb.StorageNotification</item> <item>com.android.systemui.power.PowerUI</item> <item>com.android.systemui.media.RingtonePlayer</item> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 067d56f3d157..d2ed6017b205 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -143,6 +143,7 @@ <attr name="handleThickness" format="dimension" /> <attr name="handleColor" format="color" /> <attr name="scrimColor" format="color" /> + <attr name="containerBackgroundColor" format="color" /> <attr name="isVertical" format="boolean" /> @@ -178,6 +179,7 @@ <attr name="handleThickness" /> <attr name="handleColor" /> <attr name="scrimColor" /> + <attr name="containerBackgroundColor" /> </declare-styleable> <declare-styleable name="MagnifierView"> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index b22a0d55ece7..d132609b218c 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -396,10 +396,6 @@ <!-- Whether or not the notifications should always fade as they are dismissed. --> <bool name="config_fadeNotificationsOnDismiss">false</bool> - <!-- Whether or not the parent of the notification row itself is being translated when swiped or - its children views. If true, then the contents are translated and vice versa. --> - <bool name="config_translateNotificationContentsOnSwipe">true</bool> - <!-- Whether or not the fade on the notification is based on the amount that it has been swiped off-screen. --> <bool name="config_fadeDependingOnAmountSwiped">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0ccde60f9bfd..9b860c75f476 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -347,7 +347,7 @@ <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) --> <dimen name="screenshot_action_chip_margin_vertical">4dp</dimen> <dimen name="screenshot_action_chip_padding_vertical">11dp</dimen> - <dimen name="screenshot_action_chip_icon_size">18dp</dimen> + <dimen name="screenshot_action_chip_icon_size">18sp</dimen> <!-- Padding on each side of the icon for icon-only chips --> <dimen name="screenshot_action_chip_icon_only_padding_horizontal">14dp</dimen> <!-- Padding at the edges of the chip for icon-and-text chips --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 7f4e4751312e..93ce8f3c2a96 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2705,6 +2705,8 @@ <!-- Accessibility floating menu strings --> <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user the accessibility gesture had been replaced by accessibility floating button. [CHAR LIMIT=100] --> <string name="accessibility_floating_button_migration_tooltip">Accessibility button replaced the accessibility gesture\n\n<annotation id="link">View settings</annotation></string> + <!-- Message for the accessibility floating button settings tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user to have another option to switch from the accessibility gesture to a button. [CHAR LIMIT=100] --> + <string name="accessibility_floating_button_switch_migration_tooltip">You can switch from the accessibility gesture to a button\n\n<annotation id="link">Settings</annotation></string> <!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] --> <string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string> <!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] --> @@ -2977,6 +2979,8 @@ <!-- Content description for a chip in the status bar showing that the user is currently on a phone call. [CHAR LIMIT=NONE] --> <string name="ongoing_phone_call_content_description">Ongoing phone call</string> + <!-- Placeholder for string describing changes in global actions --> + <string name="global_actions_change_description" translatable="false"><xliff:g>%1$s</xliff:g></string> <!-- URL for more information about changes in global actions --> <string name="global_actions_change_url" translatable="false"></string> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index a22aa89fcfeb..7837f61dc289 100755 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -250,6 +250,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void onStateChanged(int newState) { mStatusBarState = newState; } + + @Override + public void onExpandedChanged(boolean isExpanded) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onShadeExpandedChanged(isExpanded); + } + } + } }; HashMap<Integer, SimData> mSimDatas = new HashMap<>(); @@ -776,6 +786,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { mFingerprintLockedOut = true; + if (isUdfpsEnrolled()) { + updateFingerprintListeningState(); + } } for (int i = 0; i < mCallbacks.size(); i++) { @@ -2117,7 +2130,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || (!getUserCanSkipBouncer(getCurrentUser()) && !isEncryptedOrLockdown(getCurrentUser()) && !userNeedsStrongAuth() - && userDoesNotHaveTrust); + && userDoesNotHaveTrust + && !mFingerprintLockedOut); return shouldListenKeyguardState && shouldListenUserState && shouldListenBouncerState && shouldListenUdfpsState; } @@ -3315,6 +3329,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); pw.println(" udfpsEnrolled=" + isUdfpsEnrolled()); + pw.println(" mFingerprintLockedOut=" + mFingerprintLockedOut); pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId)); if (isUdfpsEnrolled()) { pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true)); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 7af64f73bd50..9d7af4f29de2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -341,4 +341,8 @@ public class KeyguardUpdateMonitorCallback { */ public void onRequireUnlockForNfc() { } + /** + * Called when the notification shade is expanded or collapsed. + */ + public void onShadeExpandedChanged(boolean expanded) { } } diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 64a683e78953..a68f79604b25 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -130,10 +130,7 @@ public class ImageWallpaper extends WallpaperService { .getBounds(); mHeight = window.height(); mWidth = window.width(); - mMiniBitmap = null; - if (mWorker != null && mWorker.getThreadHandler() != null) { - mWorker.getThreadHandler().post(this::updateMiniBitmap); - } + mRenderer.setOnBitmapChanged(this::updateMiniBitmap); } EglHelper getEglHelperInstance() { @@ -177,20 +174,19 @@ public class ImageWallpaper extends WallpaperService { mPageOffset = (1 - imgWidth) / (float) (mPages - 1); } - private void updateMiniBitmap() { - mRenderer.useBitmap(b -> { - int size = Math.min(b.getWidth(), b.getHeight()); - float scale = 1.0f; - if (size > MIN_SURFACE_WIDTH) { - scale = (float) MIN_SURFACE_WIDTH / (float) size; - } - mImgHeight = b.getHeight(); - mImgWidth = b.getWidth(); - mMiniBitmap = Bitmap.createScaledBitmap(b, (int) Math.max(scale * b.getWidth(), 1), - (int) Math.max(scale * b.getHeight(), 1), false); - computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap); - mLocalColorsToAdd.clear(); - }); + private void updateMiniBitmap(Bitmap b) { + if (b == null) return; + int size = Math.min(b.getWidth(), b.getHeight()); + float scale = 1.0f; + if (size > MIN_SURFACE_WIDTH) { + scale = (float) MIN_SURFACE_WIDTH / (float) size; + } + mImgHeight = b.getHeight(); + mImgWidth = b.getWidth(); + mMiniBitmap = Bitmap.createScaledBitmap(b, (int) Math.max(scale * b.getWidth(), 1), + (int) Math.max(scale * b.getHeight(), 1), false); + computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap); + mLocalColorsToAdd.clear(); } private void updateSurfaceSize() { diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index b0f4da251208..affad7a57d86 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -391,9 +391,9 @@ public class SwipeHelper implements Gefingerpoken { boolean animateLeft = (Math.abs(velocity) > getEscapeVelocity() && velocity < 0) || (getTranslation(animView) < 0 && !isDismissAll); if (animateLeft || animateLeftForRtl || animateUpForMenu) { - newPos = -getSize(animView); + newPos = -getTotalTranslationLength(animView); } else { - newPos = getSize(animView); + newPos = getTotalTranslationLength(animView); } long duration; if (fixedDuration == 0) { @@ -470,6 +470,15 @@ public class SwipeHelper implements Gefingerpoken { } /** + * Get the total translation length where we want to swipe to when dismissing the view. By + * default this is the size of the view, but can also be larger. + * @param animView the view to ask about + */ + protected float getTotalTranslationLength(View animView) { + return getSize(animView); + } + + /** * Called to update the dismiss animation. */ protected void prepareDismissAnimation(View view, Animator anim) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index bfb7105f5860..17178fa8e606 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -75,7 +75,6 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; private int mMagnificationMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE; private final LayoutParams mParams; - private int mWindowHeight; @VisibleForTesting final Rect mDraggableWindowBounds = new Rect(); private boolean mIsVisible = false; @@ -95,7 +94,6 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL mWindowManager = mContext.getSystemService(WindowManager.class); mSfVsyncFrameProvider = sfVsyncFrameProvider; mParams = createLayoutParams(context); - mWindowHeight = mWindowManager.getCurrentWindowMetrics().getBounds().height(); mImageView = imageView; mImageView.setOnTouchListener(this::onTouch); mImageView.setAccessibilityDelegate(new View.AccessibilityDelegate() { @@ -313,12 +311,14 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL void onConfigurationChanged(int configDiff) { if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) { + final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds); mDraggableWindowBounds.set(getDraggableWindowBounds()); - // Keep the Y position with the same height ratio before the window height is changed. - final int windowHeight = mWindowManager.getCurrentWindowMetrics().getBounds().height(); - final float windowHeightFraction = (float) mParams.y / mWindowHeight; - mParams.y = (int) (windowHeight * windowHeightFraction); - mWindowHeight = windowHeight; + // Keep the Y position with the same height ratio before the window bounds and + // draggable bounds are changed. + final float windowHeightFraction = (float) (mParams.y - previousDraggableBounds.top) + / previousDraggableBounds.height(); + mParams.y = (int) (windowHeightFraction * mDraggableWindowBounds.height()) + + mDraggableWindowBounds.top; stickToScreenEdge(mToLeftScreenEdge); return; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java index 47f373920b90..05256e646948 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java @@ -53,7 +53,7 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { @FloatRange(from = 0.0, to = 1.0) private static final float DEFAULT_POSITION_X_PERCENT = 1.0f; @FloatRange(from = 0.0, to = 1.0) - private static final float DEFAULT_POSITION_Y_PERCENT = 0.8f; + private static final float DEFAULT_POSITION_Y_PERCENT = 0.9f; private final Context mContext; private final AccessibilityFloatingMenuView mMenuView; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java index 63cfd5123c96..ee09c620ec1d 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java @@ -143,7 +143,7 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { } void updateItemPadding(int padding, int size) { - itemView.setPaddingRelative(padding, padding, padding, padding); + itemView.setPaddingRelative(padding, padding, padding, 0); } } @@ -154,7 +154,7 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { @Override void updateItemPadding(int padding, int size) { - final int paddingBottom = size <= 2 ? padding : 0; + final int paddingBottom = size <= 1 ? padding : 0; itemView.setPaddingRelative(padding, padding, padding, paddingBottom); } } @@ -166,7 +166,7 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { @Override void updateItemPadding(int padding, int size) { - itemView.setPaddingRelative(padding, 0, padding, padding); + itemView.setPaddingRelative(padding, padding, padding, padding); } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index cf577a37d625..77cca2e3089c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -24,9 +24,13 @@ import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils +import com.android.systemui.statusbar.CircleReveal +import com.android.systemui.statusbar.LiftReveal +import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope @@ -49,10 +53,12 @@ class AuthRippleController @Inject constructor( private val commandRegistry: CommandRegistry, private val notificationShadeWindowController: NotificationShadeWindowController, private val bypassController: KeyguardBypassController, + private val biometricUnlockController: BiometricUnlockController, rippleView: AuthRippleView? ) : ViewController<AuthRippleView>(rippleView) { var fingerprintSensorLocation: PointF? = null private var faceSensorLocation: PointF? = null + private var circleReveal: LightRevealEffect? = null @VisibleForTesting public override fun onViewAttached() { @@ -96,15 +102,47 @@ class AuthRippleController @Inject constructor( private fun showRipple() { notificationShadeWindowController.setForcePluginOpen(true, this) - mView.startRipple(Runnable { - notificationShadeWindowController.setForcePluginOpen(false, this) - }) + val biometricUnlockMode = biometricUnlockController.mode + val useCircleReveal = circleReveal != null && + (biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK || + biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING || + biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM) + val lightRevealScrim = statusBar.lightRevealScrim + if (useCircleReveal) { + lightRevealScrim?.revealEffect = circleReveal!! + } + + mView.startRipple( + /* end runnable */ + Runnable { + notificationShadeWindowController.setForcePluginOpen(false, this) + if (useCircleReveal) { + lightRevealScrim?.revealEffect = LiftReveal + } + }, + /* circleReveal */ + if (useCircleReveal) { + lightRevealScrim + } else { + null + } + ) } fun updateSensorLocation() { fingerprintSensorLocation = authController.fingerprintSensorLocation faceSensorLocation = authController.faceAuthSensorLocation - statusBar.updateCircleReveal() + fingerprintSensorLocation?.let { + circleReveal = CircleReveal( + it.x, + it.y, + 0f, + Math.max( + Math.max(it.x, statusBar.displayWidth - it.x), + Math.max(it.y, statusBar.displayHeight - it.y) + ) + ) + } } private fun updateRippleColor() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 75373abc5124..dd73c4f8d071 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -31,6 +31,7 @@ import android.util.MathUtils import android.view.View import android.view.animation.PathInterpolator import com.android.internal.graphics.ColorUtils +import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.charging.RippleShader private const val RIPPLE_ANIMATION_DURATION: Long = 1533 @@ -70,51 +71,79 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at .toFloat() } - fun startRipple(onAnimationEnd: Runnable?) { + fun startRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) { if (rippleInProgress) { return // Ignore if ripple effect is already playing } - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.interpolator = PathInterpolator(0.4f, 0f, 0f, 1f) - animator.duration = RIPPLE_ANIMATION_DURATION - animator.addUpdateListener { animator -> - val now = animator.currentPlayTime - rippleShader.progress = animator.animatedValue as Float - rippleShader.time = now.toFloat() - rippleShader.distortionStrength = 1 - rippleShader.progress - invalidate() + val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + interpolator = PathInterpolator(0.4f, 0f, 0f, 1f) + duration = RIPPLE_ANIMATION_DURATION + addUpdateListener { animator -> + val now = animator.currentPlayTime + rippleShader.progress = animator.animatedValue as Float + rippleShader.time = now.toFloat() + + lightReveal?.revealAmount = animator.animatedValue as Float + invalidate() + } } - val alphaInAnimator = ValueAnimator.ofInt(0, 127) - alphaInAnimator.duration = 167 - alphaInAnimator.addUpdateListener { alphaInAnimator -> - rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color, - alphaInAnimator.animatedValue as Int) - invalidate() + + val revealAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + interpolator = rippleAnimator.interpolator + startDelay = 10 + duration = rippleAnimator.duration + addUpdateListener { animator -> + lightReveal?.revealAmount = animator.animatedValue as Float + } } - val alphaOutAnimator = ValueAnimator.ofInt(127, 0) - alphaOutAnimator.startDelay = 417 - alphaOutAnimator.duration = 1116 - alphaOutAnimator.addUpdateListener { alphaOutAnimator -> - rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color, - alphaOutAnimator.animatedValue as Int) - invalidate() + + val alphaInAnimator = ValueAnimator.ofInt(0, 127).apply { + duration = 167 + addUpdateListener { animator -> + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + animator.animatedValue as Int + ) + invalidate() + } } - val animatorSet = AnimatorSet() - animatorSet.playTogether(animator, alphaInAnimator, alphaOutAnimator) - animatorSet.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - onAnimationEnd?.run() - rippleInProgress = false - visibility = GONE + val alphaOutAnimator = ValueAnimator.ofInt(127, 0).apply { + startDelay = 417 + duration = 1116 + addUpdateListener { animator -> + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + animator.animatedValue as Int + ) + invalidate() } - }) + } + + val animatorSet = AnimatorSet().apply { + playTogether( + rippleAnimator, + revealAnimator, + alphaInAnimator, + alphaOutAnimator + ) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + rippleInProgress = true + visibility = VISIBLE + } + + override fun onAnimationEnd(animation: Animator?) { + onAnimationEnd?.run() + rippleInProgress = false + visibility = GONE + } + }) + } // TODO (b/185124905): custom haptic TBD // vibrate() animatorSet.start() - visibility = VISIBLE - rippleInProgress = true } fun setColor(color: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index f975a804ca80..ec930b0c41d1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -378,6 +378,7 @@ public class UdfpsController implements DozeReceiver { return true; case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_HOVER_ENTER: + Trace.beginSection("UdfpsController.onTouch.ACTION_DOWN"); // To simplify the lifecycle of the velocity tracker, make sure it's never null // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. if (mVelocityTracker == null) { @@ -388,8 +389,7 @@ public class UdfpsController implements DozeReceiver { mVelocityTracker.clear(); } if (isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView)) { - Trace.beginAsyncSection( - "UdfpsController#ACTION_DOWN", 1); + Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0); // The pointer that causes ACTION_DOWN is always at index 0. // We need to persist its ID to track it during ACTION_MOVE that could include // data for many other pointers because of multi-touch support. @@ -397,10 +397,12 @@ public class UdfpsController implements DozeReceiver { mVelocityTracker.addMovement(event); handled = true; } + Trace.endSection(); break; case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_HOVER_MOVE: + Trace.beginSection("UdfpsController.onTouch.ACTION_MOVE"); final int idx = mActivePointerId == -1 ? event.getPointerId(0) : event.findPointerIndex(mActivePointerId); @@ -466,11 +468,13 @@ public class UdfpsController implements DozeReceiver { onFingerUp(); } } + Trace.endSection(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_HOVER_EXIT: + Trace.beginSection("UdfpsController.onTouch.ACTION_UP"); mActivePointerId = -1; if (mVelocityTracker != null) { mVelocityTracker.recycle(); @@ -479,7 +483,7 @@ public class UdfpsController implements DozeReceiver { Log.v(TAG, "onTouch | finger up"); onFingerUp(); mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); - + Trace.endSection(); break; default: @@ -818,12 +822,11 @@ public class UdfpsController implements DozeReceiver { return; } mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major); - Trace.endAsyncSection( - "UdfpsController#ACTION_DOWN", 1); - Trace.beginAsyncSection("UdfpsController#startIllumination", 1); + Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); + Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0); mView.startIllumination(() -> { mFingerprintManager.onUiReady(mSensorProps.sensorId); - Trace.endAsyncSection("UdfpsController#startIllumination", 1); + Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0); }); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index ff95604088ed..7b34e52c16e8 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -38,6 +38,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; +import com.android.systemui.doze.DozeMachine.State; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.Assert; @@ -96,6 +97,7 @@ public class DozeTriggers implements DozeMachine.Part { private long mNotificationPulseTime; private boolean mPulsePending; + private Runnable mAodInterruptRunnable; /** see {@link #onProximityFar} prox for callback */ private boolean mWantProxSensor; @@ -303,11 +305,16 @@ public class DozeTriggers implements DozeMachine.Part { } else if (isPickup) { gentleWakeUp(pulseReason); } else if (isUdfpsLongPress) { + final State state = mMachine.getState(); + if (state == State.DOZE_AOD || state == State.DOZE) { + // Since the gesture won't be received by the UDFPS view, we need to + // manually inject an event once the display is ON + mAodInterruptRunnable = () -> + mAuthController.onAodInterrupt((int) screenX, (int) screenY, + rawValues[3] /* major */, rawValues[4] /* minor */); + } + requestPulse(DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS, true, null); - // Since the gesture won't be received by the UDFPS view, manually inject an - // event. - mAuthController.onAodInterrupt((int) screenX, (int) screenY, - rawValues[3] /* major */, rawValues[4] /* minor */); } else { mDozeHost.extendPulse(pulseReason); } @@ -439,6 +446,7 @@ public class DozeTriggers implements DozeMachine.Part { public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { switch (newState) { case INITIALIZED: + mAodInterruptRunnable = null; sWakeDisplaySensorState = true; mBroadcastReceiver.register(mBroadcastDispatcher); mDozeHost.addCallback(mHostCallback); @@ -448,6 +456,7 @@ public class DozeTriggers implements DozeMachine.Part { break; case DOZE: case DOZE_AOD: + mAodInterruptRunnable = null; mWantProxSensor = newState != DozeMachine.State.DOZE; mWantSensors = true; mWantTouchScreenSensors = true; @@ -494,6 +503,11 @@ public class DozeTriggers implements DozeMachine.Part { || state == Display.STATE_DOZE_SUSPEND || state == Display.STATE_OFF; mDozeSensors.setProxListening(mWantProxSensor && lowPowerStateOrOff); mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors, lowPowerStateOrOff); + + if (mAodInterruptRunnable != null && state == Display.STATE_ON) { + mAodInterruptRunnable.run(); + mAodInterruptRunnable = null; + } } /** @@ -576,6 +590,8 @@ public class DozeTriggers implements DozeMachine.Part { @Override public void dump(PrintWriter pw) { + pw.println(" mAodInterruptRunnable=" + mAodInterruptRunnable); + pw.print(" notificationPulseTime="); pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime)); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index ff3cb2102d60..fbe06b02d955 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -21,6 +21,7 @@ import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; import android.app.AlarmManager; import android.content.Context; +import android.content.res.Configuration; import android.os.Handler; import android.os.SystemClock; import android.provider.Settings; @@ -34,6 +35,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.wakelock.WakeLock; @@ -48,7 +50,8 @@ import dagger.Lazy; * The policy controlling doze. */ @DozeScope -public class DozeUi implements DozeMachine.Part, TunerService.Tunable { +public class DozeUi implements DozeMachine.Part, TunerService.Tunable, + ConfigurationController.ConfigurationListener { // if enabled, calls dozeTimeTick() whenever the time changes: private static final boolean BURN_IN_TESTING_ENABLED = false; private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min @@ -63,6 +66,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { private final DozeLog mDozeLog; private final Lazy<StatusBarStateController> mStatusBarStateController; private final TunerService mTunerService; + private final ConfigurationController mConfigurationController; private boolean mKeyguardShowing; private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback = @@ -84,6 +88,11 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { mHandler.post(mWakeLock.wrap(() -> {})); } } + + @Override + public void onShadeExpandedChanged(boolean expanded) { + updateAnimateScreenOff(); + } }; private long mLastTimeTickElapsed = 0; @@ -93,7 +102,8 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { WakeLock wakeLock, DozeHost host, @Main Handler handler, DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor, DozeLog dozeLog, TunerService tunerService, - Lazy<StatusBarStateController> statusBarStateController) { + Lazy<StatusBarStateController> statusBarStateController, + ConfigurationController configurationController) { mContext = context; mWakeLock = wakeLock; mHost = host; @@ -107,11 +117,15 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { mStatusBarStateController = statusBarStateController; mTunerService.addTunable(this, Settings.Secure.DOZE_ALWAYS_ON); + + mConfigurationController = configurationController; + mConfigurationController.addCallback(this); } @Override public void destroy() { mTunerService.removeTunable(this); + mConfigurationController.removeCallback(this); } @Override @@ -274,4 +288,9 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable { updateAnimateScreenOff(); } } + + @Override + public void onConfigChanged(Configuration newConfig) { + updateAnimateScreenOff(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index dfd85fe4dc90..bb44b09f1bce 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -30,6 +30,7 @@ import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.content.Context; import android.content.DialogInterface; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.drawable.Drawable; @@ -72,6 +73,7 @@ import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -112,47 +114,101 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite @VisibleForTesting boolean mShowLockScreenCards = false; + private final KeyguardStateController.Callback mKeyguardStateControllerListener = + new KeyguardStateController.Callback() { + @Override + public void onUnlockedChanged() { + if (mDialog != null) { + ActionsDialog dialog = (ActionsDialog) mDialog; + boolean unlocked = mKeyguardStateController.isUnlocked(); + if (dialog.mWalletViewController != null) { + dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked); + } + + if (unlocked) { + dialog.hideLockMessage(); + } + } + } + }; + + private final ContentObserver mSettingsObserver = new ContentObserver(mMainHandler) { + @Override + public void onChange(boolean selfChange) { + onPowerMenuLockScreenSettingsChanged(); + } + }; + /** * @param context everything needs a context :( */ @Inject - public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs, - AudioManager audioManager, IDreamManager iDreamManager, - DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, + public GlobalActionsDialog( + Context context, + GlobalActionsManager windowManagerFuncs, + AudioManager audioManager, + IDreamManager iDreamManager, + DevicePolicyManager devicePolicyManager, + LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, TelephonyListenerManager telephonyListenerManager, - GlobalSettings globalSettings, SecureSettings secureSettings, - @Nullable Vibrator vibrator, @Main Resources resources, - ConfigurationController configurationController, ActivityStarter activityStarter, - KeyguardStateController keyguardStateController, UserManager userManager, - TrustManager trustManager, IActivityManager iActivityManager, - @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger, - NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor, + GlobalSettings globalSettings, + SecureSettings secureSettings, + @Nullable Vibrator vibrator, + @Main Resources resources, + ConfigurationController configurationController, + ActivityStarter activityStarter, + KeyguardStateController keyguardStateController, + UserManager userManager, + TrustManager trustManager, + IActivityManager iActivityManager, + @Nullable TelecomManager telecomManager, + MetricsLogger metricsLogger, + NotificationShadeDepthController depthController, + SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, IWindowManager iWindowManager, @Background Executor backgroundExecutor, UiEventLogger uiEventLogger, - RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) { - - super(context, windowManagerFuncs, - audioManager, iDreamManager, - devicePolicyManager, lockPatternUtils, - broadcastDispatcher, telephonyListenerManager, - globalSettings, secureSettings, - vibrator, resources, + RingerModeTracker ringerModeTracker, + SysUiState sysUiState, + @Main Handler handler, + PackageManager packageManager, + StatusBar statusBar) { + + super(context, + windowManagerFuncs, + audioManager, + iDreamManager, + devicePolicyManager, + lockPatternUtils, + broadcastDispatcher, + telephonyListenerManager, + globalSettings, + secureSettings, + vibrator, + resources, configurationController, - keyguardStateController, userManager, - trustManager, iActivityManager, - telecomManager, metricsLogger, - depthController, colorExtractor, + keyguardStateController, + userManager, + trustManager, + iActivityManager, + telecomManager, + metricsLogger, + depthController, + colorExtractor, statusBarService, notificationShadeWindowController, iWindowManager, backgroundExecutor, uiEventLogger, null, - ringerModeTracker, sysUiState, handler); + ringerModeTracker, + sysUiState, + handler, + packageManager, + statusBar); mLockPatternUtils = lockPatternUtils; mKeyguardStateController = keyguardStateController; @@ -162,34 +218,22 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite mNotificationShadeWindowController = notificationShadeWindowController; mSysUiState = sysUiState; mActivityStarter = activityStarter; - keyguardStateController.addCallback(new KeyguardStateController.Callback() { - @Override - public void onUnlockedChanged() { - if (mDialog != null) { - ActionsDialog dialog = (ActionsDialog) mDialog; - boolean unlocked = mKeyguardStateController.isUnlocked(); - if (dialog.mWalletViewController != null) { - dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked); - } - if (unlocked) { - dialog.hideLockMessage(); - } - } - } - }); + mKeyguardStateController.addCallback(mKeyguardStateControllerListener); // Listen for changes to show pay on the power menu while locked onPowerMenuLockScreenSettingsChanged(); mGlobalSettings.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), false /* notifyForDescendants */, - new ContentObserver(handler) { - @Override - public void onChange(boolean selfChange) { - onPowerMenuLockScreenSettingsChanged(); - } - }); + mSettingsObserver); + } + + @Override + public void destroy() { + super.destroy(); + mKeyguardStateController.removeCallback(mKeyguardStateControllerListener); + mGlobalSettings.unregisterContentObserver(mSettingsObserver); } /** @@ -227,7 +271,8 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter, this::getWalletViewController, mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, - mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger()); + mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(), + getStatusBar()); if (shouldShowLockMessage(dialog)) { dialog.showLockMessage(); @@ -295,11 +340,13 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, - MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger) { + MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, + StatusBar statusBar) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions, adapter, overflowAdapter, depthController, sysuiColorExtractor, statusBarService, notificationShadeWindowController, sysuiState, - onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger, null); + onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger, null, + statusBar); mWalletFactory = walletFactory; // Update window attributes diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 8e152830e208..42cd4f7da3a8 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -74,8 +74,10 @@ import android.telephony.TelephonyManager; import android.util.ArraySet; import android.util.Log; import android.view.ContextThemeWrapper; +import android.view.GestureDetector; import android.view.IWindowManager; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -119,6 +121,7 @@ import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.scrim.ScrimDrawable; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -174,6 +177,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final IDreamManager mDreamManager; private final DevicePolicyManager mDevicePolicyManager; private final LockPatternUtils mLockPatternUtils; + private final TelephonyListenerManager mTelephonyListenerManager; private final KeyguardStateController mKeyguardStateController; private final BroadcastDispatcher mBroadcastDispatcher; protected final GlobalSettings mGlobalSettings; @@ -228,6 +232,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms protected Handler mMainHandler; private int mSmallestScreenWidthDp; + private final StatusBar mStatusBar; @VisibleForTesting public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { @@ -304,31 +309,46 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene * @param context everything needs a context :( */ @Inject - public GlobalActionsDialogLite(Context context, GlobalActionsManager windowManagerFuncs, - AudioManager audioManager, IDreamManager iDreamManager, - DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, + public GlobalActionsDialogLite( + Context context, + GlobalActionsManager windowManagerFuncs, + AudioManager audioManager, + IDreamManager iDreamManager, + DevicePolicyManager devicePolicyManager, + LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, TelephonyListenerManager telephonyListenerManager, - GlobalSettings globalSettings, SecureSettings secureSettings, - @Nullable Vibrator vibrator, @Main Resources resources, + GlobalSettings globalSettings, + SecureSettings secureSettings, + @Nullable Vibrator vibrator, + @Main Resources resources, ConfigurationController configurationController, - KeyguardStateController keyguardStateController, UserManager userManager, - TrustManager trustManager, IActivityManager iActivityManager, - @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger, - NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor, + KeyguardStateController keyguardStateController, + UserManager userManager, + TrustManager trustManager, + IActivityManager iActivityManager, + @Nullable TelecomManager telecomManager, + MetricsLogger metricsLogger, + NotificationShadeDepthController depthController, + SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, IWindowManager iWindowManager, @Background Executor backgroundExecutor, UiEventLogger uiEventLogger, GlobalActionsInfoProvider infoProvider, - RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) { + RingerModeTracker ringerModeTracker, + SysUiState sysUiState, + @Main Handler handler, + PackageManager packageManager, + StatusBar statusBar) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; mDreamManager = iDreamManager; mDevicePolicyManager = devicePolicyManager; mLockPatternUtils = lockPatternUtils; + mTelephonyListenerManager = telephonyListenerManager; mKeyguardStateController = keyguardStateController; mBroadcastDispatcher = broadcastDispatcher; mGlobalSettings = globalSettings; @@ -351,7 +371,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mRingerModeTracker = ringerModeTracker; mSysUiState = sysUiState; mMainHandler = handler; - mSmallestScreenWidthDp = mContext.getResources().getConfiguration().smallestScreenWidthDp; + mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp; + mStatusBar = statusBar; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -360,11 +381,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter); - mHasTelephony = - context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + mHasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); // get notified of phone state changes - telephonyListenerManager.addServiceStateListener(mPhoneStateListener); + mTelephonyListenerManager.addServiceStateListener(mPhoneStateListener); mGlobalSettings.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, mAirplaneModeObserver); @@ -384,6 +404,16 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mConfigurationController.addCallback(this); } + /** + * Clean up callbacks + */ + public void destroy() { + mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); + mTelephonyListenerManager.removeServiceStateListener(mPhoneStateListener); + mGlobalSettings.unregisterContentObserver(mAirplaneModeObserver); + mConfigurationController.removeCallback(this); + } + protected Context getContext() { return mContext; } @@ -392,6 +422,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene return mUiEventLogger; } + protected StatusBar getStatusBar() { + return mStatusBar; + } + /** * Show the global actions dialog (creating if necessary) * @@ -625,7 +659,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger, - mInfoProvider); + mInfoProvider, mStatusBar); dialog.setOnDismissListener(this); dialog.setOnShowListener(this); @@ -679,14 +713,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDialog.refreshDialog(); } } - - /** - * Clean up callbacks - */ - public void destroy() { - mConfigurationController.removeCallback(this); - } - /** * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is * called when the quick access wallet requests dismissal. @@ -2008,7 +2034,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } }; - private ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) { + private final ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) { @Override public void onChange(boolean selfChange) { onAirplaneModeChanged(); @@ -2100,9 +2126,53 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected final Runnable mOnRotateCallback; private UiEventLogger mUiEventLogger; private GlobalActionsInfoProvider mInfoProvider; + private GestureDetector mGestureDetector; + private StatusBar mStatusBar; protected ViewGroup mContainer; + @VisibleForTesting + protected GestureDetector.SimpleOnGestureListener mGestureListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDown(MotionEvent e) { + // All gestures begin with this message, so continue listening + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + // Close without opening shade + mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + cancel(); + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + if (distanceY < 0 && distanceY > distanceX + && e1.getY() <= mStatusBar.getStatusBarHeight()) { + // Downwards scroll from top + openShadeAndDismiss(); + return true; + } + return false; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + if (velocityY > 0 && Math.abs(velocityY) > Math.abs(velocityX) + && e1.getY() <= mStatusBar.getStatusBarHeight()) { + // Downwards fling from top + openShadeAndDismiss(); + return true; + } + return false; + } + }; + ActionsDialogLite(Context context, int themeRes, MyAdapter adapter, MyOverflowAdapter overflowAdapter, NotificationShadeDepthController depthController, @@ -2110,7 +2180,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene NotificationShadeWindowController notificationShadeWindowController, SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, - @Nullable GlobalActionsInfoProvider infoProvider) { + @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar) { super(context, themeRes); mContext = context; mAdapter = adapter; @@ -2125,6 +2195,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mKeyguardShowing = keyguardShowing; mUiEventLogger = uiEventLogger; mInfoProvider = infoProvider; + mStatusBar = statusBar; + + mGestureDetector = new GestureDetector(mContext, mGestureListener); // Window initialization Window window = getWindow(); @@ -2146,6 +2219,23 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene initializeLayout(); } + @Override + public boolean onTouchEvent(MotionEvent event) { + return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event); + } + + private void openShadeAndDismiss() { + mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + if (mStatusBar.isKeyguardShowing()) { + // match existing lockscreen behavior to open QS when swiping from status bar + mStatusBar.animateExpandSettingsPanel(null); + } else { + // otherwise, swiping down should expand notification shade + mStatusBar.animateExpandNotificationsPanel(); + } + dismiss(); + } + private ListPopupWindow createPowerOverflowPopup() { GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu( new ContextThemeWrapper( @@ -2194,9 +2284,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mGlobalActionsLayout.setRotationListener(this::onRotate); mGlobalActionsLayout.setAdapter(mAdapter); mContainer = findViewById(com.android.systemui.R.id.global_actions_container); - mContainer.setOnClickListener(v -> { - mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); - cancel(); + mContainer.setOnTouchListener((v, event) -> { + mGestureDetector.onTouchEvent(event); + return v.onTouchEvent(event); }); View overflowButton = findViewById( diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index 178a74cecc2e..e37d3d586ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -32,7 +32,6 @@ import android.widget.TextView; import com.android.internal.R; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.scrim.ScrimDrawable; import com.android.systemui.statusbar.BlurUtils; @@ -52,19 +51,24 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks private final KeyguardStateController mKeyguardStateController; private final DeviceProvisionedController mDeviceProvisionedController; private final BlurUtils mBlurUtils; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final CommandQueue mCommandQueue; private GlobalActionsDialogLite mGlobalActionsDialog; private boolean mDisabled; @Inject public GlobalActionsImpl(Context context, CommandQueue commandQueue, - Lazy<GlobalActionsDialogLite> globalActionsDialogLazy, BlurUtils blurUtils) { + Lazy<GlobalActionsDialogLite> globalActionsDialogLazy, BlurUtils blurUtils, + KeyguardStateController keyguardStateController, + DeviceProvisionedController deviceProvisionedController, + KeyguardUpdateMonitor keyguardUpdateMonitor) { mContext = context; mGlobalActionsDialogLazy = globalActionsDialogLazy; - mKeyguardStateController = Dependency.get(KeyguardStateController.class); - mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); + mKeyguardStateController = keyguardStateController; + mDeviceProvisionedController = deviceProvisionedController; mCommandQueue = commandQueue; mBlurUtils = blurUtils; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; mCommandQueue.addCallback(this); } @@ -83,7 +87,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks mGlobalActionsDialog = mGlobalActionsDialogLazy.get(); mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(), mDeviceProvisionedController.isDeviceProvisioned()); - Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth(); + mKeyguardUpdateMonitor.requestFaceAuth(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt index 39008eecd6a2..17b532a643cd 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt @@ -25,6 +25,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import android.widget.ImageView +import android.widget.TextView import com.android.systemui.R import com.android.systemui.controls.controller.ControlsController import com.android.systemui.plugins.ActivityStarter @@ -70,6 +71,11 @@ class GlobalActionsInfoProvider @Inject constructor( val view = LayoutInflater.from(context).inflate(R.layout.global_actions_change_panel, parent, false) + + val walletTitle = walletClient.serviceLabel ?: context.getString(R.string.wallet_title) + val message = view.findViewById<TextView>(R.id.global_actions_change_message) + message?.setText(context.getString(R.string.global_actions_change_description, walletTitle)) + val button = view.findViewById<ImageView>(R.id.global_actions_change_button) button.setOnClickListener { _ -> dismissParent.run() diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java index 01a353ce8f1f..d30783c29f92 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java @@ -46,6 +46,7 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { private final ImageGLWallpaper mWallpaper; private final Rect mSurfaceSize = new Rect(); private final WallpaperTexture mTexture; + private Consumer<Bitmap> mOnBitmapUpdated; public ImageWallpaperRenderer(Context context) { final WallpaperManager wpm = context.getSystemService(WallpaperManager.class); @@ -60,10 +61,9 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { /** * @hide - * @return */ - public void useBitmap(Consumer<Bitmap> c) { - mTexture.use(c); + public void setOnBitmapChanged(Consumer<Bitmap> c) { + mOnBitmapUpdated = c; } @Override @@ -80,6 +80,8 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { mTexture.use(bitmap -> { if (bitmap == null) { Log.w(TAG, "reload texture failed!"); + } else if (mOnBitmapUpdated != null) { + mOnBitmapUpdated.accept(bitmap); } mWallpaper.setup(bitmap); }); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index bec4ce6ba658..fc5f3b8ae994 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -20,7 +20,6 @@ import android.annotation.Nullable; import android.content.res.ColorStateList; import android.graphics.Color; import android.text.TextUtils; -import android.view.View; import androidx.annotation.IntDef; @@ -202,10 +201,7 @@ public class KeyguardIndicationRotateTextViewController extends mCurrIndicationType = type; mIndicationQueue.removeIf(x -> x == type); - if (mCurrIndicationType == INDICATION_TYPE_NONE) { - mView.setVisibility(View.GONE); - } else { - mView.setVisibility(View.VISIBLE); + if (mCurrIndicationType != INDICATION_TYPE_NONE) { mIndicationQueue.add(type); // re-add to show later } @@ -299,7 +295,7 @@ public class KeyguardIndicationRotateTextViewController extends } } - private static final int INDICATION_TYPE_NONE = -1; + static final int INDICATION_TYPE_NONE = -1; public static final int INDICATION_TYPE_OWNER_INFO = 0; public static final int INDICATION_TYPE_DISCLOSURE = 1; public static final int INDICATION_TYPE_LOGOUT = 2; diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt index 77d789292e5e..2bf102f724f4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt @@ -186,11 +186,9 @@ class KeyguardMediaController @Inject constructor( } private fun hideMediaPlayer() { - if (useSplitShade) { - setVisibility(splitShadeContainer, View.GONE) - } else { - setVisibility(singlePaneContainer, View.GONE) - } + // always hide splitShadeContainer as it's initially visible and may influence layout + setVisibility(splitShadeContainer, View.GONE) + setVisibility(singlePaneContainer, View.GONE) } private fun setVisibility(view: ViewGroup?, newVisibility: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index c2b580773424..19190cd4a17d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -46,6 +46,7 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.constraintlayout.widget.ConstraintSet; +import com.android.internal.jank.InteractionJankMonitor; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.R; import com.android.systemui.animation.ActivityLaunchAnimator; @@ -468,7 +469,8 @@ public class MediaControlPanel { TransitionLayout player) { // TODO(b/174236650): Make sure that the carousel indicator also fades out. // TODO(b/174236650): Instrument the animation to measure jank. - return new GhostedViewLaunchAnimatorController(player) { + return new GhostedViewLaunchAnimatorController(player, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER) { @Override protected float getCurrentTopCornerRadius() { return ((IlluminationDrawable) player.getBackground()).getCornerRadius(); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 5b1e039ad0f8..28d336ea18d5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -39,6 +39,7 @@ import android.media.session.MediaSession import android.net.Uri import android.os.Parcelable import android.os.UserHandle +import android.provider.Settings import android.service.notification.StatusBarNotification import android.text.TextUtils import android.util.Log @@ -54,6 +55,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.row.HybridGroupManager +import com.android.systemui.tuner.TunerService import com.android.systemui.util.Assert import com.android.systemui.util.Utils import com.android.systemui.util.concurrency.DelayableExecutor @@ -114,7 +116,8 @@ class MediaDataManager( private val smartspaceMediaDataProvider: SmartspaceMediaDataProvider, private var useMediaResumption: Boolean, private val useQsMediaPlayer: Boolean, - private val systemClock: SystemClock + private val systemClock: SystemClock, + private val tunerService: TunerService ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -147,6 +150,7 @@ class MediaDataManager( // There should ONLY be at most one Smartspace media recommendation. private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA private var smartspaceSession: SmartspaceSession? = null + private var allowMediaRecommendations = Utils.allowMediaRecommendations(context) @Inject constructor( @@ -164,12 +168,13 @@ class MediaDataManager( mediaDataFilter: MediaDataFilter, activityStarter: ActivityStarter, smartspaceMediaDataProvider: SmartspaceMediaDataProvider, - clock: SystemClock + clock: SystemClock, + tunerService: TunerService ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory, broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener, mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter, activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context), - Utils.useQsMediaPlayer(context), clock) + Utils.useQsMediaPlayer(context), clock, tunerService) private val appChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -243,6 +248,14 @@ class MediaDataManager( }) } smartspaceSession?.let { it.requestSmartspaceUpdate() } + tunerService.addTunable(object : TunerService.Tunable { + override fun onTuningChanged(key: String?, newValue: String?) { + allowMediaRecommendations = Utils.allowMediaRecommendations(context) + if (!allowMediaRecommendations) { + dismissSmartspaceRecommendation(key = smartspaceMediaData.targetId, delay = 0L) + } + } + }, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION) } fun destroy() { @@ -695,8 +708,7 @@ class MediaDataManager( } override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) { - if (!Utils.allowMediaRecommendations(context)) { - Log.d(TAG, "Smartspace recommendation is disabled in Settings.") + if (!allowMediaRecommendations) { return } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 075bc700cfa0..edbf18789e28 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -187,6 +187,12 @@ class MediaHierarchyManager @Inject constructor( private var currentAttachmentLocation = -1 /** + * Is there any active media in the carousel? + */ + private var hasActiveMedia: Boolean = false + get() = mediaHosts.get(LOCATION_QQS)?.visible == true + + /** * Are we currently waiting on an animation to start? */ private var animationPending: Boolean = false @@ -476,8 +482,12 @@ class MediaHierarchyManager @Inject constructor( val viewHost = createUniqueObjectHost() mediaObject.hostView = viewHost mediaObject.addVisibilityChangeListener { + // If QQS changes visibility, we need to force an update to ensure the transition + // goes into the correct state + val stateUpdate = mediaObject.location == LOCATION_QQS + // Never animate because of a visibility change, only state changes should do that - updateDesiredLocation(forceNoAnimation = true) + updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = stateUpdate) } mediaHosts[mediaObject.location] = mediaObject if (mediaObject.location == desiredLocation) { @@ -521,10 +531,15 @@ class MediaHierarchyManager @Inject constructor( * going from the old desired location to the new one. * * @param forceNoAnimation optional parameter telling the system not to animate + * @param forceStateUpdate optional parameter telling the system to update transition state + * even if location did not change */ - private fun updateDesiredLocation(forceNoAnimation: Boolean = false) { + private fun updateDesiredLocation( + forceNoAnimation: Boolean = false, + forceStateUpdate: Boolean = false + ) { val desiredLocation = calculateLocation() - if (desiredLocation != this.desiredLocation) { + if (desiredLocation != this.desiredLocation || forceStateUpdate) { if (this.desiredLocation >= 0) { previousLocation = this.desiredLocation } @@ -784,7 +799,7 @@ class MediaHierarchyManager @Inject constructor( private fun getQSTransformationProgress(): Float { val currentHost = getHost(desiredLocation) val previousHost = getHost(previousLocation) - if (currentHost?.location == LOCATION_QS) { + if (hasActiveMedia && currentHost?.location == LOCATION_QS) { if (previousHost?.location == LOCATION_QQS) { if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) { return qsExpansion @@ -917,6 +932,7 @@ class MediaHierarchyManager @Inject constructor( val location = when { qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS qsExpansion > 0.4f && onLockscreen -> LOCATION_QS + !hasActiveMedia -> LOCATION_QS onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN else -> LOCATION_QQS diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 0d9749e05262..ff5d0b157c80 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -251,8 +251,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private float mMLResults; // For debugging - private ArrayDeque<String> mPredictionLog = new ArrayDeque<>(); - private ArrayDeque<String> mGestureLog = new ArrayDeque<>(); + private LogArray mPredictionLog = new LogArray(MAX_NUM_LOGGED_PREDICTIONS); + private LogArray mGestureLogInsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES); + private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES); private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; @@ -631,7 +632,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker return mMLResults >= mMLModelThreshold ? 1 : 0; } - private boolean isWithinTouchRegion(int x, int y) { + private boolean isWithinInsets(int x, int y) { // Disallow if we are in the bottom gesture area if (y >= (mDisplaySize.y - mBottomGestureHeight)) { return false; @@ -644,7 +645,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) { return false; } + return true; + } + private boolean isWithinTouchRegion(int x, int y) { // If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back // gesture final boolean isInsidePip = mIsInPipMode && mPipExcludedBounds.contains(x, y); @@ -675,14 +679,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker } // For debugging purposes - if (mPredictionLog.size() >= MAX_NUM_LOGGED_PREDICTIONS) { - mPredictionLog.removeFirst(); - } - mPredictionLog.addLast(String.format("Prediction [%d,%d,%d,%d,%f,%d]", + mPredictionLog.log(String.format("Prediction [%d,%d,%d,%d,%f,%d]", System.currentTimeMillis(), x, y, app, mMLResults, withinRange ? 1 : 0)); - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, mPredictionLog.peekLast()); - } // Always allow if the user is in a transient sticky immersive state if (mIsNavBarShownTransiently) { @@ -755,7 +753,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mMLResults = 0; mLogGesture = false; mInRejectedExclusion = false; - mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed + boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY()); + mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed && isWithinInsets && !mGestureBlockingActivityRunning && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); @@ -769,18 +768,13 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mThresholdCrossed = false; } - // For debugging purposes - if (mGestureLog.size() >= MAX_NUM_LOGGED_GESTURES) { - mGestureLog.removeFirst(); - } - mGestureLog.addLast(String.format( + // For debugging purposes, only log edge points + (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format( "Gesture [%d,alw=%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]", - System.currentTimeMillis(), mAllowGesture, mIsOnLeftEdge, mIsBackGestureAllowed, + System.currentTimeMillis(), mAllowGesture, mIsOnLeftEdge, + mIsBackGestureAllowed, QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize, mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, mGestureLog.peekLast()); - } } else if (mAllowGesture || mLogGesture) { if (!mThresholdCrossed) { mEndPoint.x = (int) ev.getX(); @@ -907,7 +901,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker pw.println(" mUseMLModel=" + mUseMLModel); pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); pw.println(" mStartingQuickstepRotation=" + mStartingQuickstepRotation); - pw.println(" mInRejectedExclusion" + mInRejectedExclusion); + pw.println(" mInRejectedExclusion=" + mInRejectedExclusion); pw.println(" mExcludeRegion=" + mExcludeRegion); pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); pw.println(" mIsInPipMode=" + mIsInPipMode); @@ -922,7 +916,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker pw.println(" mTouchSlop=" + mTouchSlop); pw.println(" mBottomGestureHeight=" + mBottomGestureHeight); pw.println(" mPredictionLog=" + String.join("\n", mPredictionLog)); - pw.println(" mGestureLog=" + String.join("\n", mGestureLog)); + pw.println(" mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets)); + pw.println(" mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets)); pw.println(" mEdgeBackPlugin=" + mEdgeBackPlugin); } @@ -945,4 +940,23 @@ public class EdgeBackGestureHandler extends CurrentUserTracker } proto.edgeBackGestureHandler.allowGesture = mAllowGesture; } + + + private static class LogArray extends ArrayDeque<String> { + private final int mLength; + + LogArray(int length) { + mLength = length; + } + + void log(String message) { + if (size() >= mLength) { + removeFirst(); + } + addLast(message); + if (DEBUG_MISSING_GESTURE) { + Log.d(DEBUG_MISSING_GESTURE_TAG, message); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java index b5ac90828fce..d863dcce4fc7 100644 --- a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java @@ -240,9 +240,16 @@ public class NotificationHelper { /** Returns whether {@code entry} is suppressed from shade, meaning we should not show it. */ public static boolean shouldFilterOut( Optional<Bubbles> bubblesOptional, NotificationEntry entry) { - return bubblesOptional.isPresent() - && bubblesOptional.get().isBubbleNotificationSuppressedFromShade( - entry.getKey(), entry.getSbn().getGroupKey()); + boolean isSuppressed = false; + //TODO(b/190822282): Investigate what is causing the NullPointerException + try { + isSuppressed = bubblesOptional.isPresent() + && bubblesOptional.get().isBubbleNotificationSuppressedFromShade( + entry.getKey(), entry.getSbn().getGroupKey()); + } catch (Exception e) { + Log.e(TAG, "Exception checking if notification is suppressed: " + e); + } + return isSuppressed; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index f6d93895ce05..929aedae6706 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -28,6 +28,7 @@ import android.view.View; import android.widget.TextView; import android.widget.Toast; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; @@ -73,7 +74,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final PageIndicator mPageIndicator; private final View mPowerMenuLite; private final boolean mShowPMLiteButton; - private GlobalActionsDialogLite mGlobalActionsDialog; + private final GlobalActionsDialogLite mGlobalActionsDialog; private final UiEventLogger mUiEventLogger; private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = @@ -272,7 +273,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private void startSettingsActivity() { ActivityLaunchAnimator.Controller animationController = mSettingsButtonContainer != null ? ActivityLaunchAnimator.Controller.fromView( - mSettingsButtonContainer) : null; + mSettingsButtonContainer, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON) : null; mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), true /* dismissShade */, animationController); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index e1a66b2c07ee..7b8a6a0a8d0e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -539,8 +539,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) { View hostView = mediaHost.getHostView(); - // on keyguard we cross-fade to expanded, so no need to pin it. - if (mLastQSExpansion > 0 && !isKeyguardState()) { + // On keyguard we cross-fade to expanded, so no need to pin it. + // If the collapsed qs isn't visible, we also just keep it at the laid out position. + if (mLastQSExpansion > 0 && !isKeyguardState() && mQqsMediaHost.getVisible()) { float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView) - hostView.getHeight(); float currentPosition = mediaHost.getCurrentBounds().top diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 525bad8a0e25..6ddf2a75f491 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -45,7 +45,9 @@ import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.external.CustomTile; +import com.android.systemui.qs.external.CustomTileStatePersister; import com.android.systemui.qs.external.TileLifecycleManager; +import com.android.systemui.qs.external.TileServiceKey; import com.android.systemui.qs.external.TileServices; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.UserTracker; @@ -93,6 +95,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private final QSLogger mQSLogger; private final UiEventLogger mUiEventLogger; private final InstanceIdSequence mInstanceIdSequence; + private final CustomTileStatePersister mCustomTileStatePersister; private final List<Callback> mCallbacks = new ArrayList<>(); private AutoTileManager mAutoTiles; @@ -119,7 +122,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, - SecureSettings secureSettings) { + SecureSettings secureSettings, + CustomTileStatePersister customTileStatePersister) { mIconController = iconController; mContext = context; mUserContext = context; @@ -139,6 +143,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mDumpManager.registerDumpable(TAG, this); mUserTracker = userTracker; mSecureSettings = secureSettings; + mCustomTileStatePersister = customTileStatePersister; mainHandler.post(() -> { // This is technically a hack to avoid circular dependency of @@ -418,6 +423,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D changeTiles(mTileSpecs, newSpecs); } + /** + * Change the tiles triggered by the user editing. + * <p> + * This is not called on device start, or on user change. + */ public void changeTiles(List<String> previousTiles, List<String> newTiles) { final List<String> copy = new ArrayList<>(previousTiles); final int NP = copy.size(); @@ -433,6 +443,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mBroadcastDispatcher); lifecycleManager.onStopListening(); lifecycleManager.onTileRemoved(); + mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser)); TileLifecycleManager.setTileAdded(mContext, component, false); lifecycleManager.flushMessagesAndUnbind(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 7cc6ecd8cf62..997b96626747 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -399,12 +399,14 @@ public class QuickStatusBarHeader extends FrameLayout { mClockIconsSeparatorLayoutParams.width = 0; setSeparatorVisibility(false); mShowClockIconsSeparator = false; + mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE); } else { datePrivacySeparatorLayoutParams.width = topCutout.width(); mDatePrivacySeparator.setVisibility(View.VISIBLE); mClockIconsSeparatorLayoutParams.width = topCutout.width(); mShowClockIconsSeparator = true; setSeparatorVisibility(mKeyguardExpansionFraction == 0f); + mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ON); } } mDatePrivacySeparator.setLayoutParams(datePrivacySeparatorLayoutParams); 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 10eea828bcb4..396eca5c1bee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -46,6 +46,7 @@ import android.widget.Switch; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -85,6 +86,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private final IQSTileService mService; private final TileServiceManager mServiceManager; private final int mUser; + private final CustomTileStatePersister mCustomTileStatePersister; private android.graphics.drawable.Icon mDefaultIcon; private CharSequence mDefaultLabel; @@ -94,6 +96,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private boolean mIsTokenGranted; private boolean mIsShowingDialog; + private final TileServiceKey mKey; + private CustomTile( QSHost host, Looper backgroundLooper, @@ -104,7 +108,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener ActivityStarter activityStarter, QSLogger qsLogger, String action, - Context userContext + Context userContext, + CustomTileStatePersister customTileStatePersister ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); @@ -113,15 +118,29 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile = new Tile(); mUserContext = userContext; mUser = mUserContext.getUserId(); - updateDefaultTileAndIcon(); + mKey = new TileServiceKey(mComponent, mUser); + mServiceManager = host.getTileServices().getTileWrapper(this); + mService = mServiceManager.getTileService(); + mCustomTileStatePersister = customTileStatePersister; + } + + @Override + protected void handleInitialize() { + updateDefaultTileAndIcon(); if (mServiceManager.isToggleableTile()) { // Replace states with BooleanState resetStates(); } - - mService = mServiceManager.getTileService(); mServiceManager.setTileChangeListener(this); + if (mServiceManager.isActiveTile()) { + Tile t = mCustomTileStatePersister.readState(mKey); + if (t != null) { + applyTileState(t, /* overwriteNulls */ false); + mServiceManager.clearPendingBind(); + refreshState(); + } + } } @Override @@ -191,7 +210,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener @Override public void onTileChanged(ComponentName tile) { - updateDefaultTileAndIcon(); + mHandler.post(this::updateDefaultTileAndIcon); } @Override @@ -213,16 +232,44 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener } public Tile getQsTile() { + // TODO(b/191145007) Move to background thread safely updateDefaultTileAndIcon(); return mTile; } - public void updateState(Tile tile) { - mTile.setIcon(tile.getIcon()); - mTile.setLabel(tile.getLabel()); - mTile.setSubtitle(tile.getSubtitle()); - mTile.setContentDescription(tile.getContentDescription()); - mTile.setStateDescription(tile.getStateDescription()); + /** + * Update state of {@link this#mTile} from a remote {@link TileService}. + * @param tile tile populated with state to apply + */ + public void updateTileState(Tile tile) { + // This comes from a binder call IQSService.updateQsTile + mHandler.post(() -> handleUpdateTileState(tile)); + } + + private void handleUpdateTileState(Tile tile) { + applyTileState(tile, /* overwriteNulls */ true); + if (mServiceManager.isActiveTile()) { + mCustomTileStatePersister.persistState(mKey, tile); + } + } + + @WorkerThread + private void applyTileState(Tile tile, boolean overwriteNulls) { + if (tile.getIcon() != null || overwriteNulls) { + mTile.setIcon(tile.getIcon()); + } + if (tile.getLabel() != null || overwriteNulls) { + mTile.setLabel(tile.getLabel()); + } + if (tile.getSubtitle() != null || overwriteNulls) { + mTile.setSubtitle(tile.getSubtitle()); + } + if (tile.getContentDescription() != null || overwriteNulls) { + mTile.setContentDescription(tile.getContentDescription()); + } + if (tile.getStateDescription() != null || overwriteNulls) { + mTile.setStateDescription(tile.getStateDescription()); + } mTile.setState(tile.getState()); } @@ -459,6 +506,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener final StatusBarStateController mStatusBarStateController; final ActivityStarter mActivityStarter; final QSLogger mQSLogger; + final CustomTileStatePersister mCustomTileStatePersister; Context mUserContext; String mSpec = ""; @@ -472,7 +520,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, - QSLogger qsLogger + QSLogger qsLogger, + CustomTileStatePersister customTileStatePersister ) { mQSHostLazy = hostLazy; mBackgroundLooper = backgroundLooper; @@ -482,6 +531,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mStatusBarStateController = statusBarStateController; mActivityStarter = activityStarter; mQSLogger = qsLogger; + mCustomTileStatePersister = customTileStatePersister; } Builder setSpec(@NonNull String spec) { @@ -509,7 +559,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mActivityStarter, mQSLogger, action, - mUserContext + mUserContext, + mCustomTileStatePersister ); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt new file mode 100644 index 000000000000..021e632810f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt @@ -0,0 +1,122 @@ +/* + * 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.qs.external + +import android.content.ComponentName +import android.content.Context +import android.service.quicksettings.Tile +import android.util.Log +import com.android.internal.annotations.VisibleForTesting +import org.json.JSONException +import org.json.JSONObject +import javax.inject.Inject + +data class TileServiceKey(val componentName: ComponentName, val user: Int) { + private val string = "${componentName.flattenToString()}:$user" + override fun toString() = string +} +private const val STATE = "state" +private const val LABEL = "label" +private const val SUBTITLE = "subtitle" +private const val CONTENT_DESCRIPTION = "content_description" +private const val STATE_DESCRIPTION = "state_description" + +/** + * Persists and retrieves state for [CustomTile]. + * + * This class will persists to a fixed [SharedPreference] file a state for a pair of [ComponentName] + * and user id ([TileServiceKey]). + * + * It persists the state from a [Tile] necessary to present the view in the same state when + * retrieved, with the exception of the icon. + */ +class CustomTileStatePersister @Inject constructor(context: Context) { + companion object { + private const val FILE_NAME = "custom_tiles_state" + } + + private val sharedPreferences = context.getSharedPreferences(FILE_NAME, 0) + + /** + * Read the state from [SharedPreferences]. + * + * Returns `null` if the tile has no saved state. + * + * Any fields that have not been saved will be set to `null` + */ + fun readState(key: TileServiceKey): Tile? { + val state = sharedPreferences.getString(key.toString(), null) ?: return null + return try { + readTileFromString(state) + } catch (e: JSONException) { + Log.e("TileServicePersistence", "Bad saved state: $state", e) + null + } + } + + /** + * Persists the state into [SharedPreferences]. + * + * The implementation does not store fields that are `null` or icons. + */ + fun persistState(key: TileServiceKey, tile: Tile) { + val state = writeToString(tile) + + sharedPreferences.edit().putString(key.toString(), state).apply() + } + + /** + * Removes the state for a given tile, user pair. + * + * Used when the tile is removed by the user. + */ + fun removeState(key: TileServiceKey) { + sharedPreferences.edit().remove(key.toString()).apply() + } +} + +@VisibleForTesting +internal fun readTileFromString(stateString: String): Tile { + val json = JSONObject(stateString) + return Tile().apply { + state = json.getInt(STATE) + label = json.getStringOrNull(LABEL) + subtitle = json.getStringOrNull(SUBTITLE) + contentDescription = json.getStringOrNull(CONTENT_DESCRIPTION) + stateDescription = json.getStringOrNull(STATE_DESCRIPTION) + } +} + +// Properties with null values will not be saved to the Json string in any way. This makes sure +// to properly retrieve a null in that case. +private fun JSONObject.getStringOrNull(name: String): String? { + return if (has(name)) getString(name) else null +} + +@VisibleForTesting +internal fun writeToString(tile: Tile): String { + // Not storing the icon + return with(tile) { + JSONObject() + .put(STATE, state) + .put(LABEL, label) + .put(SUBTITLE, subtitle) + .put(CONTENT_DESCRIPTION, contentDescription) + .put(STATE_DESCRIPTION, stateDescription) + .toString() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 35cf2a12745e..a7cd11314d7e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -204,7 +204,7 @@ public class TileServices extends IQSService.Stub { tileServiceManager.clearPendingBind(); tileServiceManager.setLastUpdate(System.currentTimeMillis()); } - customTile.updateState(tile); + customTile.updateTileState(tile); customTile.refreshState(); } } 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 8f7c493417ec..842fd6c62d06 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -160,7 +160,8 @@ public class QSFactoryImpl implements QSFactory { public QSTile createTile(String tileSpec) { QSTileImpl tile = createTileInternal(tileSpec); if (tile != null) { - tile.handleStale(); // Tile was just created, must be stale. + tile.initialize(); + tile.postStale(); // Tile was just created, must be stale. } return tile; } 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 a938821a343f..4616be8f7937 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -50,6 +50,7 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.InstanceId; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; @@ -158,6 +159,15 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy */ abstract public int getMetricsCategory(); + /** + * Performs initialization of the tile + * + * Use this to perform initialization of the tile. Empty by default. + */ + protected void handleInitialize() { + + } + protected QSTileImpl( QSHost host, Looper backgroundLooper, @@ -346,6 +356,15 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mHandler.sendEmptyMessage(H.DESTROY); } + /** + * Schedules initialization of the tile. + * + * Should be called upon creation of the tile, before performing other operations + */ + public void initialize() { + mHandler.sendEmptyMessage(H.INITIALIZE); + } + public TState getState() { return mState; } @@ -370,6 +389,13 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } /** + * Posts a stale message to the background thread. + */ + public void postStale() { + mHandler.sendEmptyMessage(H.STALE); + } + + /** * Handles secondary click on the tile. * * Defaults to {@link QSTileImpl#handleClick} @@ -389,7 +415,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy */ protected void handleLongClick(@Nullable View view) { ActivityLaunchAnimator.Controller animationController = - view != null ? ActivityLaunchAnimator.Controller.fromView(view) : null; + view != null ? ActivityLaunchAnimator.Controller.fromView(view, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) : null; mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0, animationController); } @@ -580,6 +607,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private static final int SET_LISTENING = 13; @VisibleForTesting protected static final int STALE = 14; + private static final int INITIALIZE = 15; @VisibleForTesting protected H(Looper looper) { @@ -638,6 +666,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } else if (msg.what == STALE) { name = "handleStale"; handleStale(); + } else if (msg.what == INITIALIZE) { + name = "initialize"; + handleInitialize(); } else { throw new IllegalArgumentException("Unknown msg: " + msg.what); } 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 69d49d44f822..73d13700d61b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -11,6 +11,7 @@ import android.text.TextUtils import android.text.format.DateFormat import android.view.View import androidx.annotation.VisibleForTesting +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator @@ -70,7 +71,10 @@ class AlarmTile @Inject constructor( } override fun handleClick(view: View?) { - val animationController = view?.let { ActivityLaunchAnimator.Controller.fromView(it) } + val animationController = view?.let { + ActivityLaunchAnimator.Controller.fromView( + it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) + } val pendingIntent = lastAlarmInfo?.showIntent if (pendingIntent != null) { mActivityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController) 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 6d3190ffa725..f66b7226fbae 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -22,6 +22,7 @@ import android.os.Handler import android.os.Looper import android.service.quicksettings.Tile import android.view.View +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator @@ -106,7 +107,8 @@ class DeviceControlsTile @Inject constructor( putExtra(ControlsUiController.EXTRA_ANIMATE, true) } val animationController = view?.let { - ActivityLaunchAnimator.Controller.fromView(it) + ActivityLaunchAnimator.Controller.fromView( + it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) } mUiHandler.post { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 0e4434baa0e8..98cd88af232f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -37,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.animation.ActivityLaunchAnimator; @@ -120,7 +121,8 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { @Override protected void handleClick(@Nullable View view) { ActivityLaunchAnimator.Controller animationController = - view == null ? null : ActivityLaunchAnimator.Controller.fromView(view); + view == null ? null : ActivityLaunchAnimator.Controller.fromView(view, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE); mUiHandler.post(() -> { if (mSelectedCard != null) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java index 9e11451afa06..0a60f6da159e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java @@ -62,6 +62,7 @@ public class CropView extends View { private final float mCropTouchMargin; private final Paint mShadePaint; private final Paint mHandlePaint; + private final Paint mContainerBackgroundPaint; // Crop rect with each element represented as [0,1] along its proper axis. private RectF mCrop = new RectF(0, 0, 1, 1); @@ -79,6 +80,9 @@ public class CropView extends View { // The allowable values for the current boundary being dragged private Range<Float> mMotionRange; + // Value [0,1] indicating progress in animateEntrance() + private float mEntranceInterpolation = 1f; + private CropInteractionListener mCropInteractionListener; private final ExploreByTouchHelper mExploreByTouchHelper; @@ -92,6 +96,9 @@ public class CropView extends View { attrs, R.styleable.CropView, 0, 0); mShadePaint = new Paint(); mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT)); + mContainerBackgroundPaint = new Paint(); + mContainerBackgroundPaint.setColor(t.getColor(R.styleable.CropView_containerBackgroundColor, + Color.TRANSPARENT)); mHandlePaint = new Paint(); mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK)); mHandlePaint.setStrokeCap(Paint.Cap.ROUND); @@ -125,10 +132,22 @@ public class CropView extends View { @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); - drawShade(canvas, 0, 0, 1, mCrop.top); - drawShade(canvas, 0, mCrop.bottom, 1, 1); + // Top and bottom borders reflect the boundary between the (scrimmed) image and the + // opaque container background. This is only meaningful during an entrance transition. + float topBorder = MathUtils.lerp(mCrop.top, 0, mEntranceInterpolation); + float bottomBorder = MathUtils.lerp(mCrop.bottom, 1, mEntranceInterpolation); + drawShade(canvas, 0, topBorder, 1, mCrop.top); + drawShade(canvas, 0, mCrop.bottom, 1, bottomBorder); drawShade(canvas, 0, mCrop.top, mCrop.left, mCrop.bottom); drawShade(canvas, mCrop.right, mCrop.top, 1, mCrop.bottom); + + // Entrance transition expects the crop bounds to be full width, so we only draw container + // background on the top and bottom. + drawContainerBackground(canvas, 0, 0, 1, topBorder); + drawContainerBackground(canvas, 0, bottomBorder, 1, 1); + + mHandlePaint.setAlpha((int) (mEntranceInterpolation * 255)); + drawHorizontalHandle(canvas, mCrop.top, /* draw the handle tab up */ true); drawHorizontalHandle(canvas, mCrop.bottom, /* draw the handle tab down */ false); drawVerticalHandle(canvas, mCrop.left, /* left */ true); @@ -282,6 +301,22 @@ public class CropView extends View { } /** + * Fade in crop bounds, animate reveal of cropped-out area from current crop bounds. + */ + public void animateEntrance() { + mEntranceInterpolation = 0; + ValueAnimator animator = new ValueAnimator(); + animator.addUpdateListener(animation -> { + mEntranceInterpolation = animation.getAnimatedFraction(); + invalidate(); + }); + animator.setFloatValues(0f, 1f); + animator.setDuration(750); + animator.setInterpolator(new FastOutSlowInInterpolator()); + animator.start(); + } + + /** * Set additional top and bottom padding for the image being cropped (used when the * corresponding ImageView doesn't take the full height). */ @@ -369,6 +404,13 @@ public class CropView extends View { fractionToVerticalPixels(bottom), mShadePaint); } + private void drawContainerBackground(Canvas canvas, float left, float top, float right, + float bottom) { + canvas.drawRect(fractionToHorizontalPixels(left), fractionToVerticalPixels(top), + fractionToHorizontalPixels(right), + fractionToVerticalPixels(bottom), mContainerBackgroundPaint); + } + private void drawHorizontalHandle(Canvas canvas, float frac, boolean handleTabUp) { int y = fractionToVerticalPixels(frac); canvas.drawLine(fractionToHorizontalPixels(mCrop.left), y, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index d5b4032b1c0f..25ec1d74008e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -192,7 +192,6 @@ public class LongScreenshotActivity extends Activity { mLongScreenshot = longScreenshot; Drawable drawable = mLongScreenshot.getDrawable(); mPreview.setImageDrawable(drawable); - mCropView.setVisibility(View.VISIBLE); mMagnifierView.setDrawable(mLongScreenshot.getDrawable(), mLongScreenshot.getWidth(), mLongScreenshot.getHeight()); // Original boundaries go from the image tile set's y=0 to y=pageSize, so @@ -219,10 +218,12 @@ public class LongScreenshotActivity extends Activity { public void onTransitionEnd(Transition transition) { super.onTransitionEnd(transition); mPreview.animate().alpha(1f); - mCropView.animateBoundaryTo( + mCropView.setBoundaryPosition( CropView.CropBoundary.TOP, topFraction); - mCropView.animateBoundaryTo( + mCropView.setBoundaryPosition( CropView.CropBoundary.BOTTOM, bottomFraction); + mCropView.animateEntrance(); + mCropView.setVisibility(View.VISIBLE); setButtonsEnabled(true); mEnterTransitionView.setVisibility(View.GONE); } @@ -250,6 +251,7 @@ public class LongScreenshotActivity extends Activity { Log.d(TAG, "onCachedImageLoaded(imageResult=" + imageResult + ")"); BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap); mPreview.setImageDrawable(drawable); + mPreview.setAlpha(1f); mMagnifierView.setDrawable(drawable, imageResult.bitmap.getWidth(), imageResult.bitmap.getHeight()); mCropView.setVisibility(View.VISIBLE); @@ -476,19 +478,21 @@ public class LongScreenshotActivity extends Activity { params.height = boundaries.height(); mTransitionView.setLayoutParams(params); - ConstraintLayout.LayoutParams enterTransitionParams = - (ConstraintLayout.LayoutParams) mEnterTransitionView.getLayoutParams(); - float topFraction = Math.max(0, - -mLongScreenshot.getTop() / (float) mLongScreenshot.getHeight()); - enterTransitionParams.width = (int) (scale * drawable.getIntrinsicWidth()); - enterTransitionParams.height = (int) (scale * mLongScreenshot.getPageHeight()); - mEnterTransitionView.setLayoutParams(enterTransitionParams); - - Matrix matrix = new Matrix(); - matrix.setScale(scale, scale); - matrix.postTranslate(0, -scale * drawable.getIntrinsicHeight() * topFraction); - mEnterTransitionView.setImageMatrix(matrix); - mEnterTransitionView.setTranslationY( - topFraction * previewHeight + mPreview.getPaddingTop() + extraPadding); + if (mLongScreenshot != null) { + ConstraintLayout.LayoutParams enterTransitionParams = + (ConstraintLayout.LayoutParams) mEnterTransitionView.getLayoutParams(); + float topFraction = Math.max(0, + -mLongScreenshot.getTop() / (float) mLongScreenshot.getHeight()); + enterTransitionParams.width = (int) (scale * drawable.getIntrinsicWidth()); + enterTransitionParams.height = (int) (scale * mLongScreenshot.getPageHeight()); + mEnterTransitionView.setLayoutParams(enterTransitionParams); + + Matrix matrix = new Matrix(); + matrix.setScale(scale, scale); + matrix.postTranslate(0, -scale * drawable.getIntrinsicHeight() * topFraction); + mEnterTransitionView.setImageMatrix(matrix); + mEnterTransitionView.setTranslationY( + topFraction * previewHeight + mPreview.getPaddingTop() + extraPadding); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 91a0e6fedef8..0bb702f6c9e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -112,6 +112,7 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private ViewGroup mIndicationArea; private KeyguardIndicationTextView mTopIndicationView; + private KeyguardIndicationTextView mLockScreenIndicationView; private final IBatteryStats mBatteryInfo; private final SettableWakeLock mWakeLock; private final DockManager mDockManager; @@ -208,17 +209,21 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal mKeyguardUpdateMonitor.registerCallback(mTickReceiver); mStatusBarStateController.addCallback(mStatusBarStateListener); mKeyguardStateController.addCallback(this); + + mStatusBarStateListener.onDozingChanged(mStatusBarStateController.isDozing()); } public void setIndicationArea(ViewGroup indicationArea) { mIndicationArea = indicationArea; mTopIndicationView = indicationArea.findViewById(R.id.keyguard_indication_text); + mLockScreenIndicationView = indicationArea.findViewById( + R.id.keyguard_indication_text_bottom); mInitialTextColorState = mTopIndicationView != null ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE); mRotateTextViewController = new KeyguardIndicationRotateTextViewController( - indicationArea.findViewById(R.id.keyguard_indication_text_bottom), - mExecutor, - mStatusBarStateController); + mLockScreenIndicationView, + mExecutor, + mStatusBarStateController); updateIndication(false /* animate */); updateDisclosure(); if (mBroadcastReceiver == null) { @@ -630,6 +635,7 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal // should be shown based on user or device state // AoD if (mDozing) { + mLockScreenIndicationView.setVisibility(View.GONE); mTopIndicationView.setVisibility(VISIBLE); // When dozing we ignore any text color and use white instead, because // colors can be hard to read in low brightness. @@ -659,6 +665,8 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal // LOCK SCREEN mTopIndicationView.setVisibility(GONE); + mTopIndicationView.setText(null); + mLockScreenIndicationView.setVisibility(View.VISIBLE); updateIndications(animate, KeyguardUpdateMonitor.getCurrentUser()); } @@ -914,7 +922,8 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState); } else if (mKeyguardUpdateMonitor.isScreenOn()) { - showTransientIndication(errString); + showTransientIndication(errString, /* isError */ true, + /* hideOnScreenOff */ true); // We want to keep this message around in case the screen was off hideTransientIndicationDelayed(HIDE_DELAY_MS); } else { @@ -1032,9 +1041,8 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal if (mHideTransientMessageOnScreenOff && mDozing) { hideTransientIndication(); - } else { - updateIndication(false); } + updateIndication(false); } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 6a5f001ac2ee..ec648ad519a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -93,10 +93,10 @@ class CircleReveal( val endRadius: Float ) : LightRevealEffect { override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) { - val interpolatedAmount = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(amount) - val fadeAmount = - LightRevealEffect.getPercentPastThreshold(interpolatedAmount, 0.75f) - val radius = startRadius + ((endRadius - startRadius) * interpolatedAmount) + // reveal amount updates already have an interpolator, so we intentionally use the + // non-interpolated amount + val fadeAmount = LightRevealEffect.getPercentPastThreshold(amount, 0.5f) + val radius = startRadius + ((endRadius - startRadius) * amount) scrim.revealGradientEndColorAlpha = 1f - fadeAmount scrim.setRevealGradientBounds( centerX - radius /* left */, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 9f59023f1890..f8a1ff879e72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -145,11 +145,11 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll } @Override - public boolean setState(int state) { + public boolean setState(int state, boolean force) { if (state > MAX_STATE || state < MIN_STATE) { throw new IllegalArgumentException("Invalid state " + state); } - if (state == mState) { + if (!force && state == mState) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java index b6d6ed53b681..73f3d90bd4f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java @@ -59,7 +59,19 @@ public interface SysuiStatusBarStateController extends StatusBarStateController * @param state see {@link StatusBarState} for valid options * @return {@code true} if the state changed, else {@code false} */ - boolean setState(int state); + default boolean setState(int state) { + return setState(state, false /* force */); + } + + /** + * Update the status bar state + * @param state see {@link StatusBarState} for valid options + * @param force whether to set the state even if it's the same as the current state. This will + * dispatch the state to all StatusBarStateListeners, ensuring that all listening + * components are reset to this state. + * @return {@code true} if the state was changed or set forcefully + */ + boolean setState(int state, boolean force); /** * Update the dozing state from {@link StatusBar}'s perspective diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt index 8479b30c3a75..f30010cf4d1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt @@ -383,10 +383,18 @@ const val ANIMATING_OUT = 3 const val SHOWING_PERSISTENT_DOT = 4 private const val TAG = "SystemStatusAnimationScheduler" -private const val DELAY: Long = 100 -private const val DISPLAY_LENGTH = 5000L -private const val ENTRANCE_ANIM_LENGTH = 500L -private const val CHIP_ANIM_LENGTH = 500L +private const val DELAY = 0L + +/** + * The total time spent animation should be 1500ms. The entrance animation is how much time + * we give to the system to animate system elements out of the way. Total chip animation length + * will be equivalent to 2*chip_anim_length + display_length + */ +private const val ENTRANCE_ANIM_LENGTH = 250L +private const val CHIP_ANIM_LENGTH = 250L +// 1s + entrance time + chip anim_length +private const val DISPLAY_LENGTH = 1500L + private const val MIN_UPTIME: Long = 5 * 1000 private const val DEBUG = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index aef01e9bd811..0fb1c54bb150 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -71,11 +71,11 @@ public final class NotificationClicker implements View.OnClickListener { // Check if the notification is displaying the menu, if so slide notification back if (isMenuVisible(row)) { mLogger.logMenuVisible(entry); - row.animateTranslateNotification(0); + row.animateResetTranslation(); return; } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) { mLogger.logParentMenuVisible(entry); - row.getNotificationParent().animateTranslateNotification(0); + row.getNotificationParent().animateResetTranslation(); return; } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { // We never want to open the app directly if the user clicks in between diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 760bee21b0d1..b0a7767accfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -264,7 +264,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } override fun onStateChanged(newState: Int) { - if (unlockedScreenOffAnimationController.shouldPlayScreenOffAnimation()) { + if (dozeParameters.shouldControlUnlockedScreenOff()) { if (unlockedScreenOffAnimationController.isScreenOffAnimationPlaying() && state == StatusBarState.KEYGUARD && newState == StatusBarState.SHADE) { 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 413662447028..c24c2be3faa3 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 @@ -330,30 +330,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - @Override - public void setDistanceToTopRoundness(float distanceToTopRoundness) { - super.setDistanceToTopRoundness(distanceToTopRoundness); - mBackgroundNormal.setDistanceToTopRoundness(distanceToTopRoundness); - } - - /** Sets whether this view is the last notification in a section. */ - @Override - public void setLastInSection(boolean lastInSection) { - if (lastInSection != mLastInSection) { - super.setLastInSection(lastInSection); - mBackgroundNormal.setLastInSection(lastInSection); - } - } - - /** Sets whether this view is the first notification in a section. */ - @Override - public void setFirstInSection(boolean firstInSection) { - if (firstInSection != mFirstInSection) { - super.setFirstInSection(firstInSection); - mBackgroundNormal.setFirstInSection(firstInSection); - } - } - /** * Set an override tint color that is used for the background. * 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 6fd556763943..ba28dc59def4 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 @@ -846,8 +846,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateClickAndFocus(); if (mNotificationParent != null) { setOverrideTintColor(NO_COLOR, 0.0f); - // Let's reset the distance to top roundness, as this isn't applied to group children - setDistanceToTopRoundness(NO_ROUNDNESS); mNotificationParent.updateBackgroundForGroupState(); } updateBackgroundClipping(); @@ -876,7 +874,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected boolean handleSlideBack() { if (mMenuRow != null && mMenuRow.isMenuVisible()) { - animateTranslateNotification(0 /* targetLeft */); + animateResetTranslation(); return true; } return false; @@ -1713,21 +1711,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this); mChildrenContainer.onNotificationUpdated(); - if (mShouldTranslateContents) { - mTranslateableViews.add(mChildrenContainer); - } + mTranslateableViews.add(mChildrenContainer); }); - if (mShouldTranslateContents) { - // Add the views that we translate to reveal the menu - mTranslateableViews = new ArrayList<>(); - for (int i = 0; i < getChildCount(); i++) { - mTranslateableViews.add(getChildAt(i)); - } - // Remove views that don't translate - mTranslateableViews.remove(mChildrenContainerStub); - mTranslateableViews.remove(mGutsStub); + // Add the views that we translate to reveal the menu + mTranslateableViews = new ArrayList<>(); + for (int i = 0; i < getChildCount(); i++) { + mTranslateableViews.add(getChildAt(i)); } + // Remove views that don't translate + mTranslateableViews.remove(mChildrenContainerStub); + mTranslateableViews.remove(mGutsStub); } private void doLongClickCallback() { @@ -1805,7 +1799,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mTranslateAnim.cancel(); } - if (!mShouldTranslateContents) { + if (mDismissUsingRowTranslationX) { setTranslationX(0); } else if (mTranslateableViews != null) { for (int i = 0; i < mTranslateableViews.size(); i++) { @@ -1867,23 +1861,47 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mPrivateLayout.getActiveRemoteInputText(); } - public void animateTranslateNotification(final float leftTarget) { + /** + * Reset the translation with an animation. + */ + public void animateResetTranslation() { if (mTranslateAnim != null) { mTranslateAnim.cancel(); } - mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */); + mTranslateAnim = getTranslateViewAnimator(0, null /* updateListener */); if (mTranslateAnim != null) { mTranslateAnim.start(); } } + /** + * Set the dismiss behavior of the view. + * @param usingRowTranslationX {@code true} if the view should translate using regular + * translationX, otherwise the contents will be + * translated. + */ + @Override + public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { + if (usingRowTranslationX != mDismissUsingRowTranslationX) { + // In case we were already transitioning, let's switch over! + float previousTranslation = getTranslation(); + if (previousTranslation != 0) { + setTranslation(0); + } + super.setDismissUsingRowTranslationX(usingRowTranslationX); + if (previousTranslation != 0) { + setTranslation(previousTranslation); + } + } + } + @Override public void setTranslation(float translationX) { invalidate(); if (isBlockingHelperShowingAndTranslationFinished()) { mGuts.setTranslationX(translationX); return; - } else if (!mShouldTranslateContents) { + } else if (mDismissUsingRowTranslationX) { setTranslationX(translationX); } else if (mTranslateableViews != null) { // Translate the group of views @@ -1907,7 +1925,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public float getTranslation() { - if (!mShouldTranslateContents) { + if (mDismissUsingRowTranslationX) { return getTranslationX(); } @@ -2898,7 +2916,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView float y = event.getY(); NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper(); NotificationHeaderView header = wrapper == null ? null : wrapper.getNotificationHeader(); - if (header != null && header.isInTouchRect(x - getTranslation(), y)) { + // the extra translation only needs to be added, if we're translating the notification + // contents, otherwise the motionEvent is already at the right place due to the + // touch event system. + float translation = !mDismissUsingRowTranslationX ? getTranslation() : 0; + if (header != null && header.isInTouchRect(x - translation, y)) { return true; } if ((!mIsSummaryWithChildren || shouldShowPublic()) @@ -3037,24 +3059,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - public boolean topAmountNeedsClipping() { - if (isGroupExpanded()) { - return true; - } - if (isGroupExpansionChanging()) { - return true; - } - if (getShowingLayout().shouldClipToRounding(true /* topRounded */, - false /* bottomRounded */)) { - return true; - } - if (mGuts != null && mGuts.getAlpha() != 0.0f) { - return true; - } - return false; - } - - @Override protected boolean childNeedsClipping(View child) { if (child instanceof NotificationContentView) { NotificationContentView contentView = (NotificationContentView) child; 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 5134c62dc182..d58fe3b3c4a3 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 @@ -71,21 +71,19 @@ public abstract class ExpandableOutlineView extends ExpandableView { private int mBackgroundTop; /** - * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when + * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when * it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself. */ - protected boolean mShouldTranslateContents; - private boolean mTopAmountRounded; - private float mDistanceToTopRoundness = -1; + protected boolean mDismissUsingRowTranslationX = true; private float[] mTmpCornerRadii = new float[8]; private final ViewOutlineProvider mProvider = new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { if (!mCustomOutline && getCurrentTopRoundness() == 0.0f - && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners - && !mTopAmountRounded) { - int translation = mShouldTranslateContents ? (int) getTranslation() : 0; + && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners) { + // Only when translating just the contents, does the outline need to be shifted. + int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0; int left = Math.max(translation, 0); int top = mClipTopAmount + mBackgroundTop; int right = getWidth() + Math.min(translation, 0); @@ -110,7 +108,9 @@ public abstract class ExpandableOutlineView extends ExpandableView { float topRoundness = mAlwaysRoundBothCorners ? mOutlineRadius : getCurrentBackgroundRadiusTop(); if (!mCustomOutline) { - int translation = mShouldTranslateContents && !ignoreTranslation + // The outline just needs to be shifted if we're translating the contents. Otherwise + // it's already in the right place. + int translation = !mDismissUsingRowTranslationX && !ignoreTranslation ? (int) getTranslation() : 0; int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f); left = Math.max(translation, 0) - halfExtraWidth; @@ -168,33 +168,15 @@ public abstract class ExpandableOutlineView extends ExpandableView { @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { canvas.save(); - Path intersectPath = null; - if (mTopAmountRounded && topAmountNeedsClipping()) { - int left = (int) (- mExtraWidthForClipping / 2.0f); - int top = (int) (mClipTopAmount - mDistanceToTopRoundness); - int right = getWidth() + (int) (mExtraWidthForClipping + left); - int bottom = (int) Math.max(mMinimumHeightForClipping, - Math.max(getActualHeight() - mClipBottomAmount, top + mOutlineRadius)); - getRoundedRectPath(left, top, right, bottom, mOutlineRadius, 0.0f, mClipPath); - intersectPath = mClipPath; - } - boolean clipped = false; if (childNeedsClipping(child)) { Path clipPath = getCustomClipPath(child); if (clipPath == null) { clipPath = getClipPath(false /* ignoreTranslation */); } if (clipPath != null) { - if (intersectPath != null) { - clipPath.op(intersectPath, Path.Op.INTERSECT); - } canvas.clipPath(clipPath); - clipped = true; } } - if (!clipped && intersectPath != null) { - canvas.clipPath(intersectPath); - } boolean result = super.drawChild(canvas, child, drawingTime); canvas.restore(); return result; @@ -212,32 +194,19 @@ public abstract class ExpandableOutlineView extends ExpandableView { invalidate(); } - @Override - public void setDistanceToTopRoundness(float distanceToTopRoundness) { - super.setDistanceToTopRoundness(distanceToTopRoundness); - if (distanceToTopRoundness != mDistanceToTopRoundness) { - mTopAmountRounded = distanceToTopRoundness >= 0; - mDistanceToTopRoundness = distanceToTopRoundness; - applyRoundness(); - } - } - protected boolean childNeedsClipping(View child) { return false; } - public boolean topAmountNeedsClipping() { - return true; - } - protected boolean isClippingNeeded() { - return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ; + // When translating the contents instead of the overall view, we need to make sure we clip + // rounded to the contents. + boolean forTranslation = getTranslation() != 0 && !mDismissUsingRowTranslationX; + return mAlwaysRoundBothCorners || mCustomOutline || forTranslation; } private void initDimens() { Resources res = getResources(); - mShouldTranslateContents = - res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe); mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius); mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline); if (!mAlwaysRoundBothCorners) { @@ -272,11 +241,6 @@ public abstract class ExpandableOutlineView extends ExpandableView { } public float getCurrentBackgroundRadiusTop() { - // If this view is top amount notification view, it should always has round corners on top. - // It will be applied with applyRoundness() - if (mTopAmountRounded) { - return mOutlineRadius; - } return getCurrentTopRoundness() * mOutlineRadius; } @@ -382,9 +346,25 @@ public abstract class ExpandableOutlineView extends ExpandableView { } } + /** + * Set the dismiss behavior of the view. + * @param usingRowTranslationX {@code true} if the view should translate using regular + * translationX, otherwise the contents will be + * translated. + */ + public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { + mDismissUsingRowTranslationX = usingRowTranslationX; + } + @Override public int getOutlineTranslation() { - return mCustomOutline ? mOutlineRect.left : (int) getTranslation(); + if (mCustomOutline) { + return mOutlineRect.left; + } + if (mDismissUsingRowTranslationX) { + return 0; + } + return (int) getTranslation(); } public void updateOutline() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 763d197847c3..8b0764b1c313 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -46,7 +46,6 @@ import java.util.List; public abstract class ExpandableView extends FrameLayout implements Dumpable { private static final String TAG = "ExpandableView"; - public static final float NO_ROUNDNESS = -1; protected OnHeightChangedListener mOnHeightChangedListener; private int mActualHeight; protected int mClipTopAmount; @@ -192,14 +191,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { } } - /** - * Set the distance to the top roundness, from where we should start clipping a value above - * or equal to 0 is the effective distance, and if a value below 0 is received, there should - * be no clipping. - */ - public void setDistanceToTopRoundness(float distanceToTopRoundness) { - } - public void setActualHeight(int actualHeight) { setActualHeight(actualHeight, true /* notifyListeners */); } 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 4b1f679b8851..754de580cd61 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 @@ -42,10 +42,8 @@ public class NotificationBackgroundView extends View { private int mActualHeight; private int mClipBottomAmount; private int mTintColor; - private float[] mCornerRadii = new float[8]; + private final float[] mCornerRadii = new float[8]; private boolean mBottomIsRounded; - private boolean mLastInSection; - private boolean mFirstInSection; private int mBackgroundTop; private boolean mBottomAmountClips = true; private boolean mExpandAnimationRunning; @@ -53,9 +51,6 @@ public class NotificationBackgroundView extends View { private int mDrawableAlpha = 255; private boolean mIsPressedAllowed; - private boolean mTopAmountRounded; - private float mDistanceToTopRoundness; - public NotificationBackgroundView(Context context, AttributeSet attrs) { super(context, attrs); mDontModifyCorners = getResources().getBoolean( @@ -90,15 +85,6 @@ public class NotificationBackgroundView extends View { left = (int) ((getWidth() - mActualWidth) / 2.0f); right = (int) (left + mActualWidth); } - if (mTopAmountRounded) { - int clipTop = (int) (mClipTopAmount - mDistanceToTopRoundness); - if (clipTop >= 0 || !mFirstInSection) { - top += clipTop; - } - if (clipTop >= 0 && !mLastInSection) { - bottom += clipTop; - } - } drawable.setBounds(left, top, right, bottom); drawable.draw(canvas); } @@ -180,14 +166,6 @@ public class NotificationBackgroundView extends View { invalidate(); } - public void setDistanceToTopRoundness(float distanceToTopRoundness) { - if (distanceToTopRoundness != mDistanceToTopRoundness) { - mTopAmountRounded = distanceToTopRoundness >= 0; - mDistanceToTopRoundness = distanceToTopRoundness; - invalidate(); - } - } - @Override public boolean hasOverlappingRendering() { @@ -246,18 +224,6 @@ public class NotificationBackgroundView extends View { } } - /** Sets whether this background belongs to the last notification in a section. */ - public void setLastInSection(boolean lastInSection) { - mLastInSection = lastInSection; - invalidate(); - } - - /** Sets whether this background belongs to the first notification in a section. */ - public void setFirstInSection(boolean firstInSection) { - mFirstInSection = firstInSection; - invalidate(); - } - private void updateBackgroundRadii() { if (mDontModifyCorners) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 0c86262d9037..6822d24947c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -75,7 +75,6 @@ public class AmbientState { private int mExpandAnimationTopChange; private ExpandableNotificationRow mExpandingNotification; private float mHideAmount; - private float mNotificationScrimTop; private boolean mAppearing; private float mPulseHeight = MAX_PULSE_HEIGHT; private float mDozeAmount = 0.0f; @@ -256,20 +255,6 @@ public class AmbientState { return mHideAmount; } - /** - * Set y position of top of notifications background scrim, relative to top of screen. - */ - public void setNotificationScrimTop(float notificationScrimTop) { - mNotificationScrimTop = notificationScrimTop; - } - - /** - * @return Y position of top of notifications background scrim, relative to top of screen. - */ - public float getNotificationScrimTop() { - return mNotificationScrimTop; - } - public void setHideSensitive(boolean hideSensitive) { mHideSensitive = hideSensitive; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index f90b4c079c50..d79c57565dcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -40,6 +40,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Outline; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.PointF; import android.graphics.Rect; import android.os.Bundle; @@ -424,13 +425,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private final Rect mBackgroundAnimationRect = new Rect(); private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>(); private int mHeadsUpInset; + + /** + * The position of the scroll boundary relative to this view. This is where the notifications + * stop scrolling and will start to clip instead. + */ + private int mQsScrollBoundaryPosition; private HeadsUpAppearanceController mHeadsUpAppearanceController; private final Rect mTmpRect = new Rect(); private DismissListener mDismissListener; private DismissAllAnimationListener mDismissAllAnimationListener; private NotificationRemoteInputManager mRemoteInputManager; private ShadeController mShadeController; - private Runnable mOnStackYChanged; + private Consumer<Boolean> mOnStackYChanged; protected boolean mClearAllEnabled; @@ -453,6 +460,38 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private NotificationStackScrollLayoutController mController; private boolean mKeyguardMediaControllorVisible; + + /** + * The clip path used to clip the view in a rounded way. + */ + private final Path mRoundedClipPath = new Path(); + + /** + * Should we use rounded rect clipping right now + */ + private boolean mShouldUseRoundedRectClipping = false; + + private int mRoundedRectClippingLeft; + private int mRoundedRectClippingTop; + private int mRoundedRectClippingBottom; + private int mRoundedRectClippingRight; + private float[] mBgCornerRadii = new float[8]; + + /** + * Whether stackY should be animated in case the view is getting shorter than the scroll + * position and this scrolling will lead to the top scroll inset getting smaller. + */ + private boolean mAnimateStackYForContentHeightChange = false; + + /** + * Are we launching a notification right now + */ + private boolean mLaunchingNotification; + + /** + * Do notifications dismiss with normal transitioning + */ + private boolean mDismissUsingRowTranslationX = true; private NotificationEntry mTopHeadsUpEntry; private long mNumHeadsUp; private NotificationStackScrollLayoutController.TouchHandler mTouchHandler; @@ -506,7 +545,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSectionsManager = notificationSectionsManager; mFeatureFlags = featureFlags; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res); + updateSplitNotificationShade(); mSectionsManager.initialize(this, LayoutInflater.from(context)); mSections = mSectionsManager.createSectionsForBuckets(); @@ -862,6 +901,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mCornerRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius); mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); + mQsScrollBoundaryPosition = res.getDimensionPixelSize( + com.android.internal.R.dimen.quick_qs_offset_height); } void updateCornerRadius() { @@ -961,6 +1002,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateFirstAndLastBackgroundViews(); updateAlgorithmLayoutMinHeight(); updateOwnTranslationZ(); + + // Once the layout has finished, we don't need to animate any scrolling clampings anymore. + mAnimateStackYForContentHeightChange = false; } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -1017,33 +1061,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void onPreDrawDuringAnimation() { mShelf.updateAppearance(); - updateClippingToTopRoundedCorner(); if (!mNeedsAnimation && !mChildrenUpdateRequested) { updateBackground(); } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - private void updateClippingToTopRoundedCorner() { - Float clipStart = mAmbientState.getNotificationScrimTop(); - Float clipEnd = clipStart + mCornerRadius; - boolean first = true; - for (int i = 0; i < getChildCount(); i++) { - ExpandableView child = (ExpandableView) getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - float start = child.getTranslationY(); - float end = start + child.getActualHeight(); - boolean clip = clipStart > start && clipStart < end - || clipEnd >= start && clipEnd <= end; - clip &= !(first && mScrollAdapter.isScrolledToTop()); - child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0) - : ExpandableView.NO_ROUNDNESS); - first = false; - } - } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateScrollStateForAddedChildren() { if (mChildrenToAddAnimated.isEmpty()) { @@ -1117,7 +1139,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void clampScrollPosition() { int scrollRange = getScrollRange(); if (scrollRange < mOwnScrollY) { - setOwnScrollY(scrollRange); + boolean animateStackY = false; + if (scrollRange < getScrollAmountToScrollBoundary() + && mAnimateStackYForContentHeightChange) { + // if the scroll boundary updates the position of the stack, + animateStackY = true; + } + setOwnScrollY(scrollRange, animateStackY); } } @@ -1146,6 +1174,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Apply expansion fraction to the y position and height of the notifications panel. */ private void updateStackPosition() { + updateStackPosition(false /* listenerNeedsAnimation */); + } + + /** + * Apply expansion fraction to the y position and height of the notifications panel. + * @param listenerNeedsAnimation does the listener need to animate? + */ + private void updateStackPosition(boolean listenerNeedsAnimation) { // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition + mAmbientState.getOverExpansion(); @@ -1153,7 +1189,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final float stackY = MathUtils.lerp(0, endTopPosition, fraction); mAmbientState.setStackY(stackY); if (mOnStackYChanged != null) { - mOnStackYChanged.run(); + mOnStackYChanged.accept(listenerNeedsAnimation); } if (mQsExpansionFraction <= 0) { final float stackEndHeight = Math.max(0f, @@ -1165,7 +1201,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - void setOnStackYChanged(Runnable onStackYChanged) { + /** + * Add a listener when the StackY changes. The argument signifies whether an animation is + * needed. + */ + void setOnStackYChanged(Consumer<Boolean> onStackYChanged) { mOnStackYChanged = onStackYChanged; } @@ -1600,7 +1640,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Resources res = getResources(); - mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res); + updateSplitNotificationShade(); mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height); float densityScale = res.getDisplayMetrics().density; mSwipeHelper.setDensityScale(densityScale); @@ -2527,8 +2567,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateScrollStateForRemovedChild(child); boolean animationGenerated = generateRemoveAnimation(child); if (animationGenerated) { - if (!mSwipedOutViews.contains(child) - || Math.abs(child.getTranslation()) != child.getWidth()) { + if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) { container.addTransientView(child, 0); child.setTransientContainer(container); } @@ -2540,6 +2579,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable focusNextViewIfFocused(child); } + /** + * Has this view been fully swiped out such that it's not visible anymore. + */ + public boolean isFullySwipedOut(ExpandableView child) { + return Math.abs(child.getTranslation()) >= Math.abs(getTotalTranslationLength(child)); + } + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void focusNextViewIfFocused(View view) { if (view instanceof ExpandableNotificationRow) { @@ -2659,17 +2705,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final int startingPosition = getPositionInLinearLayout(removedChild); final int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements; final int endPosition = startingPosition + childHeight; - if (endPosition <= mOwnScrollY) { + final int scrollBoundaryStart = getScrollAmountToScrollBoundary(); + mAnimateStackYForContentHeightChange = true; + // This is reset onLayout + if (endPosition <= mOwnScrollY - scrollBoundaryStart) { // This child is fully scrolled of the top, so we have to deduct its height from the // scrollPosition setOwnScrollY(mOwnScrollY - childHeight); - } else if (startingPosition < mOwnScrollY) { + } else if (startingPosition < mOwnScrollY - scrollBoundaryStart) { // This child is currently being scrolled into, set the scroll position to the // start of this child - setOwnScrollY(startingPosition); + setOwnScrollY(startingPosition + scrollBoundaryStart); } } + /** + * @return the amount of scrolling needed to start clipping notifications. + */ + private int getScrollAmountToScrollBoundary() { + return mTopPadding - mQsScrollBoundaryPosition; + } + @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int getIntrinsicHeight(View view) { if (view instanceof ExpandableView) { @@ -2758,7 +2814,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateAnimationState(child); updateChronometerForChild(child); if (child instanceof ExpandableNotificationRow) { - ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl); + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + row.setDismissRtl(mDismissRtl); + row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX); + } } @@ -2818,6 +2877,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void applyExpandAnimationParams(ExpandAnimationParameters params) { mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange()); + + // Disable clipping for launches + setLaunchingNotification(params != null); requestChildrenUpdate(); } @@ -2901,7 +2963,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAnimationEvents.clear(); updateBackground(); updateViewShadows(); - updateClippingToTopRoundedCorner(); } else { applyCurrentState(); } @@ -3030,7 +3091,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable removedTranslation = row.getTranslationWhenRemoved(); ignoreChildren = false; } - childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth(); + childWasSwipedOut |= isFullySwipedOut(row); } else if (child instanceof MediaHeaderView) { childWasSwipedOut = true; } @@ -3038,11 +3099,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable Rect clipBounds = child.getClipBounds(); childWasSwipedOut = clipBounds != null && clipBounds.height() == 0; - if (childWasSwipedOut && child instanceof ExpandableView) { + if (childWasSwipedOut) { // Clean up any potential transient views if the child has already been swiped // out, as we won't be animating it further (due to its height already being // clipped to 0. - ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer(); + ViewGroup transientContainer = child.getTransientContainer(); if (transientContainer != null) { transientContainer.removeTransientView(child); } @@ -3795,6 +3856,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateNotificationAnimationStates(); updateChronometers(); requestChildrenUpdate(); + updateUseRoundedRectClipping(); } } @@ -3815,6 +3877,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void onChildHeightChanged(ExpandableView view, boolean needsAnimation) { + boolean previouslyNeededAnimation = mAnimateStackYForContentHeightChange; + if (needsAnimation) { + mAnimateStackYForContentHeightChange = true; + } updateContentHeight(); updateScrollPositionOnExpandInBottom(view); clampScrollPosition(); @@ -3835,6 +3901,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestAnimationOnViewResize(row); } requestChildrenUpdate(); + mAnimateStackYForContentHeightChange = previouslyNeededAnimation; } void onChildHeightReset(ExpandableView view) { @@ -4015,7 +4082,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable setAnimationRunning(false); updateBackground(); updateViewShadows(); - updateClippingToTopRoundedCorner(); } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -4048,7 +4114,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable expandableView.setFakeShadowIntensity( diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD, previous.getOutlineAlpha(), (int) yLocation, - previous.getOutlineTranslation()); + (int) (previous.getOutlineTranslation() + previous.getTranslation())); } previous = expandableView; } @@ -4550,6 +4616,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setQsExpansionFraction(float qsExpansionFraction) { mQsExpansionFraction = qsExpansionFraction; + updateUseRoundedRectClipping(); // If notifications are scrolled, // clear out scrollY by the time we push notifications offscreen @@ -4560,13 +4627,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void setOwnScrollY(int ownScrollY) { + setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */); + } + + @ShadeViewRefactor(RefactorComponent.COORDINATOR) + private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) { if (ownScrollY != mOwnScrollY) { // We still want to call the normal scrolled changed for accessibility reasons onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY); mOwnScrollY = ownScrollY; mAmbientState.setScrollY(mOwnScrollY); updateOnScrollChange(); - updateStackPosition(); + updateStackPosition(animateStackYChangeListener); } } @@ -4632,6 +4704,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mStatusBarState = statusBarState; mAmbientState.setStatusBarState(statusBarState); updateSpeedBumpIndex(); + updateDismissBehavior(); } void onStatePostChange(boolean fromShadeLocked) { @@ -5192,6 +5265,108 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } /** + * Set rounded rect clipping bounds on this view. + */ + public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, + int bottomRadius) { + if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right + && mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top + && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) { + return; + } + mRoundedRectClippingLeft = left; + mRoundedRectClippingTop = top; + mRoundedRectClippingBottom = bottom; + mRoundedRectClippingRight = right; + mBgCornerRadii[0] = topRadius; + mBgCornerRadii[1] = topRadius; + mBgCornerRadii[2] = topRadius; + mBgCornerRadii[3] = topRadius; + mBgCornerRadii[4] = bottomRadius; + mBgCornerRadii[5] = bottomRadius; + mBgCornerRadii[6] = bottomRadius; + mBgCornerRadii[7] = bottomRadius; + mRoundedClipPath.reset(); + mRoundedClipPath.addRoundRect(left, top, right, bottom, mBgCornerRadii, Path.Direction.CW); + if (mShouldUseRoundedRectClipping) { + invalidate(); + } + } + + private void updateSplitNotificationShade() { + boolean split = shouldUseSplitNotificationShade(mFeatureFlags, getResources()); + if (split != mShouldUseSplitNotificationShade) { + mShouldUseSplitNotificationShade = split; + updateDismissBehavior(); + updateUseRoundedRectClipping(); + } + } + + private void updateDismissBehavior() { + // On the split keyguard, dismissing with clipping without a visual boundary looks odd, + // so let's use the content dismiss behavior instead. + boolean dismissUsingRowTranslationX = !mShouldUseSplitNotificationShade + || mStatusBarState != StatusBarState.KEYGUARD; + if (mDismissUsingRowTranslationX != dismissUsingRowTranslationX) { + mDismissUsingRowTranslationX = dismissUsingRowTranslationX; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) child).setDismissUsingRowTranslationX( + dismissUsingRowTranslationX); + } + } + } + } + + /** + * Set if we're launching a notification right now. + */ + private void setLaunchingNotification(boolean launching) { + if (launching == mLaunchingNotification) { + return; + } + mLaunchingNotification = launching; + updateUseRoundedRectClipping(); + } + + /** + * Should we use rounded rect clipping + */ + private void updateUseRoundedRectClipping() { + // We don't want to clip notifications when QS is expanded, because incoming heads up on + // the bottom would be clipped otherwise + boolean qsAllowsClipping = mQsExpansionFraction < 0.5f || mShouldUseSplitNotificationShade; + boolean clip = !mLaunchingNotification && mIsExpanded && qsAllowsClipping; + if (clip != mShouldUseRoundedRectClipping) { + mShouldUseRoundedRectClipping = clip; + invalidate(); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (mShouldUseRoundedRectClipping) { + // Let's clip rounded. + canvas.clipPath(mRoundedClipPath); + } + super.dispatchDraw(canvas); + } + + /** + * Calculate the total translation needed when dismissing. + */ + public float getTotalTranslationLength(View animView) { + if (!mDismissUsingRowTranslationX) { + return animView.getMeasuredWidth(); + } + float notificationWidth = animView.getMeasuredWidth(); + int containerWidth = getMeasuredWidth(); + float padding = (containerWidth - notificationWidth) / 2.0f; + return containerWidth - padding; + } + + /** * A listener that is notified when the empty space below the notifications is clicked on */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index dec98887577e..fb4f5592e97f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -384,6 +384,11 @@ public class NotificationStackScrollLayoutController { } @Override + public float getTotalTranslationLength(View animView) { + return mView.getTotalTranslationLength(animView); + } + + @Override public void onSnooze(StatusBarNotification sbn, NotificationSwipeActionHelper.SnoozeOption snoozeOption) { mStatusBar.setNotificationSnoozed(sbn, snoozeOption); @@ -822,8 +827,18 @@ public class NotificationStackScrollLayoutController { return mView.isLayoutRtl(); } + /** + * @return the left of the view. + */ public int getLeft() { - return mView.getLeft(); + return mView.getLeft(); + } + + /** + * @return the top of the view. + */ + public int getTop() { + return mView.getTop(); } public float getTranslationX() { @@ -1008,7 +1023,7 @@ public class NotificationStackScrollLayoutController { mView.setQsExpansionFraction(expansionFraction); } - public void setOnStackYChanged(Runnable onStackYChanged) { + public void setOnStackYChanged(Consumer<Boolean> onStackYChanged) { mView.setOnStackYChanged(onStackYChanged); } @@ -1440,6 +1455,14 @@ public class NotificationStackScrollLayoutController { } /** + * Set rounded rect clipping bounds on this view. + */ + public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, + int bottomRadius) { + mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius); + } + + /** * Enum for UiEvent logged from this class */ enum NotificationPanelEvent implements UiEventLogger.UiEventEnum { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index f4c4d440b063..664776975b24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -325,6 +325,11 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc } @Override + protected float getTotalTranslationLength(View animView) { + return mCallback.getTotalTranslationLength(animView); + } + + @Override public void setTranslation(View v, float translate) { if (v instanceof SwipeableView) { ((SwipeableView) v).setTranslation(translate); @@ -466,6 +471,13 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption); void onDismiss(); + + /** + * Get the total translation length where we want to swipe to when dismissing the view. By + * default this is the size of the view, but can also be larger. + * @param animView the view to ask about + */ + float getTotalTranslationLength(View animView); } static class Builder { 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 e5fd103c239f..74e8de4c9d11 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 @@ -158,7 +158,7 @@ public class StackScrollAlgorithm { AmbientState ambientState) { float drawStart = ambientState.isOnKeyguard() ? 0 : ambientState.getStackY() - ambientState.getScrollY(); - float clipStart = ambientState.getNotificationScrimTop(); + float clipStart = 0; int childCount = algorithmState.visibleChildren.size(); boolean firstHeadsUp = true; for (int i = 0; i < childCount; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 4fd2064b394d..ee12b4b2d728 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -392,7 +392,7 @@ public class StackStateAnimator { 0, () -> removeTransientView(changingView), null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { - if (Math.abs(changingView.getTranslation()) == changingView.getWidth() + if (mHostLayout.isFullySwipedOut(changingView) && changingView.getTransientContainer() != null) { changingView.getTransientContainer().removeTransientView(changingView); } 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 01d489f91de2..c4d1abc1b74c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -63,6 +63,7 @@ public class DozeParameters implements TunerService.Tunable, private final Resources mResources; private final BatteryController mBatteryController; private final FeatureFlags mFeatureFlags; + private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private final Set<Callback> mCallbacks = new HashSet<>(); @@ -78,7 +79,8 @@ public class DozeParameters implements TunerService.Tunable, BatteryController batteryController, TunerService tunerService, DumpManager dumpManager, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { mResources = resources; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mAlwaysOnPolicy = alwaysOnDisplayPolicy; @@ -89,6 +91,7 @@ public class DozeParameters implements TunerService.Tunable, mPowerManager = powerManager; mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation); mFeatureFlags = featureFlags; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; tunerService.addTunable( this, @@ -220,7 +223,8 @@ public class DozeParameters implements TunerService.Tunable, * then abruptly showing AOD. */ public boolean shouldControlUnlockedScreenOff() { - return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations(); + return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations() + && mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation(); } private boolean getBoolean(String propName, int resId) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java index 68e20705fbeb..96276f46d23d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -38,7 +38,7 @@ import java.util.LinkedList; * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open"). */ public class KeyguardIndicationTextView extends TextView { - private static final long MSG_DURATION_MILLIS = 600; + private static final long MSG_DURATION_MILLIS = 1500; private long mNextAnimationTime = 0; private boolean mAnimationsEnabled = true; private LinkedList<CharSequence> mMessages = new LinkedList<>(); 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 a6ecbfdf5391..7289ec9c81a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -837,6 +837,7 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.setOverscrollTopChangedListener( mOnOverscrollTopChangedListener); mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled); + mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged); mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener( mOnEmptySpaceClickListener); addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp); @@ -2209,15 +2210,17 @@ public class NotificationPanelViewController extends PanelViewController { mDepthController.setQsPanelExpansion(qsExpansionFraction); } - private Runnable mOnStackYChanged = () -> { + private void onStackYChanged(boolean shouldAnimate) { if (mQs != null) { + if (shouldAnimate) { + mAnimateNextNotificationBounds = true; + mNotificationBoundsAnimationDelay = 0; + } setQSClippingBounds(); } }; private void onNotificationScrolled(int newScrollPosition) { - // Since this is an overscroller, sometimes the scrollY can be temporarily negative - // (when overscrollng on the top and flinging). Let's updateQSExpansionEnabledAmbient(); } @@ -2239,14 +2242,13 @@ public class NotificationPanelViewController extends PanelViewController { * and QS state. */ private void setQSClippingBounds() { - int top = 0; - int bottom = 0; - int left = 0; - int right = 0; + int top; + int bottom; + int left; + int right; final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction()); - final boolean visible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0) - && !mShouldUseSplitNotificationShade; + final boolean qsVisible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0); if (!mShouldUseSplitNotificationShade) { if (mTransitioningToFullShadeProgress > 0.0f) { @@ -2255,7 +2257,6 @@ public class NotificationPanelViewController extends PanelViewController { top = mTransitionToFullShadeQSPosition; } else { final float notificationTop = getQSEdgePosition(); - mAmbientState.setNotificationScrimTop(notificationTop); top = (int) (isOnKeyguard() ? Math.min(qsPanelBottomY, notificationTop) : notificationTop); } @@ -2263,8 +2264,7 @@ public class NotificationPanelViewController extends PanelViewController { // notification bounds should take full screen width regardless of insets left = 0; right = getView().getRight() + mDisplayRightInset; - } else if (qsPanelBottomY > 0) { // so bounds are empty on lockscreen - mAmbientState.setNotificationScrimTop(mSplitShadeNotificationsTopPadding); + } else { top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding); bottom = mNotificationStackScrollLayoutController.getHeight(); left = mNotificationStackScrollLayoutController.getLeft(); @@ -2272,17 +2272,17 @@ public class NotificationPanelViewController extends PanelViewController { } // top should never be lower than bottom, otherwise it will be invisible. top = Math.min(top, bottom); - applyQSClippingBounds(left, top, right, bottom, visible); + applyQSClippingBounds(left, top, right, bottom, qsVisible); } private void applyQSClippingBounds(int left, int top, int right, int bottom, - boolean visible) { + boolean qsVisible) { if (!mAnimateNextNotificationBounds || mKeyguardStatusAreaClipBounds.isEmpty()) { if (mQsClippingAnimation != null) { // update the end position of the animator mQsClippingAnimationEndBounds.set(left, top, right, bottom); } else { - applyQSClippingImmediately(left, top, right, bottom, visible); + applyQSClippingImmediately(left, top, right, bottom, qsVisible); } } else { mQsClippingAnimationEndBounds.set(left, top, right, bottom); @@ -2306,7 +2306,7 @@ public class NotificationPanelViewController extends PanelViewController { int animBottom = (int) MathUtils.lerp(startBottom, mQsClippingAnimationEndBounds.bottom, fraction); applyQSClippingImmediately(animLeft, animTop, animRight, animBottom, - visible /* visible */); + qsVisible /* qsVisible */); }); mQsClippingAnimation.addListener(new AnimatorListenerAdapter() { @Override @@ -2321,7 +2321,7 @@ public class NotificationPanelViewController extends PanelViewController { } private void applyQSClippingImmediately(int left, int top, int right, int bottom, - boolean visible) { + boolean qsVisible) { // Fancy clipping for quick settings int radius = mScrimCornerRadius; int statusBarClipTop = 0; @@ -2329,19 +2329,34 @@ public class NotificationPanelViewController extends PanelViewController { if (!mShouldUseSplitNotificationShade) { // The padding on this area is large enough that we can use a cheaper clipping strategy mKeyguardStatusAreaClipBounds.set(left, top, right, bottom); - clipStatusView = visible; + clipStatusView = qsVisible; radius = (int) MathUtils.lerp(mScreenCornerRadius, mScrimCornerRadius, Math.min(top / (float) mScrimCornerRadius, 1f)); statusBarClipTop = top - mKeyguardStatusBar.getTop(); } if (mQs != null) { - mQs.setFancyClipping(top, bottom, radius, visible); + mQs.setFancyClipping(top, bottom, radius, qsVisible + && !mShouldUseSplitNotificationShade); } mKeyguardStatusViewController.setClipBounds( clipStatusView ? mKeyguardStatusAreaClipBounds : null); - mScrimController.setNotificationsBounds(left, top, right, bottom); + if (!qsVisible && mShouldUseSplitNotificationShade) { + // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to + // be visible, otherwise you can see the bounds once swiping up to see bouncer + mScrimController.setNotificationsBounds(0, 0, 0, 0); + } else { + mScrimController.setNotificationsBounds(left, top, right, bottom); + } + mScrimController.setScrimCornerRadius(radius); mKeyguardStatusBar.setTopClipping(statusBarClipTop); + int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft(); + int nsslRight = right - mNotificationStackScrollLayoutController.getLeft(); + int nsslTop = top - mNotificationStackScrollLayoutController.getTop(); + int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop(); + int bottomRadius = mShouldUseSplitNotificationShade ? radius : 0; + mNotificationStackScrollLayoutController.setRoundedClippingBounds( + nsslLeft, nsslTop, nsslRight, nsslBottom, radius, bottomRadius); } private float getQSEdgePosition() { @@ -3334,7 +3349,6 @@ public class NotificationPanelViewController extends PanelViewController { // The expandedHeight is always the full panel Height when bypassing expandedHeight = getMaxPanelHeightNonBypass(); } - mNotificationStackScrollLayoutController.setOnStackYChanged(mOnStackYChanged); mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight); updateKeyguardBottomAreaAlpha(); updateBigClockAlpha(); @@ -4233,7 +4247,7 @@ public class NotificationPanelViewController extends PanelViewController { int oldState = mBarState; boolean keyguardShowing = statusBarState == KEYGUARD; - if (mUnlockedScreenOffAnimationController.shouldPlayScreenOffAnimation() + if (mDozeParameters.shouldControlUnlockedScreenOff() && oldState == StatusBarState.SHADE && statusBarState == KEYGUARD) { // This means we're doing the screen off animation - position the keyguard status diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 52f9aca82783..c95879650049 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -26,11 +26,9 @@ import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENAB import android.app.IActivityManager; import android.content.Context; import android.content.pm.ActivityInfo; -import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.Binder; import android.os.RemoteException; -import android.os.SystemProperties; import android.os.Trace; import android.util.Log; import android.view.Display; @@ -53,6 +51,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.google.android.collect.Lists; @@ -108,12 +107,14 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW StatusBarStateController statusBarStateController, ConfigurationController configurationController, KeyguardViewMediator keyguardViewMediator, - KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor, - DumpManager dumpManager) { + KeyguardBypassController keyguardBypassController, + SysuiColorExtractor colorExtractor, + DumpManager dumpManager, + KeyguardStateController keyguardStateController) { mContext = context; mWindowManager = windowManager; mActivityManager = activityManager; - mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation(); + mKeyguardScreenRotation = keyguardStateController.isKeyguardScreenRotationAllowed(); mDozeParameters = dozeParameters; mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze(); mLpChanged = new LayoutParams(); @@ -173,12 +174,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } } - private boolean shouldEnableKeyguardScreenRotation() { - Resources res = mContext.getResources(); - return SystemProperties.getBoolean("lockscreen.rot_override", false) - || res.getBoolean(R.bool.config_enableLockScreenRotation); - } - /** * Adds the notification shade view to the window manager. */ 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 bf7bc0ea633b..f1296721dda8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -21,7 +21,6 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; -import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -46,8 +45,6 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import static com.android.wm.shell.bubbles.BubbleController.TASKBAR_CHANGED_BROADCAST; -import android.animation.ValueAnimator; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; @@ -121,6 +118,7 @@ import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.DateTimeView; +import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; @@ -402,8 +400,6 @@ public class StatusBar extends SystemUI implements DemoMode, private LightRevealScrim mLightRevealScrim; private WiredChargingRippleController mChargingRippleAnimationController; private PowerButtonReveal mPowerButtonReveal; - private CircleReveal mCircleReveal; - private ValueAnimator mCircleRevealAnimator = ValueAnimator.ofFloat(0f, 1f); private final Object mQueueLock = new Object(); @@ -2778,9 +2774,14 @@ public class StatusBar extends SystemUI implements DemoMode, + String.valueOf(CameraIntents.getOverrideCameraPackage(mContext))); } - public static void dumpBarTransitions(PrintWriter pw, String var, BarTransitions transitions) { + public static void dumpBarTransitions( + PrintWriter pw, String var, @Nullable BarTransitions transitions) { pw.print(" "); pw.print(var); pw.print(".BarTransitions.mMode="); - pw.println(BarTransitions.modeToString(transitions.getMode())); + if (transitions != null) { + pw.println(BarTransitions.modeToString(transitions.getMode())); + } else { + pw.println("Unknown"); + } } public void createAndAddWindows(@Nullable RegisterStatusBarResult result) { @@ -2803,11 +2804,11 @@ public class StatusBar extends SystemUI implements DemoMode, return mDisplayMetrics.density; } - float getDisplayWidth() { + public float getDisplayWidth() { return mDisplayMetrics.widthPixels; } - float getDisplayHeight() { + public float getDisplayHeight() { return mDisplayMetrics.heightPixels; } @@ -2841,9 +2842,11 @@ public class StatusBar extends SystemUI implements DemoMode, mActivityIntentHelper.wouldLaunchResolverActivity(intent, mLockscreenUserManager.getCurrentUserId()); + boolean animate = + animationController != null && !willLaunchResolverActivity && shouldAnimateLaunch( + true /* isActivityIntent */); ActivityLaunchAnimator.Controller animController = - !willLaunchResolverActivity && shouldAnimateLaunch(true /* isActivityIntent */) - ? wrapAnimationController(animationController, dismissShade) : null; + animate ? wrapAnimationController(animationController, dismissShade) : null; // If we animate, we will dismiss the shade only once the animation is done. This is taken // care of by the StatusBarLaunchAnimationController. @@ -2857,7 +2860,7 @@ public class StatusBar extends SystemUI implements DemoMode, int[] result = new int[]{ActivityManager.START_CANCELED}; mActivityLaunchAnimator.startIntentWithAnimation(animController, - true /* animate */, intent.getPackage(), (adapter) -> { + animate, intent.getPackage(), (adapter) -> { ActivityOptions options = new ActivityOptions( getActivityOptions(mDisplayId, adapter)); options.setDisallowEnterPictureInPictureWhileLaunching( @@ -2907,16 +2910,12 @@ public class StatusBar extends SystemUI implements DemoMode, } }; executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly, - willLaunchResolverActivity, true /* deferred */); + willLaunchResolverActivity, true /* deferred */, animate); } @Nullable private ActivityLaunchAnimator.Controller wrapAnimationController( - @Nullable ActivityLaunchAnimator.Controller animationController, boolean dismissShade) { - if (animationController == null) { - return null; - } - + ActivityLaunchAnimator.Controller animationController, boolean dismissShade) { View rootView = animationController.getLaunchContainer().getRootView(); if (rootView == mSuperStatusBarViewFactory.getStatusBarWindowView()) { // We are animating a view in the status bar. We have to make sure that the status bar @@ -2959,34 +2958,56 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean dismissShade, final boolean afterKeyguardGone, final boolean deferred) { - dismissKeyguardThenExecute(() -> { - if (runnable != null) { - if (mStatusBarKeyguardViewManager.isShowing() - && mStatusBarKeyguardViewManager.isOccluded()) { - mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); - } else { - AsyncTask.execute(runnable); - } - } - if (dismissShade) { - if (mExpandedVisible && !mBouncerShowing) { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */, true /* delayed*/); - } else { + executeRunnableDismissingKeyguard(runnable, cancelAction, dismissShade, afterKeyguardGone, + deferred, false /* willAnimateOnKeyguard */); + } - // Do it after DismissAction has been processed to conserve the needed ordering. - mHandler.post(mShadeController::runPostCollapseRunnables); + public void executeRunnableDismissingKeyguard(final Runnable runnable, + final Runnable cancelAction, + final boolean dismissShade, + final boolean afterKeyguardGone, + final boolean deferred, + final boolean willAnimateOnKeyguard) { + OnDismissAction onDismissAction = new OnDismissAction() { + @Override + public boolean onDismiss() { + if (runnable != null) { + if (mStatusBarKeyguardViewManager.isShowing() + && mStatusBarKeyguardViewManager.isOccluded()) { + mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); + } else { + AsyncTask.execute(runnable); + } } - } else if (isInLaunchTransition() - && mNotificationPanelViewController.isLaunchTransitionFinished()) { + if (dismissShade) { + if (mExpandedVisible && !mBouncerShowing) { + mShadeController.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, + true /* force */, true /* delayed*/); + } else { + + // Do it after DismissAction has been processed to conserve the needed + // ordering. + mHandler.post(mShadeController::runPostCollapseRunnables); + } + } else if (StatusBar.this.isInLaunchTransition() + && mNotificationPanelViewController.isLaunchTransitionFinished()) { + + // We are not dismissing the shade, but the launch transition is already + // finished, + // so nobody will call readyForKeyguardDone anymore. Post it such that + // keyguardDonePending gets called first. + mHandler.post(mStatusBarKeyguardViewManager::readyForKeyguardDone); + } + return deferred; + } - // We are not dismissing the shade, but the launch transition is already finished, - // so nobody will call readyForKeyguardDone anymore. Post it such that - // keyguardDonePending gets called first. - mHandler.post(mStatusBarKeyguardViewManager::readyForKeyguardDone); + @Override + public boolean willRunAnimationOnKeyguard() { + return willAnimateOnKeyguard; } - return deferred; - }, cancelAction, afterKeyguardGone); + }; + dismissKeyguardThenExecute(onDismissAction, cancelAction, afterKeyguardGone); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -3408,6 +3429,10 @@ public class StatusBar extends SystemUI implements DemoMode, } boolean updateIsKeyguard() { + return updateIsKeyguard(false /* force */); + } + + boolean updateIsKeyguard(boolean force) { boolean wakeAndUnlocking = mBiometricUnlockController.getMode() == BiometricUnlockController.MODE_WAKE_AND_UNLOCK; @@ -3431,7 +3456,7 @@ public class StatusBar extends SystemUI implements DemoMode, showKeyguardImpl(); } } else { - return hideKeyguardImpl(); + return hideKeyguardImpl(force); } return false; } @@ -3513,9 +3538,6 @@ public class StatusBar extends SystemUI implements DemoMode, public void fadeKeyguardWhilePulsing() { mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING, ()-> { - if (shouldShowCircleReveal()) { - startCircleReveal(); - } hideKeyguard(); mStatusBarKeyguardViewManager.onKeyguardFadedAway(); }).start(); @@ -3560,11 +3582,11 @@ public class StatusBar extends SystemUI implements DemoMode, /** * @return true if we would like to stay in the shade, false if it should go away entirely */ - public boolean hideKeyguardImpl() { + public boolean hideKeyguardImpl(boolean force) { mIsKeyguard = false; Trace.beginSection("StatusBar#hideKeyguard"); boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide(); - if (!(mStatusBarStateController.setState(StatusBarState.SHADE))) { + if (!(mStatusBarStateController.setState(StatusBarState.SHADE, force))) { //TODO: StatusBarStateController should probably know about hiding the keyguard and // notify listeners. @@ -3856,7 +3878,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onDozeAmountChanged(float linear, float eased) { if (mFeatureFlags.useNewLockscreenAnimations() - && !mCircleRevealAnimator.isRunning()) { + && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { mLightRevealScrim.setRevealAmount(1f - linear); } } @@ -3879,7 +3901,7 @@ public class StatusBar extends SystemUI implements DemoMode, || (!isDozing && mWakefulnessLifecycle.getLastWakeReason() == PowerManager.WAKE_REASON_POWER_BUTTON)) { mLightRevealScrim.setRevealEffect(mPowerButtonReveal); - } else if (!mCircleRevealAnimator.isRunning()) { + } else if (!(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE); } @@ -3891,36 +3913,8 @@ public class StatusBar extends SystemUI implements DemoMode, Trace.endSection(); } - /** - * Update the parameters for the dozing circle reveal that animates when the user authenticates - * from AOD using the fingerprint sensor. - */ - public void updateCircleReveal() { - final PointF fpLocation = mAuthRippleController.getFingerprintSensorLocation(); - if (fpLocation != null) { - mCircleReveal = - new CircleReveal( - fpLocation.x, - fpLocation.y, - 0, - Math.max(Math.max(fpLocation.x, getDisplayWidth() - fpLocation.x), - Math.max(fpLocation.y, getDisplayHeight() - fpLocation.y))); - } - } - - private void startCircleReveal() { - mLightRevealScrim.setRevealEffect(mCircleReveal); - mCircleRevealAnimator.cancel(); - mCircleRevealAnimator.addUpdateListener(animation -> - mLightRevealScrim.setRevealAmount( - (float) mCircleRevealAnimator.getAnimatedValue())); - mCircleRevealAnimator.setDuration(900); - mCircleRevealAnimator.start(); - } - - private boolean shouldShowCircleReveal() { - return mCircleReveal != null && !mCircleRevealAnimator.isRunning() - && mBiometricUnlockController.getBiometricType() == FINGERPRINT; + public LightRevealScrim getLightRevealScrim() { + return mLightRevealScrim; } private void updateKeyguardState() { @@ -4060,7 +4054,7 @@ public class StatusBar extends SystemUI implements DemoMode, // The screen off animation uses our LightRevealScrim - we need to be expanded for it to // be visible. - if (mUnlockedScreenOffAnimationController.shouldPlayScreenOffAnimation()) { + if (mDozeParameters.shouldControlUnlockedScreenOff()) { makeExpandedVisible(true); } @@ -4610,28 +4604,37 @@ public class StatusBar extends SystemUI implements DemoMode, * * @param action The action to execute after dismissing the keyguard. * @param collapsePanel Whether we should collapse the panel after dismissing the keyguard. - * @param deferKeyguardDismiss Whether we should defer the keyguard actual dismissal, for - * instance to run animations on the keyguard before hiding it. + * @param willAnimateOnKeyguard Whether {@param action} will run an animation on the keyguard if + * we are locked. */ private void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone, - boolean collapsePanel, boolean deferKeyguardDismiss) { + boolean collapsePanel, boolean willAnimateOnKeyguard) { if (!mDeviceProvisionedController.isDeviceProvisioned()) return; - dismissKeyguardThenExecute(() -> { - new Thread(() -> { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - action.run(); - }).start(); + OnDismissAction onDismissAction = new OnDismissAction() { + @Override + public boolean onDismiss() { + new Thread(() -> { + try { + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { + } + action.run(); + }).start(); - return collapsePanel ? mShadeController.collapsePanel() : deferKeyguardDismiss; - }, afterKeyguardGone); + return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard; + } + + @Override + public boolean willRunAnimationOnKeyguard() { + return willAnimateOnKeyguard; + } + }; + dismissKeyguardThenExecute(onDismissAction, afterKeyguardGone); } @Override @@ -4675,7 +4678,6 @@ public class StatusBar extends SystemUI implements DemoMode, // the animation on the keyguard). The animation will take care of (instantly) collapsing // the shade and hiding the keyguard once it is done. boolean collapse = !animate; - boolean deferKeyguardDismiss = animate; executeActionDismissingKeyguard(() -> { try { // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the @@ -4704,7 +4706,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (intentSentUiThreadCallback != null) { postOnUiThread(intentSentUiThreadCallback); } - }, willLaunchResolverActivity, collapse, deferKeyguardDismiss); + }, willLaunchResolverActivity, collapse, animate); } private void postOnUiThread(Runnable runnable) { 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 c7efcb2923e7..e8463992ed13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -192,6 +192,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private OnDismissAction mAfterKeyguardGoneAction; private Runnable mKeyguardGoneCancelAction; + private boolean mDismissActionWillAnimateOnKeyguard; private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>(); // Dismiss action to be launched when we stop dozing or the keyguard is gone. @@ -447,6 +448,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mAfterKeyguardGoneAction = r; mKeyguardGoneCancelAction = cancelAction; + mDismissActionWillAnimateOnKeyguard = r != null && r.willRunAnimationOnKeyguard(); // If there is an an alternate auth interceptor (like the UDFPS), show that one instead // of the bouncer. @@ -625,9 +627,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBouncer.startPreHideAnimation(finishRunnable); mStatusBar.onBouncerPreHideAnimation(); - // startPreHideAnimation() will change the visibility of the bouncer, so we have to - // make sure to update its state. - updateStates(); + // We update the state (which will show the keyguard) only if an animation will run on + // the keyguard. If there is no animation, we wait before updating the state so that we + // go directly from bouncer to launcher/app. + if (mDismissActionWillAnimateOnKeyguard) { + updateStates(); + } } else if (finishRunnable != null) { finishRunnable.run(); } @@ -798,6 +803,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mAfterKeyguardGoneAction = null; } mKeyguardGoneCancelAction = null; + mDismissActionWillAnimateOnKeyguard = false; for (int i = 0; i < mAfterKeyguardGoneRunnables.size(); i++) { mAfterKeyguardGoneRunnables.get(i).run(); } @@ -866,6 +872,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return; // allow bouncer to trigger saved actions } mAfterKeyguardGoneAction = null; + mDismissActionWillAnimateOnKeyguard = false; if (mKeyguardGoneCancelAction != null) { mKeyguardGoneCancelAction.run(); mKeyguardGoneCancelAction = null; @@ -892,6 +899,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb }; protected void updateStates() { + if (mContainer == null ) { + return; + } int vis = mContainer.getSystemUiVisibility(); boolean showing = mShowing; boolean occluded = mOccluded; 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 d93b76646d58..98b9cc9bc716 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -41,6 +41,7 @@ import android.text.TextUtils; import android.util.EventLog; import android.view.View; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.widget.LockPatternUtils; @@ -260,10 +261,19 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(), mLockscreenUserManager.getCurrentUserId()); - ActivityStarter.OnDismissAction postKeyguardAction = - () -> handleNotificationClickAfterKeyguardDismissed( + ActivityStarter.OnDismissAction postKeyguardAction = new ActivityStarter.OnDismissAction() { + @Override + public boolean onDismiss() { + return handleNotificationClickAfterKeyguardDismissed( entry, row, controller, intent, isActivityIntent, animate, showOverLockscreen); + } + + @Override + public boolean willRunAnimationOnKeyguard() { + return animate; + } + }; if (showOverLockscreen) { mIsCollapsingToShowActivityOverLockscreen = true; postKeyguardAction.onDismiss(); @@ -453,53 +463,76 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit public void startNotificationGutsIntent(final Intent intent, final int appUid, ExpandableNotificationRow row) { boolean animate = mStatusBar.shouldAnimateLaunch(true /* isActivityIntent */); - mActivityStarter.dismissKeyguardThenExecute(() -> { - AsyncTask.execute(() -> { - ActivityLaunchAnimator.Controller animationController = - new StatusBarLaunchAnimatorController( - mNotificationAnimationProvider.getAnimatorController(row), - mStatusBar, true /* isActivityIntent */); - - mActivityLaunchAnimator.startIntentWithAnimation( - animationController, animate, intent.getPackage(), - (adapter) -> TaskStackBuilder.create(mContext) - .addNextIntentWithParentStack(intent) - .startActivities(getActivityOptions( - mStatusBar.getDisplayId(), - adapter), - new UserHandle(UserHandle.getUserId(appUid)))); - }); - return true; - }, null, false /* afterKeyguardGone */); + ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() { + @Override + public boolean onDismiss() { + AsyncTask.execute(() -> { + ActivityLaunchAnimator.Controller animationController = + new StatusBarLaunchAnimatorController( + mNotificationAnimationProvider.getAnimatorController(row), + mStatusBar, true /* isActivityIntent */); + + mActivityLaunchAnimator.startIntentWithAnimation( + animationController, animate, intent.getPackage(), + (adapter) -> TaskStackBuilder.create(mContext) + .addNextIntentWithParentStack(intent) + .startActivities(getActivityOptions( + mStatusBar.getDisplayId(), + adapter), + new UserHandle(UserHandle.getUserId(appUid)))); + }); + return true; + } + + @Override + public boolean willRunAnimationOnKeyguard() { + return animate; + } + }; + mActivityStarter.dismissKeyguardThenExecute(onDismissAction, null, + false /* afterKeyguardGone */); } @Override public void startHistoryIntent(View view, boolean showHistory) { boolean animate = mStatusBar.shouldAnimateLaunch(true /* isActivityIntent */); - mActivityStarter.dismissKeyguardThenExecute(() -> { - AsyncTask.execute(() -> { - Intent intent = showHistory ? new Intent( - Settings.ACTION_NOTIFICATION_HISTORY) : new Intent( - Settings.ACTION_NOTIFICATION_SETTINGS); - TaskStackBuilder tsb = TaskStackBuilder.create(mContext) - .addNextIntent(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS)); - if (showHistory) { - tsb.addNextIntent(intent); - } + ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() { + @Override + public boolean onDismiss() { + AsyncTask.execute(() -> { + Intent intent = showHistory ? new Intent( + Settings.ACTION_NOTIFICATION_HISTORY) : new Intent( + Settings.ACTION_NOTIFICATION_SETTINGS); + TaskStackBuilder tsb = TaskStackBuilder.create(mContext) + .addNextIntent(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS)); + if (showHistory) { + tsb.addNextIntent(intent); + } - ActivityLaunchAnimator.Controller animationController = - new StatusBarLaunchAnimatorController( - ActivityLaunchAnimator.Controller.fromView(view), mStatusBar, - true /* isActivityIntent */); + ActivityLaunchAnimator.Controller viewController = + ActivityLaunchAnimator.Controller.fromView(view, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON + ); + ActivityLaunchAnimator.Controller animationController = + new StatusBarLaunchAnimatorController(viewController, mStatusBar, + true /* isActivityIntent */); + + mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate, + intent.getPackage(), + (adapter) -> tsb.startActivities( + getActivityOptions(mStatusBar.getDisplayId(), adapter), + UserHandle.CURRENT)); + }); + return true; + } - mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate, - intent.getPackage(), - (adapter) -> tsb.startActivities( - getActivityOptions(mStatusBar.getDisplayId(), adapter), - UserHandle.CURRENT)); - }); - return true; - }, null, false /* afterKeyguardGone */); + @Override + public boolean willRunAnimationOnKeyguard() { + return animate; + } + }; + mActivityStarter.dismissKeyguardThenExecute(onDismissAction, null, + false /* afterKeyguardGone */); } private void removeHunAfterClick(ExpandableNotificationRow row) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index e135cc51a7bc..52bf2d577776 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -3,6 +3,8 @@ package com.android.systemui.statusbar.phone import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator +import android.content.Context +import android.content.res.Configuration import android.os.Handler import android.view.View import com.android.systemui.animation.Interpolators @@ -16,6 +18,7 @@ import com.android.systemui.statusbar.notification.AnimatableProperty import com.android.systemui.statusbar.notification.PropertyAnimator import com.android.systemui.statusbar.notification.stack.AnimationProperties import com.android.systemui.statusbar.notification.stack.StackStateAnimator +import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject /** @@ -38,10 +41,11 @@ private const val LIGHT_REVEAL_ANIMATION_DURATION = 750L */ @SysUISingleton class UnlockedScreenOffAnimationController @Inject constructor( + private val context: Context, private val wakefulnessLifecycle: WakefulnessLifecycle, private val statusBarStateControllerImpl: StatusBarStateControllerImpl, private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>, - private val dozeParameters: DozeParameters + private val keyguardStateController: KeyguardStateController ) : WakefulnessLifecycle.Observer { private val handler = Handler() @@ -131,13 +135,18 @@ class UnlockedScreenOffAnimationController @Inject constructor( lightRevealAnimationPlaying = false aodUiAnimationPlaying = false - // Make sure the status bar is in the correct keyguard state, since we might have left it in - // the KEYGUARD state if this wakeup cancelled the screen off animation. - statusBar.updateIsKeyguard() + // Make sure the status bar is in the correct keyguard state, forcing it if necessary. This + // is required if the screen off animation is cancelled, since it might be incorrectly left + // in the KEYGUARD or SHADE states depending on when it was cancelled and whether 'lock + // instantly' is enabled. We need to force it so that the state is set even if we're going + // from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have changed parts of the UI + // (such as showing AOD in the shade) without actually changing the StatusBarState. This + // ensures that the UI definitely reflects the desired state. + statusBar.updateIsKeyguard(true /* force */) } override fun onStartedGoingToSleep() { - if (shouldPlayScreenOffAnimation()) { + if (shouldPlayUnlockedScreenOffAnimation()) { lightRevealAnimationPlaying = true lightRevealAnimator.start() @@ -151,13 +160,31 @@ class UnlockedScreenOffAnimationController @Inject constructor( } /** - * Whether we should play the screen off animation when the phone starts going to sleep. We can - * do that if dozeParameters says we can control the unlocked screen off animation and we are in - * the SHADE state. If we're in KEYGUARD or SHADE_LOCKED, the regular + * Whether we want to play the screen off animation when the phone starts going to sleep, based + * on the current state of the device. */ - fun shouldPlayScreenOffAnimation(): Boolean { - return dozeParameters.shouldControlUnlockedScreenOff() && - statusBarStateControllerImpl.state == StatusBarState.SHADE + fun shouldPlayUnlockedScreenOffAnimation(): Boolean { + // We only play the unlocked screen off animation if we are... unlocked. + if (statusBarStateControllerImpl.state != StatusBarState.SHADE) { + return false + } + + // We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's + // already expanded and showing notifications/QS, the animation looks really messy. For now, + // disable it if the notification panel is expanded. + if (statusBar.notificationPanelViewController.isFullyExpanded) { + return false + } + + // If we're not allowed to rotate the keyguard, then only do the screen off animation if + // we're in portrait. Otherwise, AOD will animate in sideways, which looks weird. + if (!keyguardStateController.isKeyguardScreenRotationAllowed && + context.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) { + return false + } + + // Otherwise, good to go. + return true } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index ef7fac311799..b295f6659f81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -25,6 +25,7 @@ import android.content.Intent import android.util.Log import android.view.View import android.widget.Chronometer +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.dagger.SysUISingleton @@ -179,7 +180,10 @@ class OngoingCallController @Inject constructor( logger.logChipClicked() activityStarter.postStartActivityDismissingKeyguard( currentCallNotificationInfo.intent, 0, - ActivityLaunchAnimator.Controller.fromView(backgroundView)) + ActivityLaunchAnimator.Controller.fromView( + backgroundView, + InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP) + ) } setUpUidObserver(currentCallNotificationInfo) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index af7bf9500bf3..fcfc9670b8b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -57,6 +57,11 @@ public interface KeyguardStateController extends CallbackController<Callback> { boolean canPerformSmartSpaceTransition(); /** + * Whether the keyguard is allowed to rotate, or needs to be locked to the default orientation. + */ + boolean isKeyguardScreenRotationAllowed(); + + /** * If the device has PIN/pattern/password or a lock screen at all. */ boolean isMethodSecure(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 0945a3f884d6..64750bd803d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.biometrics.BiometricSourceType; import android.os.Build; +import android.os.SystemProperties; import android.os.Trace; import androidx.annotation.VisibleForTesting; @@ -31,6 +32,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dumpable; +import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; @@ -50,6 +52,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth"; private final ArrayList<Callback> mCallbacks = new ArrayList<>(); + private final Context mContext; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final LockPatternUtils mLockPatternUtils; private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = @@ -100,6 +103,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum public KeyguardStateControllerImpl(Context context, KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils, SmartspaceTransitionController smartspaceTransitionController) { + mContext = context; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); @@ -243,6 +247,12 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override + public boolean isKeyguardScreenRotationAllowed() { + return SystemProperties.getBoolean("lockscreen.rot_override", false) + || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation); + } + + @Override public boolean isFaceAuthEnabled() { return mFaceAuthEnabled; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 59224db86bec..06974b356914 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -806,6 +806,9 @@ public class NetworkControllerImpl extends BroadcastReceiver for (int i = 0; i < mMobileSignalControllers.size(); i++) { MobileSignalController controller = mMobileSignalControllers.valueAt(i); controller.setConfiguration(mConfig); + if (mProviderModel) { + controller.refreshCallIndicator(mCallbackHandler); + } } refreshLocale(); } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 26f4a2b9d2b2..d97815f92964 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -68,7 +68,8 @@ public class TunerServiceImpl extends TunerService { private static final String[] RESET_EXCEPTION_LIST = new String[] { QSTileHost.TILES_SETTING, Settings.Secure.DOZE_ALWAYS_ON, - Settings.Secure.MEDIA_CONTROLS_RESUME + Settings.Secure.MEDIA_CONTROLS_RESUME, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION }; private final Observer mObserver = new Observer(); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 92ef8504d123..3af82f91af1c 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -34,6 +34,7 @@ import android.graphics.drawable.Drawable; import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.view.KeyEvent; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; @@ -57,6 +58,7 @@ import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.nano.WmShellTraceProto; import com.android.wm.shell.onehanded.OneHanded; +import com.android.wm.shell.onehanded.OneHandedEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.onehanded.OneHandedUiEventLogger; import com.android.wm.shell.pip.Pip; @@ -253,6 +255,15 @@ public final class WMShell extends SystemUI } }); + oneHanded.registerEventCallback(new OneHandedEventCallback() { + @Override + public void notifyExpandNotification() { + mSysUiMainExecutor.execute( + () -> mCommandQueue.handleSystemKey( + KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN)); + } + }); + mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() { @Override public void onKeyguardBouncerChanged(boolean bouncer) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java index 485df21656d3..5617f1b6316b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java @@ -59,7 +59,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.util.MathUtils; import android.view.Choreographer; import android.view.MotionEvent; import android.view.View; @@ -489,20 +488,20 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { @Test public void onRotationChanged_buttonIsShowing_expectedYPosition() { final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - final int oldWindowHeight = windowBounds.height(); mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds); final float windowHeightFraction = - (float) mWindowManager.getLayoutParamsFromAttachedView().y / oldWindowHeight; + (float) (mWindowManager.getLayoutParamsFromAttachedView().y + - oldDraggableBounds.top) / oldDraggableBounds.height(); - // The window bounds are changed due to the rotation change. + // The window bounds and the draggable bounds are changed due to the rotation change. final Rect newWindowBounds = new Rect(0, 0, windowBounds.height(), windowBounds.width()); mWindowManager.setWindowBounds(newWindowBounds); mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION); - int expectedY = (int) (newWindowBounds.height() * windowHeightFraction); - expectedY = MathUtils.constrain(expectedY, - mMagnificationModeSwitch.mDraggableWindowBounds.top, - mMagnificationModeSwitch.mDraggableWindowBounds.bottom); + int expectedY = (int) (windowHeightFraction + * mMagnificationModeSwitch.mDraggableWindowBounds.height()) + + mMagnificationModeSwitch.mDraggableWindowBounds.top; assertEquals( "The Y position does not keep the same height ratio after the rotation changed.", expectedY, mWindowManager.getLayoutParamsFromAttachedView().y); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 240fdf3a4e17..d87a26b096fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -25,6 +25,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.ConfigurationController @@ -53,6 +54,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var authController: AuthController @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var bypassController: KeyguardBypassController + @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Before fun setUp() { @@ -66,6 +68,7 @@ class AuthRippleControllerTest : SysuiTestCase() { commandRegistry, notificationShadeWindowController, bypassController, + biometricUnlockController, rippleView ) controller.init() @@ -90,7 +93,7 @@ class AuthRippleControllerTest : SysuiTestCase() { // THEN update sensor location and show ripple verify(rippleView).setSensorLocation(fpsLocation) - verify(rippleView).startRipple(any()) + verify(rippleView).startRipple(any(), any()) } @Test @@ -111,7 +114,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -132,7 +135,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -156,7 +159,7 @@ class AuthRippleControllerTest : SysuiTestCase() { // THEN show ripple verify(rippleView).setSensorLocation(faceLocation) - verify(rippleView).startRipple(any()) + verify(rippleView).startRipple(any(), any()) } @Test @@ -176,7 +179,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -191,7 +194,7 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FACE /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -206,7 +209,7 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test 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 c0b45c6d5c96..a11b9cf357a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -16,7 +16,10 @@ package com.android.systemui.doze; +import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; + import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -161,7 +164,7 @@ public class DozeTriggersTest extends SysuiTestCase { clearInvocations(mSensors); mTriggers.transitionTo(DozeMachine.State.DOZE_PULSING, DozeMachine.State.DOZE_PULSE_DONE); - mTriggers.transitionTo(DozeMachine.State.DOZE_PULSE_DONE, DozeMachine.State.DOZE_AOD); + mTriggers.transitionTo(DozeMachine.State.DOZE_PULSE_DONE, DOZE_AOD); waitForSensorManager(); verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor)); } @@ -207,7 +210,7 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers.onSensor(DozeLog.REASON_SENSOR_QUICK_PICKUP, 100, 100, null); // THEN device goes into aod (shows clock with black background) - verify(mMachine).requestState(DozeMachine.State.DOZE_AOD); + verify(mMachine).requestState(DOZE_AOD); // THEN a log is taken that quick pick up was triggered verify(mUiEventLogger).log(DozingUpdateUiEvent.DOZING_UPDATE_QUICK_PICKUP); @@ -218,7 +221,7 @@ public class DozeTriggersTest extends SysuiTestCase { // GIVEN quick pickup is triggered when device is in DOZE when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE); mTriggers.onSensor(DozeLog.REASON_SENSOR_QUICK_PICKUP, 100, 100, null); - verify(mMachine).requestState(DozeMachine.State.DOZE_AOD); + verify(mMachine).requestState(DOZE_AOD); verify(mMachine, never()).requestState(DozeMachine.State.DOZE); // WHEN next executable is run @@ -234,6 +237,8 @@ public class DozeTriggersTest extends SysuiTestCase { @Test public void testOnSensor_Fingerprint() { + // GIVEN dozing state + when(mMachine.getState()).thenReturn(DOZE_AOD); final int screenX = 100; final int screenY = 100; final float misc = -1; @@ -241,8 +246,20 @@ public class DozeTriggersTest extends SysuiTestCase { final float major = 3f; final int reason = DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS; float[] rawValues = new float[]{screenX, screenY, misc, major, minor}; + + // WHEN longpress gesture is triggered mTriggers.onSensor(reason, screenX, screenY, rawValues); + + // THEN + // * don't immediately send interrupt + // * immediately extend pulse + verify(mAuthController, never()).onAodInterrupt(anyInt(), anyInt(), anyFloat(), anyFloat()); verify(mHost).extendPulse(reason); + + // WHEN display state changes to ON + mTriggers.onScreenState(Display.STATE_ON); + + // THEN send interrupt verify(mAuthController).onAodInterrupt(eq(screenX), eq(screenY), eq(major), eq(minor)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java index afe5c0b2edbd..1d34aac3b1cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java @@ -43,6 +43,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.wakelock.WakeLockFake; @@ -77,6 +78,8 @@ public class DozeUiTest extends SysuiTestCase { private DozeUi mDozeUi; @Mock private StatusBarStateController mStatusBarStateController; + @Mock + private ConfigurationController mConfigurationController; @Before public void setUp() throws Exception { @@ -89,7 +92,7 @@ public class DozeUiTest extends SysuiTestCase { mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler, mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService, - () -> mStatusBarStateController); + () -> mStatusBarStateController, mConfigurationController); mDozeUi.setDozeMachine(mMachine); } @@ -146,7 +149,7 @@ public class DozeUiTest extends SysuiTestCase { when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true); mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler, mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService, - () -> mStatusBarStateController); + () -> mStatusBarStateController, mConfigurationController); mDozeUi.setDozeMachine(mMachine); // Never animate if display doesn't support it. diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 83e7b17eb746..adc8ffc1d633 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Color; import android.media.AudioManager; @@ -38,8 +39,9 @@ import android.os.UserManager; import android.service.dreams.IDreamManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.GestureDetector; import android.view.IWindowManager; -import android.view.View; +import android.view.MotionEvent; import android.view.WindowManagerPolicyConstants; import androidx.test.filters.SmallTest; @@ -57,6 +59,7 @@ import com.android.systemui.plugins.GlobalActions; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -107,8 +110,10 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private RingerModeTracker mRingerModeTracker; @Mock private RingerModeLiveData mRingerModeLiveData; @Mock private SysUiState mSysUiState; + @Mock private PackageManager mPackageManager; @Mock private Handler mHandler; @Mock private UserContextProvider mUserContextProvider; + @Mock private StatusBar mStatusBar; private TestableLooper mTestableLooper; @@ -120,6 +125,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); when(mUserContextProvider.getUserContext()).thenReturn(mContext); + when(mResources.getConfiguration()).thenReturn( + getContext().getResources().getConfiguration()); mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext, mWindowManagerFuncs, @@ -150,7 +157,9 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mInfoProvider, mRingerModeTracker, mSysUiState, - mHandler + mHandler, + mPackageManager, + mStatusBar ); mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); @@ -194,7 +203,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { } @Test - public void testShouldLogOnTapOutside() { + public void testSingleTap_logAndDismiss() { mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); @@ -207,9 +216,58 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { }; doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); - View container = dialog.findViewById(com.android.systemui.R.id.global_actions_container); - container.callOnClick(); + + GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener); + gestureListener.onSingleTapConfirmed(null); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + } + + @Test + public void testSwipeDownLockscreen_logAndOpenQS() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + doReturn(true).when(mStatusBar).isKeyguardShowing(); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); + + GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener); + MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); + MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0); + gestureListener.onFling(start, end, 0, 1000); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + verify(mStatusBar).animateExpandSettingsPanel(null); + } + + @Test + public void testSwipeDown_logAndOpenNotificationShade() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + doReturn(false).when(mStatusBar).isKeyguardShowing(); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); + + GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener); + MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); + MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0); + gestureListener.onFling(start, end, 0, 1000); verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + verify(mStatusBar).animateExpandNotificationsPanel(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index 3130e977dc83..e5c104e7d377 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Color; @@ -65,6 +66,7 @@ import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -123,7 +125,9 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController; @Mock private Handler mHandler; @Mock private UserTracker mUserTracker; + @Mock private PackageManager mPackageManager; @Mock private SecureSettings mSecureSettings; + @Mock private StatusBar mStatusBar; private TestableLooper mTestableLooper; @@ -134,6 +138,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { allowTestableLooperAsMainThread(); when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); + when(mResources.getConfiguration()).thenReturn( + getContext().getResources().getConfiguration()); mGlobalActionsDialog = new GlobalActionsDialog(mContext, mWindowManagerFuncs, @@ -164,7 +170,9 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mUiEventLogger, mRingerModeTracker, mSysUiState, - mHandler + mHandler, + mPackageManager, + mStatusBar ); mGlobalActionsDialog.setZeroDialogPressDelayForTesting(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java index 2f78532b9e71..51576687880c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java @@ -34,7 +34,6 @@ import android.content.res.ColorStateList; import android.graphics.Color; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; -import android.view.View; import androidx.test.filters.SmallTest; @@ -258,8 +257,8 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas // WHEN the device is dozing mStatusBarStateListener.onDozingChanged(true); - // THEN the view is GONE - verify(mView).setVisibility(View.GONE); + // THEN switch to INDICATION_TYPE_NONE + verify(mView).switchIndication(null); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 15cfee828293..3128db423a24 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -9,8 +9,8 @@ import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession -import android.provider.Settings import android.os.Bundle +import android.provider.Settings import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -20,6 +20,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.SbnBuilder +import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq @@ -86,6 +87,8 @@ class MediaDataManagerTest : SysuiTestCase() { lateinit var mediaNotification: StatusBarNotification @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData> private val clock = FakeSystemClock() + @Mock private lateinit var tunerService: TunerService + @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable> private val originalSmartspaceSetting = Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1) @@ -114,8 +117,11 @@ class MediaDataManagerTest : SysuiTestCase() { smartspaceMediaDataProvider = smartspaceMediaDataProvider, useMediaResumption = true, useQsMediaPlayer = true, - systemClock = clock + systemClock = clock, + tunerService = tunerService ) + verify(tunerService).addTunable(capture(tunableCaptor), + eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) session = MediaSession(context, "MediaDataManagerTestSession") mediaNotification = SbnBuilder().run { setPkg(PACKAGE_NAME) @@ -364,6 +370,9 @@ class MediaDataManagerTest : SysuiTestCase() { fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() { smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) smartspaceMediaDataProvider.onTargetsAvailable(listOf()) + foregroundExecutor.advanceClockToLast() + foregroundExecutor.runAllReady() + verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false)) } @@ -372,6 +381,8 @@ class MediaDataManagerTest : SysuiTestCase() { // WHEN media recommendation setting is off Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0) + tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0") + smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) // THEN smartspace signal is ignored @@ -380,6 +391,24 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testMediaRecommendationDisabled_removesSmartspaceData() { + // GIVEN a media recommendation card is present + smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) + verify(listener).onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), + anyBoolean()) + + // WHEN the media recommendation setting is turned off + Settings.Secure.putInt(context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0) + tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0") + + // THEN listeners are notified + foregroundExecutor.advanceClockToLast() + foregroundExecutor.runAllReady() + verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true)) + } + + @Test fun testOnMediaDataChanged_updatesLastActiveTime() { val currentTime = clock.elapsedRealtime() mediaDataManager.onNotificationAdded(KEY, mediaNotification) diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java index 5f4d90b3666f..f6264ffc6a70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java @@ -49,6 +49,7 @@ import com.android.systemui.wmshell.BubblesManager; import com.android.wm.shell.bubbles.Bubble; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -213,6 +214,7 @@ public class LaunchConversationActivityTest extends SysuiTestCase { verify(mBubblesManager, never()).expandStackAndSelectBubble(any(NotificationEntry.class)); } + @Ignore @Test public void testBubbleWithNoNotifOpensBubble() throws Exception { Bubble bubble = mock(Bubble.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index b0e3e3e936a9..2ae4cbe17ac6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -46,6 +46,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.media.MediaHost; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.dagger.QSFragmentComponent; +import com.android.systemui.qs.external.CustomTileStatePersister; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSFactoryImpl; import com.android.systemui.settings.UserTracker; @@ -132,7 +133,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { () -> mock(AutoTileManager.class), mock(DumpManager.class), mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)), mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class), - mock(SecureSettings.class)); + mock(SecureSettings.class), mock(CustomTileStatePersister.class)); qs.setHost(host); qs.setListening(true); 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 7c73b4c44e90..69bdcbcff270 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -59,6 +59,8 @@ import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.external.CustomTile; +import com.android.systemui.qs.external.CustomTileStatePersister; +import com.android.systemui.qs.external.TileServiceKey; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; @@ -125,6 +127,8 @@ public class QSTileHostTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private SecureSettings mSecureSettings; + @Mock + private CustomTileStatePersister mCustomTileStatePersister; private Handler mHandler; private TestableLooper mLooper; @@ -145,7 +149,7 @@ public class QSTileHostTest extends SysuiTestCase { mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler, mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager, mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker, - mSecureSettings); + mSecureSettings, mCustomTileStatePersister); setUpTileFactory(); when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt())) @@ -371,6 +375,14 @@ public class QSTileHostTest extends SysuiTestCase { verify(mQSLogger, never()).logTileDestroyed(isNull(), anyString()); } + @Test + public void testCustomTileRemoved_stateDeleted() { + mQSTileHost.changeTiles(List.of(CUSTOM_TILE_SPEC), List.of()); + + verify(mCustomTileStatePersister) + .removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId())); + } + private class TestQSTileHost extends QSTileHost { TestQSTileHost(Context context, StatusBarIconController iconController, QSFactory defaultFactory, Handler mainHandler, Looper bgLooper, @@ -378,10 +390,11 @@ public class QSTileHostTest extends SysuiTestCase { Provider<AutoTileManager> autoTiles, DumpManager dumpManager, BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, - SecureSettings secureSettings) { + SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister) { super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager, tunerService, autoTiles, dumpManager, broadcastDispatcher, - Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings); + Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings, + customTileStatePersister); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt new file mode 100644 index 000000000000..6c96576bcbc1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt @@ -0,0 +1,159 @@ +/* + * 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.qs.external + +import android.content.ComponentName +import android.content.Context +import android.content.SharedPreferences +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class CustomTileStatePersisterTest : SysuiTestCase() { + + companion object { + private val TEST_COMPONENT = ComponentName("pkg", "cls") + private const val TEST_USER = 0 + private val KEY = TileServiceKey(TEST_COMPONENT, TEST_USER) + + private const val TEST_STATE = Tile.STATE_INACTIVE + private const val TEST_LABEL = "test_label" + private const val TEST_SUBTITLE = "test_subtitle" + private const val TEST_CONTENT_DESCRIPTION = "test_content_description" + private const val TEST_STATE_DESCRIPTION = "test_state_description" + + private fun Tile.isEqualTo(other: Tile): Boolean { + return state == other.state && + label == other.label && + subtitle == other.subtitle && + contentDescription == other.contentDescription && + stateDescription == other.stateDescription + } + } + + @Mock + private lateinit var mockContext: Context + @Mock + private lateinit var sharedPreferences: SharedPreferences + @Mock(answer = Answers.RETURNS_SELF) + private lateinit var editor: SharedPreferences.Editor + private lateinit var tile: Tile + private lateinit var customTileStatePersister: CustomTileStatePersister + + @Captor + private lateinit var stringCaptor: ArgumentCaptor<String> + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + `when`(mockContext.getSharedPreferences(anyString(), anyInt())) + .thenReturn(sharedPreferences) + `when`(sharedPreferences.edit()).thenReturn(editor) + + tile = Tile() + customTileStatePersister = CustomTileStatePersister(mockContext) + } + + @Test + fun testWriteState() { + tile.apply { + state = TEST_STATE + label = TEST_LABEL + subtitle = TEST_SUBTITLE + contentDescription = TEST_CONTENT_DESCRIPTION + stateDescription = TEST_STATE_DESCRIPTION + } + + customTileStatePersister.persistState(KEY, tile) + + verify(editor).putString(eq(KEY.toString()), capture(stringCaptor)) + + assertThat(tile.isEqualTo(readTileFromString(stringCaptor.value))).isTrue() + } + + @Test + fun testReadState() { + tile.apply { + state = TEST_STATE + label = TEST_LABEL + subtitle = TEST_SUBTITLE + contentDescription = TEST_CONTENT_DESCRIPTION + stateDescription = TEST_STATE_DESCRIPTION + } + + `when`(sharedPreferences.getString(eq(KEY.toString()), any())) + .thenReturn(writeToString(tile)) + + assertThat(tile.isEqualTo(customTileStatePersister.readState(KEY)!!)).isTrue() + } + + @Test + fun testReadStateDefault() { + `when`(sharedPreferences.getString(any(), any())).thenAnswer { + it.getArgument(1) + } + + assertThat(customTileStatePersister.readState(KEY)).isNull() + } + + @Test + fun testStoreNulls() { + assertThat(tile.label).isNull() + + customTileStatePersister.persistState(KEY, tile) + + verify(editor).putString(eq(KEY.toString()), capture(stringCaptor)) + + assertThat(readTileFromString(stringCaptor.value).label).isNull() + } + + @Test + fun testReadNulls() { + assertThat(tile.label).isNull() + + `when`(sharedPreferences.getString(eq(KEY.toString()), any())) + .thenReturn(writeToString(tile)) + + assertThat(customTileStatePersister.readState(KEY)!!.label).isNull() + } + + @Test + fun testRemoveState() { + customTileStatePersister.removeState(KEY) + + verify(editor).remove(KEY.toString()) + } +}
\ No newline at end of file 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 b1c3d1da8fea..9b5c1619ef31 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 @@ -38,6 +38,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.util.mockito.any import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -48,8 +49,9 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito.`when` -import org.mockito.Mockito.any import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @@ -76,6 +78,7 @@ class CustomTileTest : SysuiTestCase() { @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var applicationInfo: ApplicationInfo @Mock private lateinit var serviceInfo: ServiceInfo + @Mock private lateinit var customTileStatePersister: CustomTileStatePersister private lateinit var customTile: CustomTile private lateinit var testableLooper: TestableLooper @@ -108,10 +111,13 @@ class CustomTileTest : SysuiTestCase() { metricsLogger, statusBarStateController, activityStarter, - qsLogger + qsLogger, + customTileStatePersister ) customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + customTile.initialize() + testableLooper.processAllMessages() } @Test @@ -123,6 +129,8 @@ class CustomTileTest : SysuiTestCase() { `when`(userContext.userId).thenReturn(10) val tile = CustomTile.create(customTileBuilder, TILE_SPEC, userContext) + tile.initialize() + testableLooper.processAllMessages() assertEquals(10, tile.user) } @@ -131,6 +139,8 @@ class CustomTileTest : SysuiTestCase() { fun testToggleableTileHasBooleanState() { `when`(tileServiceManager.isToggleableTile).thenReturn(true) customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + customTile.initialize() + testableLooper.processAllMessages() assertTrue(customTile.state is QSTile.BooleanState) assertTrue(customTile.newTileState() is QSTile.BooleanState) @@ -146,6 +156,9 @@ class CustomTileTest : SysuiTestCase() { fun testValueUpdatedInBooleanTile() { `when`(tileServiceManager.isToggleableTile).thenReturn(true) customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + customTile.initialize() + testableLooper.processAllMessages() + customTile.qsTile.icon = mock(Icon::class.java) `when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java))) .thenReturn(mock(Drawable::class.java)) @@ -173,4 +186,88 @@ class CustomTileTest : SysuiTestCase() { .thenReturn(null) customTile.handleUpdateState(customTile.newTileState(), null) } + + @Test + fun testNoLoadStateTileNotActive() { + // Not active by default + testableLooper.processAllMessages() + + verify(customTileStatePersister, never()).readState(any()) + } + + @Test + fun testNoPersistedStateTileNotActive() { + // Not active by default + val t = Tile().apply { + state = Tile.STATE_INACTIVE + } + customTile.updateTileState(t) + testableLooper.processAllMessages() + + verify(customTileStatePersister, never()).persistState(any(), any()) + } + + @Test + fun testPersistedStateRetrieved() { + val state = Tile.STATE_INACTIVE + val label = "test_label" + val subtitle = "test_subtitle" + val contentDescription = "test_content_description" + val stateDescription = "test_state_description" + + val t = Tile().apply { + this.state = state + this.label = label + this.subtitle = subtitle + this.contentDescription = contentDescription + this.stateDescription = stateDescription + } + `when`(tileServiceManager.isActiveTile).thenReturn(true) + `when`(customTileStatePersister + .readState(TileServiceKey(componentName, customTile.user))).thenReturn(t) + val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + tile.initialize() + testableLooper.processAllMessages() + + // Make sure we have an icon in the tile because we don't have a default icon + // This should not be overridden by the retrieved tile that has null icon. + tile.qsTile.icon = mock(Icon::class.java) + `when`(tile.qsTile.icon.loadDrawable(any(Context::class.java))) + .thenReturn(mock(Drawable::class.java)) + + tile.refreshState() + + testableLooper.processAllMessages() + + val tileState = tile.state + + assertEquals(state, tileState.state) + assertEquals(label, tileState.label) + assertEquals(subtitle, tileState.secondaryLabel) + assertEquals(contentDescription, tileState.contentDescription) + assertEquals(stateDescription, tileState.stateDescription) + } + + @Test + fun testStoreStateOnChange() { + val t = Tile().apply { + state = Tile.STATE_INACTIVE + label = "test_label" + subtitle = "test_subtitle" + contentDescription = "test_content_description" + stateDescription = "test_state_description" + } + `when`(tileServiceManager.isActiveTile).thenReturn(true) + + val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + tile.initialize() + testableLooper.processAllMessages() + + tile.updateTileState(t) + + testableLooper.processAllMessages() + + verify(customTileStatePersister) + .persistState(TileServiceKey(componentName, customTile.user), t) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 641f917bcfbe..2b1840462291 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -118,7 +118,8 @@ public class TileServicesTest extends SysuiTestCase { mQSLogger, mUiEventLogger, mUserTracker, - mSecureSettings); + mSecureSettings, + mock(CustomTileStatePersister.class)); mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher, mUserTracker); } 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 80231a49bb44..ea4d7cc2529c 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 @@ -121,6 +121,9 @@ public class QSTileImplTest extends SysuiTestCase { mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, mFalsingManager, mMetricsLogger, mStatusBarStateController, mActivityStarter, mQsLogger); + mTile.initialize(); + mTestableLooper.processAllMessages(); + mTile.setTileSpec(SPEC); } 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 32b1f433dfcf..5e2d8fde84da 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 @@ -80,6 +80,8 @@ class AlarmTileTest : SysuiTestCase() { nextAlarmController ) + tile.initialize() + verify(nextAlarmController).observe(eq(tile), capture(callbackCaptor)) tile.refreshState() testableLooper.processAllMessages() 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 f17bd56d0052..1bf83513d472 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 @@ -87,6 +87,9 @@ class BatterySaverTileTest : SysuiTestCase() { qsLogger, batteryController, secureSettings) + + tile.initialize() + testableLooper.processAllMessages() } @Test 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 7c1a5f5ebf30..d44a52607707 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 @@ -112,11 +112,14 @@ public class CastTileTest extends SysuiTestCase { mNetworkController, mHotspotController ); + mCastTile.initialize(); // We are not setting the mocks to listening, so we trigger a first refresh state to // set the initial state mCastTile.refreshState(); + mTestableLooper.processAllMessages(); + mCastTile.handleSetListening(true); ArgumentCaptor<NetworkController.SignalCallback> signalCallbackArgumentCaptor = ArgumentCaptor.forClass(NetworkController.SignalCallback.class); 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 6d1bbd9708ea..94af10a485fd 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 @@ -378,7 +378,10 @@ class DeviceControlsTileTest : SysuiTestCase() { qsLogger, controlsComponent, keyguardStateController - ) + ).also { + it.initialize() + testableLooper.processAllMessages() + } } } 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 99d028cd8c5c..cfd37358dcff 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 @@ -92,6 +92,9 @@ public class NfcTileTest extends SysuiTestCase { mQSLogger, mBroadcastDispatcher ); + + mNfcTile.initialize(); + mTestableLooper.processAllMessages(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index e4a9aacb57ab..a50cbe5adc48 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -155,6 +155,9 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { mPackageManager, mSecureSettings, mController); + + mTile.initialize(); + mTestableLooper.processAllMessages(); } @Test 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 df4908ddc4ef..9eb688de3511 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 @@ -89,6 +89,9 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { mActivityStarter, mQSLogger ); + + mTile.initialize(); + mTestableLooper.processAllMessages(); } @Test 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 3b4e863ed8bd..964ce01312bf 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 @@ -91,6 +91,9 @@ public class ScreenRecordTileTest extends SysuiTestCase { mController, mKeyguardDismissUtil ); + + mTile.initialize(); + mTestableLooper.processAllMessages(); } // Test that the tile is inactive and labeled correctly when the controller is neither starting diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 2e0827f24bf8..fa25c3f1e005 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -289,6 +289,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testIconScrollXAfterTranslationAndReset() throws Exception { + mGroupRow.setDismissUsingRowTranslationX(false); mGroupRow.setTranslation(50); assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java index 9ac600afe990..5bf1bb3c573f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java @@ -63,6 +63,7 @@ public class DozeParametersTest extends SysuiTestCase { @Mock private BatteryController mBatteryController; @Mock private FeatureFlags mFeatureFlags; @Mock private DumpManager mDumpManager; + @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @Before public void setup() { @@ -75,7 +76,8 @@ public class DozeParametersTest extends SysuiTestCase { mBatteryController, mTunerService, mDumpManager, - mFeatureFlags + mFeatureFlags, + mUnlockedScreenOffAnimationController ); } @Test @@ -125,7 +127,8 @@ public class DozeParametersTest extends SysuiTestCase { when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); when(mFeatureFlags.useNewLockscreenAnimations()).thenReturn(true); - + when(mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation()) + .thenReturn(true); assertTrue(mDozeParameters.shouldControlUnlockedScreenOff()); // Trigger the setter for the current value. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 52f74317f3c9..c5c6b67c32cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -85,7 +85,6 @@ import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FeatureFlags; @@ -679,21 +678,6 @@ public class NotificationPanelViewTest extends SysuiTestCase { verify(mTapAgainViewController).show(); } - @Test - public void testNotificationClipping_isAlignedWithNotificationScrimInSplitShade() { - mStatusBarStateController.setState(SHADE); - QS qs = mock(QS.class); - when(qs.getHeader()).thenReturn(mock(View.class)); - mNotificationPanelViewController.mQs = qs; - enableSplitShade(); - - // hacky way to refresh notification scrim top with non-zero qsPanelBottom value - mNotificationPanelViewController.setTransitionToFullShadeAmount(200, false, 0); - - verify(mAmbientState) - .setNotificationScrimTop(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE); - } - private FalsingManager.FalsingTapListener getFalsingTapListener() { for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) { listener.onViewAttachedToWindow(mView); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java index 323843098a1a..9fe47eceff1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java @@ -45,6 +45,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; import org.junit.Test; @@ -70,6 +71,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private SysuiColorExtractor mColorExtractor; @Mock ColorExtractor.GradientColors mGradientColors; @Mock private DumpManager mDumpManager; + @Mock private KeyguardStateController mKeyguardStateController; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; @@ -83,7 +85,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, - mColorExtractor, mDumpManager); + mColorExtractor, mDumpManager, mKeyguardStateController); mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index b2efd682bc62..cbc7c6dd0447 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -777,6 +777,12 @@ public class StatusBarTest extends SysuiTestCase { } @Test + public void testDumpBarTransitions_DoesNotCrash() { + StatusBar.dumpBarTransitions( + new PrintWriter(new ByteArrayOutputStream()), "var", /* transitions= */ null); + } + + @Test @RunWithLooper(setAsMainLooper = true) public void testUpdateKeyguardState_DoesNotCrash() { mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD); @@ -843,12 +849,14 @@ public class StatusBarTest extends SysuiTestCase { // By default, showKeyguardImpl sets state to KEYGUARD. mStatusBar.showKeyguardImpl(); - verify(mStatusBarStateController).setState(eq(StatusBarState.KEYGUARD)); + verify(mStatusBarStateController).setState( + eq(StatusBarState.KEYGUARD), eq(false) /* force */); // If useFullscreenUserSwitcher is true, state is set to FULLSCREEN_USER_SWITCHER. when(mUserSwitcherController.useFullscreenUserSwitcher()).thenReturn(true); mStatusBar.showKeyguardImpl(); - verify(mStatusBarStateController).setState(eq(StatusBarState.FULLSCREEN_USER_SWITCHER)); + verify(mStatusBarStateController).setState( + eq(StatusBarState.FULLSCREEN_USER_SWITCHER), eq(false) /* force */); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java index 1aebf1c1c80d..e136d00b86f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java @@ -132,4 +132,9 @@ public class FakeKeyguardStateController implements KeyguardStateController { public boolean canPerformSmartSpaceTransition() { return false; } + + @Override + public boolean isKeyguardScreenRotationAllowed() { + return false; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 6e2e4cb9ecfa..496976e8f55c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -98,6 +98,7 @@ import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; @@ -227,6 +228,8 @@ public class BubblesTest extends SysuiTestCase { private TaskStackListenerImpl mTaskStackListener; @Mock private ShellTaskOrganizer mShellTaskOrganizer; + @Mock + private KeyguardStateController mKeyguardStateController; private TestableBubblePositioner mPositioner; @@ -249,7 +252,7 @@ public class BubblesTest extends SysuiTestCase { mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, - mColorExtractor, mDumpManager); + mColorExtractor, mDumpManager, mKeyguardStateController); mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); @@ -287,6 +290,7 @@ public class BubblesTest extends SysuiTestCase { // TODO: Fix mPositioner = new TestableBubblePositioner(mContext, mWindowManager); + mPositioner.setMaxBubbles(5); mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor); TestableNotificationInterruptStateProviderImpl interruptionStateProvider = diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index 9339f81940d9..9114b7a35fd2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -82,6 +82,7 @@ import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; @@ -192,6 +193,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { private TaskStackListenerImpl mTaskStackListener; @Mock private ShellTaskOrganizer mShellTaskOrganizer; + @Mock + private KeyguardStateController mKeyguardStateController; private TestableBubblePositioner mPositioner; @@ -213,7 +216,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, - mColorExtractor, mDumpManager); + mColorExtractor, mDumpManager, mKeyguardStateController); mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); @@ -232,6 +235,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); mPositioner = new TestableBubblePositioner(mContext, mWindowManager); + mPositioner.setMaxBubbles(5); mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor); TestableNotificationInterruptStateProviderImpl interruptionStateProvider = diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java index 24a7cd5c89ac..6edc373d2926 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java @@ -22,9 +22,11 @@ import android.graphics.Insets; import android.graphics.Rect; import android.view.WindowManager; +import com.android.wm.shell.R; import com.android.wm.shell.bubbles.BubblePositioner; public class TestableBubblePositioner extends BubblePositioner { + private int mMaxBubbles; public TestableBubblePositioner(Context context, WindowManager windowManager) { @@ -33,5 +35,15 @@ public class TestableBubblePositioner extends BubblePositioner { updateInternal(Configuration.ORIENTATION_PORTRAIT, Insets.of(0, 0, 0, 0), new Rect(0, 0, 500, 1000)); + mMaxBubbles = context.getResources().getInteger(R.integer.bubbles_max_rendered); + } + + public void setMaxBubbles(int max) { + mMaxBubbles = max; + } + + @Override + public int getMaxBubbles() { + return mMaxBubbles; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 1dd0b21bda30..ff15d0151ea4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -37,6 +37,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; +import com.android.wm.shell.onehanded.OneHandedEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.Pip; @@ -106,6 +107,7 @@ public class WMShellTest extends SysuiTestCase { verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class)); verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class)); verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class)); + verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class)); } @Test diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml index b9c5f1ded3d5..8d0227eed875 100644 --- a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml @@ -25,9 +25,9 @@ <!-- Max((28 + 20), 0) = 48 --> <dimen name="status_bar_height_landscape">48dp</dimen> <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) --> - <dimen name="quick_qs_offset_height">28dp</dimen> + <dimen name="quick_qs_offset_height">48dp</dimen> <!-- Total height of QQS (quick_qs_offset_height + 128) --> - <dimen name="quick_qs_total_height">156dp</dimen> + <dimen name="quick_qs_total_height">176dp</dimen> <dimen name="waterfall_display_left_edge_size">20dp</dimen> <dimen name="waterfall_display_top_edge_size">0dp</dimen> diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java index 3310cb4e3e79..ea2c7d2a41e9 100644 --- a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java +++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java @@ -31,9 +31,9 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.WindowManagerPolicyConstants; import com.android.internal.os.SomeArgs; -import com.android.server.policy.WindowManagerPolicy; import java.util.ArrayList; import java.util.Arrays; @@ -122,6 +122,12 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement return; } cancelAnyPendingInjectedEvents(); + // The events injected from outside of system_server are not trusted. Remove the flag to + // prevent accessibility service from impersonating a real input device. + policyFlags &= ~WindowManagerPolicyConstants.FLAG_INPUTFILTER_TRUSTED; + // Indicate that the input event is injected from accessibility, to let applications + // distinguish it from events injected by other means. + policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY; sendMotionEventToNext(event, rawEvent, policyFlags); } @@ -156,7 +162,9 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement return false; } MotionEvent motionEvent = (MotionEvent) message.obj; - sendMotionEventToNext(motionEvent, motionEvent, WindowManagerPolicy.FLAG_PASS_TO_USER); + sendMotionEventToNext(motionEvent, motionEvent, + WindowManagerPolicyConstants.FLAG_PASS_TO_USER + | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY); boolean isEndOfSequence = message.arg1 != 0; if (isEndOfSequence) { notifyService(mServiceInterfaceForCurrentGesture, mSequencesInProgress.get(0), true); @@ -308,7 +316,8 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement MotionEvent cancelEvent = obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1); sendMotionEventToNext(cancelEvent, cancelEvent, - WindowManagerPolicy.FLAG_PASS_TO_USER); + WindowManagerPolicyConstants.FLAG_PASS_TO_USER + | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY); mOpenGesturesInProgress.put(source, false); } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 83dfe8ed2576..05131d44b01e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -454,19 +454,13 @@ public class CompanionDeviceManagerService extends SystemService implements Bind }).cancelTimeout(); }, FgThread.getExecutor()).whenComplete(uncheckExceptions((association, err) -> { - - final long callingIdentity = Binder.clearCallingIdentity(); - try { - if (err == null) { - addAssociation(association); - } else { - Slog.e(LOG_TAG, "Failed to discover device(s)", err); - callback.onFailure("No devices found: " + err.getMessage()); - } - cleanup(); - } finally { - Binder.restoreCallingIdentity(callingIdentity); + if (err == null) { + addAssociation(association); + } else { + Slog.e(LOG_TAG, "Failed to discover device(s)", err); + callback.onFailure("No devices found: " + err.getMessage()); } + cleanup(); })); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 743c4b8c12de..5bb2ddebb47a 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1826,6 +1826,7 @@ public final class ActiveServices { if (!r.fgRequired) { final long delayMs = SystemClock.elapsedRealtime() - r.createRealTime; if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) { + resetFgsRestrictionLocked(r); setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), r.appInfo.uid, r.intent.getIntent(), r, r.userId,false); final String temp = "startForegroundDelayMs:" + delayMs; @@ -1911,7 +1912,6 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; - r.mLogEntering = true; // The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could // be deferred, make a copy of mAllowStartForeground and // mAllowWhileInUsePermissionInFgs. @@ -1937,6 +1937,9 @@ public final class ActiveServices { AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); registerAppOpCallbackLocked(r); mAm.updateForegroundServiceUsageStats(r.name, r.userId, true); + logFGSStateChangeLocked(r, + FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, + 0); } // Even if the service is already a FGS, we need to update the notification, // so we need to call it again. @@ -1992,6 +1995,7 @@ public final class ActiveServices { FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT, r.mFgsExitTime > r.mFgsEnterTime ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0); + r.mFgsNotificationWasDeferred = false; resetFgsRestrictionLocked(r); mAm.updateForegroundServiceUsageStats(r.name, r.userId, false); if (r.app != null) { @@ -2196,6 +2200,7 @@ public final class ActiveServices { } r.fgDisplayTime = when; r.mFgsNotificationDeferred = true; + r.mFgsNotificationWasDeferred = true; r.mFgsNotificationShown = false; mPendingFgsNotifications.add(r); if (DEBUG_FOREGROUND_SERVICE) { @@ -2239,11 +2244,6 @@ public final class ActiveServices { Slog.d(TAG_SERVICE, " - service no longer running/fg, ignoring"); } } - // Regardless of whether we needed to post the notification or the - // service is no longer running, we may not have logged its FGS - // transition yet depending on the timing and API sequence that led - // to this point - so make sure to do so. - maybeLogFGSStateEnteredLocked(r); } } if (DEBUG_FOREGROUND_SERVICE) { @@ -2286,16 +2286,6 @@ public final class ActiveServices { } } - private void maybeLogFGSStateEnteredLocked(ServiceRecord r) { - if (r.mLogEntering) { - logFGSStateChangeLocked(r, - FrameworkStatsLog - .FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, - 0); - r.mLogEntering = false; - } - } - /** * Callback from NotificationManagerService whenever it posts a notification * associated with a foreground service. This is the unified handling point @@ -2314,9 +2304,7 @@ public final class ActiveServices { && id == sr.foregroundId && sr.appInfo.packageName.equals(pkg)) { // Found it. If 'shown' is false, it means that the notification - // subsystem will not be displaying it yet, so all we do is log - // the "fgs entered" transition noting deferral, then we're done. - maybeLogFGSStateEnteredLocked(sr); + // subsystem will not be displaying it yet. if (shown) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "Notification shown; canceling deferral of " @@ -4415,6 +4403,7 @@ public final class ActiveServices { r.isForeground = false; r.foregroundId = 0; r.foregroundNoti = null; + r.mFgsNotificationWasDeferred = false; resetFgsRestrictionLocked(r); // Clear start entries. @@ -6447,7 +6436,7 @@ public final class ActiveServices { ? r.mRecentCallerApplicationInfo.targetSdkVersion : 0, r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mCallingUid : INVALID_UID, - r.mFgsNotificationDeferred, + r.mFgsNotificationWasDeferred, r.mFgsNotificationShown, durationMs, r.mStartForegroundCount, diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 4e6e91ac7b5d..7f6e6687625c 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -78,6 +78,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.BatteryUsageStatsProvider; +import com.android.internal.os.BatteryUsageStatsStore; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.PowerProfile; import com.android.internal.os.RailStats; @@ -124,10 +125,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub Watchdog.Monitor { static final String TAG = "BatteryStatsService"; static final boolean DBG = false; + private static final boolean BATTERY_USAGE_STORE_ENABLED = true; private static IBatteryStats sService; final BatteryStatsImpl mStats; + private final BatteryUsageStatsStore mBatteryUsageStatsStore; private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider; private final Context mContext; private final BatteryExternalStatsWorker mWorker; @@ -348,7 +351,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub mStats.setPowerProfileLocked(new PowerProfile(context)); mStats.startTrackingSystemServerCpuTime(); - mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats); + if (BATTERY_USAGE_STORE_ENABLED) { + mBatteryUsageStatsStore = + new BatteryUsageStatsStore(context, mStats, systemDir, mHandler); + } else { + mBatteryUsageStatsStore = null; + } + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats, + mBatteryUsageStatsStore); } public void publish() { @@ -752,6 +762,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL, null, // use default PullAtomMetadata values BackgroundThread.getExecutor(), pullAtomCallback); + statsManager.setPullAtomCallback( + FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET, + null, // use default PullAtomMetadata values + BackgroundThread.getExecutor(), pullAtomCallback); } /** StatsPullAtomCallback for pulling BatteryUsageStats data. */ @@ -768,6 +782,17 @@ public final class BatteryStatsService extends IBatteryStats.Stub new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(); bus = getBatteryUsageStats(List.of(powerProfileQuery)).get(0); break; + case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: + final long sessionStart = mBatteryUsageStatsStore + .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp(); + final long sessionEnd = mStats.getStartClockTime(); + final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() + .aggregateSnapshots(sessionStart, sessionEnd) + .build(); + bus = getBatteryUsageStats(List.of(query)).get(0); + mBatteryUsageStatsStore + .setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd); + break; default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 3ac1e8445bea..e65707acb362 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -351,7 +351,8 @@ public class OomAdjuster { final int index = mCache.indexOfKey(app.packageName); Pair<Boolean, WeakReference<ApplicationInfo>> p; if (index < 0) { - p = new Pair<>(mPlatformCompat.isChangeEnabled(mChangeId, app), + p = new Pair<>(mPlatformCompat.isChangeEnabledInternalNoLogging(mChangeId, + app), new WeakReference<>(app)); mCache.put(app.packageName, p); return p.first; @@ -361,7 +362,8 @@ public class OomAdjuster { return p.first; } // Cache is invalid, regenerate it - p = new Pair<>(mPlatformCompat.isChangeEnabled(mChangeId, app), + p = new Pair<>(mPlatformCompat.isChangeEnabledInternalNoLogging(mChangeId, + app), new WeakReference<>(app)); mCache.setValueAt(index, p); return p.first; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 3ba07af710c2..141f081ab9a7 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -109,7 +109,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean fgWaiting; // is a timeout for going foreground already scheduled? boolean isNotAppComponentUsage; // is service binding not considered component/package usage? boolean isForeground; // is service currently in foreground mode? - boolean mLogEntering; // need to report fgs transition once deferral policy is known int foregroundId; // Notification ID of last foreground req. Notification foregroundNoti; // Notification record of foreground state. long fgDisplayTime; // time at which the FGS notification should become visible @@ -167,8 +166,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN long mFgsEnterTime = 0; // The uptime when the service exits FGS state. long mFgsExitTime = 0; - // FGS notification was deferred. + // FGS notification is deferred. boolean mFgsNotificationDeferred; + // FGS notification was deferred. + boolean mFgsNotificationWasDeferred; // FGS notification was shown before the FGS finishes, or it wasn't deferred in the first place. boolean mFgsNotificationShown; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 104bc9b2c8d1..99a33e4462e2 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -848,7 +848,7 @@ public class AppOpsService extends IAppOpsService.Stub { return mAttributionFlags; } - /** @return attributoin chiang id for the access */ + /** @return attribution chain id for the access */ public int getAttributionChainId() { return mAttributionChainId; } @@ -912,7 +912,8 @@ public class AppOpsService extends IAppOpsService.Stub { proxyAttributionTag, uidState, flags); mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, - tag, uidState, flags, accessTime); + tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE, + AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); } /** @@ -1053,9 +1054,9 @@ public class AppOpsService extends IAppOpsService.Stub { event.numUnfinishedStarts++; if (isStarted) { - // TODO: Consider storing the attribution chain flags and id mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, uidState, flags, startTime); + parent.packageName, tag, uidState, flags, startTime, attributionFlags, + attributionChainId); } } @@ -1112,7 +1113,8 @@ public class AppOpsService extends IAppOpsService.Stub { mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, parent.packageName, tag, event.getUidState(), - event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration()); + event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), + event.getAttributionFlags(), event.getAttributionChainId()); if (!isPausing) { mInProgressStartOpEventPool.release(event); @@ -1215,7 +1217,8 @@ public class AppOpsService extends IAppOpsService.Stub { event.mStartElapsedTime = SystemClock.elapsedRealtime(); event.mStartTime = startTime; mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, event.mUidState, event.mFlags, startTime); + parent.packageName, tag, event.mUidState, event.mFlags, startTime, + event.getAttributionFlags(), event.getAttributionChainId()); if (shouldSendActive) { scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, tag, true, event.getAttributionFlags(), event.getAttributionChainId()); diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index 10cfddfb3ae4..49469cc8a597 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -26,6 +26,7 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; +import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY; import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; @@ -156,8 +157,11 @@ final class DiscreteRegistry { private static final String ATTR_NOTE_DURATION = "nd"; private static final String ATTR_UID_STATE = "us"; private static final String ATTR_FLAGS = "f"; + private static final String ATTR_ATTRIBUTION_FLAGS = "af"; + private static final String ATTR_CHAIN_ID = "ci"; - private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED; + private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED + | OP_FLAG_TRUSTED_PROXY; // Lock for read/write access to on disk state private final Object mOnDiskLock = new Object(); @@ -227,13 +231,14 @@ final class DiscreteRegistry { void recordDiscreteAccess(int uid, String packageName, int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, - long accessDuration) { + long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId) { if (!isDiscreteOp(op, flags)) { return; } synchronized (mInMemoryLock) { mDiscreteOps.addDiscreteAccess(op, uid, packageName, attributionTag, flags, uidState, - accessTime, accessDuration); + accessTime, accessDuration, attributionFlags, attributionChainId); } } @@ -383,9 +388,10 @@ final class DiscreteRegistry { void addDiscreteAccess(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, - @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) { + @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { getOrCreateDiscreteUidOps(uid).addDiscreteAccess(op, packageName, attributionTag, flags, - uidState, accessTime, accessDuration); + uidState, accessTime, accessDuration, attributionFlags, attributionChainId); } private void filter(long beginTimeMillis, long endTimeMillis, @@ -613,9 +619,10 @@ final class DiscreteRegistry { void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, - long accessTime, long accessDuration) { + long accessTime, long accessDuration, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { getOrCreateDiscretePackageOps(packageName).addDiscreteAccess(op, attributionTag, flags, - uidState, accessTime, accessDuration); + uidState, accessTime, accessDuration, attributionFlags, attributionChainId); } private DiscretePackageOps getOrCreateDiscretePackageOps(String packageName) { @@ -680,9 +687,10 @@ final class DiscreteRegistry { void addDiscreteAccess(int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, - long accessTime, long accessDuration) { + long accessTime, long accessDuration, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { getOrCreateDiscreteOp(op).addDiscreteAccess(attributionTag, flags, uidState, accessTime, - accessDuration); + accessDuration, attributionFlags, attributionChainId); } void merge(DiscretePackageOps other) { @@ -823,37 +831,39 @@ final class DiscreteRegistry { for (int j = 0; j < n; j++) { DiscreteOpEvent event = list.get(j); list.set(j, new DiscreteOpEvent(event.mNoteTime - offset, event.mNoteDuration, - event.mUidState, event.mOpFlag)); + event.mUidState, event.mOpFlag, event.mAttributionFlags, + event.mAttributionChainId)); } } } void addDiscreteAccess(@Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, - long accessTime, long accessDuration) { + long accessTime, long accessDuration, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList( attributionTag); - accessTime = accessTime / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; - accessDuration = accessDuration == -1 ? -1 - : (accessDuration + sDiscreteHistoryQuantization - 1) - / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; int nAttributedOps = attributedOps.size(); int i = nAttributedOps; for (; i > 0; i--) { DiscreteOpEvent previousOp = attributedOps.get(i - 1); - if (previousOp.mNoteTime < accessTime) { + if (discretizeTimeStamp(previousOp.mNoteTime) < discretizeTimeStamp(accessTime)) { break; } - if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) { - if (accessDuration != previousOp.mNoteDuration) { + if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState + && previousOp.mAttributionFlags == attributionFlags + && previousOp.mAttributionChainId == attributionChainId) { + if (discretizeDuration(accessDuration) != discretizeDuration( + previousOp.mNoteDuration)) { break; } else { return; } } } - attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags)); + attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags, + attributionFlags, attributionChainId)); } private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) { @@ -875,7 +885,8 @@ final class DiscreteRegistry { for (int j = 0; j < nEvents; j++) { DiscreteOpEvent event = events.get(j); result.addDiscreteAccess(op, uid, packageName, tag, event.mUidState, - event.mOpFlag, event.mNoteTime, event.mNoteDuration); + event.mOpFlag, discretizeTimeStamp(event.mNoteTime), + discretizeDuration(event.mNoteDuration)); } } } @@ -932,11 +943,15 @@ final class DiscreteRegistry { -1); int uidState = parser.getAttributeInt(null, ATTR_UID_STATE); int opFlags = parser.getAttributeInt(null, ATTR_FLAGS); + int attributionFlags = parser.getAttributeInt(null, + ATTR_ATTRIBUTION_FLAGS, AppOpsManager.ATTRIBUTION_FLAGS_NONE); + int attributionChainId = parser.getAttributeInt(null, ATTR_CHAIN_ID, + AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); if (noteTime + noteDuration < beginTimeMillis) { continue; } DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration, - uidState, opFlags); + uidState, opFlags, attributionFlags, attributionChainId); events.add(event); } } @@ -952,13 +967,18 @@ final class DiscreteRegistry { final long mNoteDuration; final @AppOpsManager.UidState int mUidState; final @AppOpsManager.OpFlags int mOpFlag; + final @AppOpsManager.AttributionFlags int mAttributionFlags; + final int mAttributionChainId; DiscreteOpEvent(long noteTime, long noteDuration, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int opFlag) { + @AppOpsManager.OpFlags int opFlag, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { mNoteTime = noteTime; mNoteDuration = noteDuration; mUidState = uidState; mOpFlag = opFlag; + mAttributionFlags = attributionFlags; + mAttributionChainId = attributionChainId; } private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @@ -969,13 +989,19 @@ final class DiscreteRegistry { pw.print("-"); pw.print(flagsToString(mOpFlag)); pw.print("] at "); - date.setTime(mNoteTime); + date.setTime(discretizeTimeStamp(mNoteTime)); pw.print(sdf.format(date)); if (mNoteDuration != -1) { pw.print(" for "); - pw.print(mNoteDuration); + pw.print(discretizeDuration(mNoteDuration)); pw.print(" milliseconds "); } + if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) { + pw.print(" attribution flags="); + pw.print(mAttributionFlags); + pw.print(" with chainId="); + pw.print(mAttributionChainId); + } pw.println(); } @@ -984,6 +1010,12 @@ final class DiscreteRegistry { if (mNoteDuration != -1) { out.attributeLong(null, ATTR_NOTE_DURATION, mNoteDuration); } + if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) { + out.attributeInt(null, ATTR_ATTRIBUTION_FLAGS, mAttributionFlags); + } + if (mAttributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE) { + out.attributeInt(null, ATTR_CHAIN_ID, mAttributionChainId); + } out.attributeInt(null, ATTR_UID_STATE, mUidState); out.attributeInt(null, ATTR_FLAGS, mOpFlag); } @@ -1055,6 +1087,16 @@ final class DiscreteRegistry { return true; } + private static long discretizeTimeStamp(long timeStamp) { + return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; + + } + + private static long discretizeDuration(long duration) { + return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1) + / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; + } + void setDebugMode(boolean debugMode) { this.mDebugMode = debugMode; } diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 8b72be78a7f6..dd5df503d936 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -487,7 +487,8 @@ final class HistoricalRegistry { void incrementOpAccessedCount(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, - long accessTime) { + long accessTime, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -499,7 +500,7 @@ final class HistoricalRegistry { attributionTag, uidState, flags, 1); mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag, - flags, uidState, accessTime, -1); + flags, uidState, accessTime, -1, attributionFlags, attributionChainId); } } } @@ -521,7 +522,8 @@ final class HistoricalRegistry { void increaseOpAccessDuration(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, - long eventStartTime, long increment) { + long eventStartTime, long increment, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -532,7 +534,8 @@ final class HistoricalRegistry { System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName, attributionTag, uidState, flags, increment); mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag, - flags, uidState, eventStartTime, increment); + flags, uidState, eventStartTime, increment, attributionFlags, + attributionChainId); } } } diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java index 625a9223de59..c24cfadf61c7 100644 --- a/services/core/java/com/android/server/display/WifiDisplayController.java +++ b/services/core/java/com/android/server/display/WifiDisplayController.java @@ -594,11 +594,6 @@ final class WifiDisplayController implements DumpUtils.Dump { private void disconnect() { mDesiredDevice = null; - mWifiP2pManager = null; - if (null != mWifiP2pChannel) { - mWifiP2pChannel.close(); - mWifiP2pChannel = null; - } updateConnection(); } diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 1db06df42947..45529a2692bb 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -65,7 +65,7 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationManager; import android.location.LocationManagerInternal; -import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener; +import android.location.LocationManagerInternal.LocationPackageTagsListener; import android.location.LocationProvider; import android.location.LocationRequest; import android.location.LocationTime; @@ -93,6 +93,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; +import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.location.eventlog.LocationEventLog; @@ -264,7 +265,7 @@ public class LocationManagerService extends ILocationManager.Stub implements new CopyOnWriteArrayList<>(); @GuardedBy("mLock") - @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener; + @Nullable LocationPackageTagsListener mLocationTagsChangedListener; LocationManagerService(Context context, Injector injector) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); @@ -1375,32 +1376,28 @@ public class LocationManagerService extends ILocationManager.Stub implements refreshAppOpsRestrictions(UserHandle.USER_ALL); } - OnProviderLocationTagsChangeListener listener; - synchronized (mLock) { - listener = mOnProviderLocationTagsChangeListener; - } - - if (listener != null) { - if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags) - || !Objects.equals(oldState.identity, newState.identity)) { - if (oldState.identity != null) { - listener.onLocationTagsChanged( - new LocationManagerInternal.LocationTagInfo( - oldState.identity.getUid(), - oldState.identity.getPackageName(), - Collections.emptySet())); - } - if (newState.identity != null) { - ArraySet<String> attributionTags = new ArraySet<>( - newState.extraAttributionTags.size() + 1); - attributionTags.addAll(newState.extraAttributionTags); - attributionTags.add(newState.identity.getAttributionTag()); - - listener.onLocationTagsChanged( - new LocationManagerInternal.LocationTagInfo( - newState.identity.getUid(), - newState.identity.getPackageName(), - attributionTags)); + if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags) + || !Objects.equals(oldState.identity, newState.identity)) { + // since we're potentially affecting the tag lists for two different uids, acquire the + // lock to ensure providers cannot change while we're looping over the providers + // multiple times, which could lead to inconsistent results. + synchronized (mLock) { + LocationPackageTagsListener listener = mLocationTagsChangedListener; + if (listener != null) { + int oldUid = oldState.identity != null ? oldState.identity.getUid() : -1; + int newUid = newState.identity != null ? newState.identity.getUid() : -1; + if (oldUid != -1) { + PackageTagsList tags = calculateAppOpsLocationSourceTags(oldUid); + FgThread.getHandler().post( + () -> listener.onLocationPackageTagsChanged(oldUid, tags)); + } + // if the new app id is the same as the old app id, no need to invoke the + // listener twice, it's already been taken care of + if (newUid != -1 && newUid != oldUid) { + PackageTagsList tags = calculateAppOpsLocationSourceTags(newUid); + FgThread.getHandler().post( + () -> listener.onLocationPackageTagsChanged(newUid, tags)); + } } } } @@ -1448,6 +1445,31 @@ public class LocationManagerService extends ILocationManager.Stub implements userId); } + PackageTagsList calculateAppOpsLocationSourceTags(int uid) { + PackageTagsList.Builder builder = new PackageTagsList.Builder(); + for (LocationProviderManager manager : mProviderManagers) { + AbstractLocationProvider.State managerState = manager.getState(); + if (managerState.identity == null) { + continue; + } + if (managerState.identity.getUid() != uid) { + continue; + } + + builder.add(managerState.identity.getPackageName(), managerState.extraAttributionTags); + if (managerState.extraAttributionTags.isEmpty() + || managerState.identity.getAttributionTag() != null) { + builder.add(managerState.identity.getPackageName(), + managerState.identity.getAttributionTag()); + } else { + Log.e(TAG, manager.getName() + " provider has specified a null attribution tag and " + + "a non-empty set of extra attribution tags - dropping the null " + + "attribution tag"); + } + } + return builder.build(); + } + private class LocalService extends LocationManagerInternal { LocalService() {} @@ -1518,10 +1540,29 @@ public class LocationManagerService extends ILocationManager.Stub implements } @Override - public void setOnProviderLocationTagsChangeListener( - @Nullable OnProviderLocationTagsChangeListener listener) { + public void setLocationPackageTagsListener( + @Nullable LocationPackageTagsListener listener) { synchronized (mLock) { - mOnProviderLocationTagsChangeListener = listener; + mLocationTagsChangedListener = listener; + + // calculate initial tag list and send to listener + if (listener != null) { + ArraySet<Integer> uids = new ArraySet<>(mProviderManagers.size()); + for (LocationProviderManager manager : mProviderManagers) { + CallerIdentity identity = manager.getIdentity(); + if (identity != null) { + uids.add(identity.getUid()); + } + } + + for (int uid : uids) { + PackageTagsList tags = calculateAppOpsLocationSourceTags(uid); + if (!tags.isEmpty()) { + FgThread.getHandler().post( + () -> listener.onLocationPackageTagsChanged(uid, tags)); + } + } + } } } } diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java index 1da45bd49767..eb7b77a2234f 100644 --- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java @@ -263,10 +263,10 @@ public abstract class AbstractLocationProvider { } /** - * The current allowed state of this provider. + * The current state of the provider. */ - public final boolean isAllowed() { - return mInternalState.get().state.allowed; + public final State getState() { + return mInternalState.get().state; } /** @@ -277,13 +277,6 @@ public abstract class AbstractLocationProvider { } /** - * The current provider properties of this provider. - */ - public final @Nullable ProviderProperties getProperties() { - return mInternalState.get().state.properties; - } - - /** * Call this method to report a change in provider properties. */ protected void setProperties(@Nullable ProviderProperties properties) { @@ -291,13 +284,6 @@ public abstract class AbstractLocationProvider { } /** - * The current identity of this provider. - */ - public final @Nullable CallerIdentity getIdentity() { - return mInternalState.get().state.identity; - } - - /** * Call this method to report a change in the provider's identity. */ protected void setIdentity(@Nullable CallerIdentity identity) { 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 4f8b87b51294..8d335b83d99c 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -1407,12 +1407,16 @@ public class LocationProviderManager extends return mName; } + public AbstractLocationProvider.State getState() { + return mProvider.getState(); + } + public @Nullable CallerIdentity getIdentity() { - return mProvider.getIdentity(); + return mProvider.getState().identity; } public @Nullable ProviderProperties getProperties() { - return mProvider.getProperties(); + return mProvider.getState().properties; } public boolean hasProvider() { @@ -2403,7 +2407,7 @@ public class LocationProviderManager extends Preconditions.checkArgument(userId >= 0); boolean enabled = mState == STATE_STARTED - && mProvider.isAllowed() + && mProvider.getState().allowed && mSettingsHelper.isLocationEnabled(userId); int index = mEnabled.indexOfKey(userId); diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java index 021e8dbdb44e..22295fe7ba28 100644 --- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java @@ -21,9 +21,7 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.annotation.Nullable; import android.location.Location; import android.location.LocationResult; -import android.location.provider.ProviderProperties; import android.location.provider.ProviderRequest; -import android.location.util.identity.CallerIdentity; import android.os.Bundle; import com.android.internal.annotations.GuardedBy; @@ -32,7 +30,6 @@ import com.android.internal.util.Preconditions; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; -import java.util.Set; /** * Represents a location provider that may switch between a mock implementation and a real @@ -290,21 +287,21 @@ public class MockableLocationProvider extends AbstractLocationProvider { Preconditions.checkState(!Thread.holdsLock(mOwnerLock)); AbstractLocationProvider provider; + State providerState; synchronized (mOwnerLock) { provider = mProvider; - pw.println("allowed=" + isAllowed()); - CallerIdentity identity = getIdentity(); - if (identity != null) { - pw.println("identity=" + identity); - } - Set<String> extraAttributionTags = getExtraAttributionTags(); - if (!extraAttributionTags.isEmpty()) { - pw.println("extra attribution tags=" + extraAttributionTags); - } - ProviderProperties properties = getProperties(); - if (properties != null) { - pw.println("properties=" + properties); - } + providerState = getState(); + } + + pw.println("allowed=" + providerState.allowed); + if (providerState.identity != null) { + pw.println("identity=" + providerState.identity); + } + if (!providerState.extraAttributionTags.isEmpty()) { + pw.println("extra attribution tags=" + providerState.extraAttributionTags); + } + if (providerState.properties != null) { + pw.println("properties=" + providerState.properties); } if (provider != null) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b26485b5aad9..a3f3a3a503c8 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4785,7 +4785,7 @@ public class NotificationManagerService extends SystemService { } @Override - public String addAutomaticZenRule(AutomaticZenRule automaticZenRule) { + public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String pkg) { Objects.requireNonNull(automaticZenRule, "automaticZenRule is null"); Objects.requireNonNull(automaticZenRule.getName(), "Name is null"); if (automaticZenRule.getOwner() == null @@ -4794,6 +4794,7 @@ public class NotificationManagerService extends SystemService { "Rule must have a conditionproviderservice and/or configuration activity"); } Objects.requireNonNull(automaticZenRule.getConditionId(), "ConditionId is null"); + checkCallerIsSameApp(pkg); if (automaticZenRule.getZenPolicy() != null && automaticZenRule.getInterruptionFilter() != INTERRUPTION_FILTER_PRIORITY) { throw new IllegalArgumentException("ZenPolicy is only applicable to " @@ -4801,7 +4802,7 @@ public class NotificationManagerService extends SystemService { } enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule"); - return mZenModeHelper.addAutomaticZenRule(automaticZenRule, + return mZenModeHelper.addAutomaticZenRule(pkg, automaticZenRule, "addAutomaticZenRule"); } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 4cb6c3ba20d8..a98f113a9d57 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -307,7 +307,8 @@ public class ZenModeHelper { return null; } - public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) { + public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule, + String reason) { if (!isSystemRule(automaticZenRule)) { PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner()); if (component == null) { @@ -340,7 +341,7 @@ public class ZenModeHelper { } newConfig = mConfig.copy(); ZenRule rule = new ZenRule(); - populateZenRule(automaticZenRule, rule, true); + populateZenRule(pkg, automaticZenRule, rule, true); newConfig.automaticRules.put(rule.id, rule); if (setConfigLocked(newConfig, reason, rule.component, true)) { return rule.id; @@ -376,7 +377,7 @@ public class ZenModeHelper { ? AUTOMATIC_RULE_STATUS_ENABLED : AUTOMATIC_RULE_STATUS_DISABLED); } - populateZenRule(automaticZenRule, rule, false); + populateZenRule(rule.pkg, automaticZenRule, rule, false); return setConfigLocked(newConfig, reason, rule.component, true); } } @@ -585,7 +586,8 @@ public class ZenModeHelper { return null; } - private void populateZenRule(AutomaticZenRule automaticZenRule, ZenRule rule, boolean isNew) { + private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, + boolean isNew) { rule.name = automaticZenRule.getName(); rule.condition = null; rule.conditionId = automaticZenRule.getConditionId(); @@ -600,9 +602,7 @@ public class ZenModeHelper { rule.id = ZenModeConfig.newRuleId(); rule.creationTime = System.currentTimeMillis(); rule.component = automaticZenRule.getOwner(); - rule.pkg = (rule.component != null) - ? rule.component.getPackageName() - : rule.configurationActivity.getPackageName(); + rule.pkg = pkg; } if (rule.enabled != automaticZenRule.isEnabled()) { @@ -611,10 +611,13 @@ public class ZenModeHelper { } protected AutomaticZenRule createAutomaticZenRule(ZenRule rule) { - return new AutomaticZenRule(rule.name, rule.component, rule.configurationActivity, + AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, + rule.configurationActivity, rule.conditionId, rule.zenPolicy, NotificationManager.zenModeToInterruptionFilter(rule.zenMode), rule.enabled, rule.creationTime); + azr.setPackageName(rule.pkg); + return azr; } public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason) { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 9370b14341dc..5b2c80903ce5 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1000,16 +1000,15 @@ public class LauncherAppsService extends SystemService { intents[0].setSourceBounds(sourceBounds); // Replace theme for splash screen - final int splashScreenThemeResId = - mShortcutServiceInternal.getShortcutStartingThemeResId(getCallingUserId(), + final String splashScreenThemeResName = + mShortcutServiceInternal.getShortcutStartingThemeResName(getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId); - if (splashScreenThemeResId != 0) { + if (splashScreenThemeResName != null && !splashScreenThemeResName.isEmpty()) { if (startActivityOptions == null) { startActivityOptions = new Bundle(); } - startActivityOptions.putInt(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResId); + startActivityOptions.putString(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResName); } - return startShortcutIntentsAsPublisher( intents, packageName, featureId, startActivityOptions, targetUserId); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index da9fd3db9bee..a053bdde777c 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -627,7 +627,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0; - if (params.isStaged || isApex) { + if (isApex) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES) + == PackageManager.PERMISSION_DENIED + && mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) + == PackageManager.PERMISSION_DENIED) { + throw new SecurityException("Not allowed to perform APEX updates"); + } + } else if (params.isStaged) { mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, TAG); } @@ -1111,6 +1118,17 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mSilentUpdatePolicy.setAllowUnlimitedSilentUpdates(installerPackageName); } + /** + * Set the silent updates throttle time in seconds. + */ + @Override + public void setSilentUpdatesThrottleTime(long throttleTimeInSeconds) { + if (!isCalledBySystemOrShell(Binder.getCallingUid())) { + throw new SecurityException("Caller not allowed to set silent updates throttle time"); + } + mSilentUpdatePolicy.setSilentUpdatesThrottleTime(throttleTimeInSeconds); + } + private static int getSessionCount(SparseArray<PackageInstallerSession> sessions, int installerUid) { int count = 0; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index f9c63a948a86..49559f299fa0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -307,8 +307,8 @@ class PackageManagerShellCommand extends ShellCommand { return runLogVisibility(); case "bypass-staged-installer-check": return runBypassStagedInstallerCheck(); - case "allow-unlimited-silent-updates": - return runAllowUnlimitedSilentUpdates(); + case "set-silent-updates-policy": + return runSetSilentUpdatesPolicy(); default: { Boolean domainVerificationResult = mDomainVerificationShell.runCommand(this, cmd); @@ -3041,12 +3041,20 @@ class PackageManagerShellCommand extends ShellCommand { } } - private int runAllowUnlimitedSilentUpdates() { + private int runSetSilentUpdatesPolicy() { final PrintWriter pw = getOutPrintWriter(); String opt; + String installerPackageName = null; + Long throttleTimeInSeconds = null; boolean reset = false; while ((opt = getNextOption()) != null) { switch (opt) { + case "--allow-unlimited-silent-updates": + installerPackageName = getNextArgRequired(); + break; + case "--throttle-time": + throttleTimeInSeconds = Long.parseLong(getNextArgRequired()); + break; case "--reset": reset = true; break; @@ -3055,10 +3063,24 @@ class PackageManagerShellCommand extends ShellCommand { return -1; } } + if (throttleTimeInSeconds != null && throttleTimeInSeconds < 0) { + pw.println("Error: Invalid value for \"--throttle-time\":" + throttleTimeInSeconds); + return -1; + } - final String installerPackageName = reset ? null : getNextArgRequired(); try { - mInterface.getPackageInstaller().setAllowUnlimitedSilentUpdates(installerPackageName); + final IPackageInstaller installer = mInterface.getPackageInstaller(); + if (reset) { + installer.setAllowUnlimitedSilentUpdates(null /* installerPackageName */); + installer.setSilentUpdatesThrottleTime(-1 /* restore to the default */); + } else { + if (installerPackageName != null) { + installer.setAllowUnlimitedSilentUpdates(installerPackageName); + } + if (throttleTimeInSeconds != null) { + installer.setSilentUpdatesThrottleTime(throttleTimeInSeconds); + } + } } catch (RemoteException e) { pw.println("Failure [" + e.getClass().getName() + " - " @@ -3889,11 +3911,14 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" --enable: turn on debug logging (default)"); pw.println(" --disable: turn off debug logging"); pw.println(""); - pw.println(" allow-unlimited-silent-updates (--reset | <INSTALLER>)"); - pw.println(" Allows unlimited silent updated installation requests from the installer"); - pw.println(" without the throttle time."); - pw.println(" --reset: clear the allowed installer and tracks of silent updates in"); - pw.println(" the system."); + pw.println(" set-silent-updates-policy [--allow-unlimited-silent-updates <INSTALLER>]"); + pw.println(" [--throttle-time <SECONDS>] [--reset]"); + pw.println(" Sets the policies of the silent updates."); + pw.println(" --allow-unlimited-silent-updates: allows unlimited silent updated"); + pw.println(" installation requests from the installer without the throttle time."); + pw.println(" --throttle-time: update the silent updates throttle time in seconds."); + pw.println(" --reset: restore the installer and throttle time to the default, and"); + pw.println(" clear tracks of silent updates in the system."); pw.println(""); mDomainVerificationShell.printHelp(pw); pw.println(""); diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 1bd9e5eedb84..b4bd086af272 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -139,7 +139,7 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_BITMAP_PATH = "bitmap-path"; private static final String ATTR_ICON_URI = "icon-uri"; private static final String ATTR_LOCUS_ID = "locus-id"; - private static final String ATTR_SPLASH_SCREEN_THEME_ID = "splash-screen-theme-id"; + private static final String ATTR_SPLASH_SCREEN_THEME_NAME = "splash-screen-theme-name"; private static final String ATTR_PERSON_NAME = "name"; private static final String ATTR_PERSON_URI = "uri"; @@ -1799,7 +1799,7 @@ class ShortcutPackage extends ShortcutPackageItem { ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle()); ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId()); ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName()); - ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_ID, si.getStartingThemeResId()); + ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_NAME, si.getStartingThemeResName()); ShortcutService.writeAttr(out, ATTR_TEXT, si.getText()); ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId()); ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName()); @@ -2010,7 +2010,7 @@ class ShortcutPackage extends ShortcutPackageItem { String bitmapPath; String iconUri; final String locusIdString; - int splashScreenThemeResId; + String splashScreenThemeResName; int backupVersionCode; ArraySet<String> categories = null; ArrayList<Person> persons = new ArrayList<>(); @@ -2021,8 +2021,8 @@ class ShortcutPackage extends ShortcutPackageItem { title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID); titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME); - splashScreenThemeResId = ShortcutService.parseIntAttribute(parser, - ATTR_SPLASH_SCREEN_THEME_ID); + splashScreenThemeResName = ShortcutService.parseStringAttribute(parser, + ATTR_SPLASH_SCREEN_THEME_NAME); text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT); textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID); textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME); @@ -2117,7 +2117,7 @@ class ShortcutPackage extends ShortcutPackageItem { rank, extras, lastChangedTimestamp, flags, iconResId, iconResName, bitmapPath, iconUri, disabledReason, persons.toArray(new Person[persons.size()]), locusId, - splashScreenThemeResId); + splashScreenThemeResName); } private static Intent parseIntent(TypedXmlPullParser parser) diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java index c06f01a463ad..b86c50b23687 100644 --- a/services/core/java/com/android/server/pm/ShortcutParser.java +++ b/services/core/java/com/android/server/pm/ShortcutParser.java @@ -383,8 +383,11 @@ public class ShortcutParser { final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0); final int disabledMessageResId = sa.getResourceId( R.styleable.Shortcut_shortcutDisabledMessage, 0); - final int splashScreenTheme = sa.getResourceId( + final int splashScreenThemeResId = sa.getResourceId( R.styleable.Shortcut_splashScreenTheme, 0); + final String splashScreenThemeResName = splashScreenThemeResId != 0 + ? service.mContext.getResources().getResourceName(splashScreenThemeResId) + : null; if (TextUtils.isEmpty(id)) { Log.w(TAG, "android:shortcutId must be provided. activity=" + activity); @@ -407,7 +410,7 @@ public class ShortcutParser { rank, iconResId, enabled, - splashScreenTheme); + splashScreenThemeResName); } finally { sa.recycle(); } @@ -416,7 +419,7 @@ public class ShortcutParser { private static ShortcutInfo createShortcutFromManifest(ShortcutService service, @UserIdInt int userId, String id, String packageName, ComponentName activityComponent, int titleResId, int textResId, int disabledMessageResId, - int rank, int iconResId, boolean enabled, int splashScreenTheme) { + int rank, int iconResId, boolean enabled, @Nullable String splashScreenThemeResName) { final int flags = (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED) @@ -456,7 +459,7 @@ public class ShortcutParser { disabledReason, null /* persons */, null /* locusId */, - splashScreenTheme); + splashScreenThemeResName); } private static String parseCategory(ShortcutService service, AttributeSet attrs) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 5f1027797292..fcbf40e29933 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -3536,7 +3536,8 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public int getShortcutStartingThemeResId(int launcherUserId, + @Nullable + public String getShortcutStartingThemeResName(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId) { Objects.requireNonNull(callingPackage, "callingPackage"); @@ -3553,11 +3554,11 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage p = getUserShortcutsLocked(userId) .getPackageShortcutsIfExists(packageName); if (p == null) { - return 0; + return null; } final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); - return shortcutInfo != null ? shortcutInfo.getStartingThemeResId() : 0; + return shortcutInfo != null ? shortcutInfo.getStartingThemeResName() : null; } } diff --git a/services/core/java/com/android/server/pm/SilentUpdatePolicy.java b/services/core/java/com/android/server/pm/SilentUpdatePolicy.java index 117acab6d079..700f72cfbb94 100644 --- a/services/core/java/com/android/server/pm/SilentUpdatePolicy.java +++ b/services/core/java/com/android/server/pm/SilentUpdatePolicy.java @@ -33,7 +33,8 @@ import java.util.concurrent.TimeUnit; * in the {@link PackageInstallerSession}. */ public class SilentUpdatePolicy { - // A throttle time to prevent the installer from silently updating the same app repeatedly. + // The default throttle time to prevent the installer from silently updating the same app + // repeatedly. private static final long SILENT_UPDATE_THROTTLE_TIME_MS = TimeUnit.SECONDS.toMillis(30); // Map to the uptime timestamp for each installer and app of the silent update. @@ -44,6 +45,9 @@ public class SilentUpdatePolicy { @GuardedBy("mSilentUpdateInfos") private String mAllowUnlimitedSilentUpdatesInstaller; + @GuardedBy("mSilentUpdateInfos") + private long mSilentUpdateThrottleTimeMs = SILENT_UPDATE_THROTTLE_TIME_MS; + /** * Checks if the silent update is allowed by the given installer and app package name. * @@ -58,7 +62,11 @@ public class SilentUpdatePolicy { return true; } final long lastSilentUpdatedMs = getTimestampMs(installerPackageName, packageName); - return SystemClock.uptimeMillis() - lastSilentUpdatedMs > SILENT_UPDATE_THROTTLE_TIME_MS; + final long throttleTimeMs; + synchronized (mSilentUpdateInfos) { + throttleTimeMs = mSilentUpdateThrottleTimeMs; + } + return SystemClock.uptimeMillis() - lastSilentUpdatedMs > throttleTimeMs; } /** @@ -99,11 +107,25 @@ public class SilentUpdatePolicy { } } + /** + * Set the silent updates throttle time in seconds. + * + * @param throttleTimeInSeconds The throttle time to set, or <code>-1</code> to restore the + * value to the default. + */ + void setSilentUpdatesThrottleTime(long throttleTimeInSeconds) { + synchronized (mSilentUpdateInfos) { + mSilentUpdateThrottleTimeMs = throttleTimeInSeconds >= 0 + ? TimeUnit.SECONDS.toMillis(throttleTimeInSeconds) + : SILENT_UPDATE_THROTTLE_TIME_MS; + } + } + private void pruneLocked(long uptime) { final int size = mSilentUpdateInfos.size(); for (int i = size - 1; i >= 0; i--) { final long lastSilentUpdatedMs = mSilentUpdateInfos.valueAt(i); - if (uptime - lastSilentUpdatedMs > SILENT_UPDATE_THROTTLE_TIME_MS) { + if (uptime - lastSilentUpdatedMs > mSilentUpdateThrottleTimeMs) { mSilentUpdateInfos.removeAt(i); } } diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 94005b2c8361..22c370ef4dbe 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -33,14 +33,15 @@ import android.content.pm.ResolveInfo; import android.location.LocationManagerInternal; import android.net.Uri; import android.os.IBinder; +import android.os.PackageTagsList; import android.os.Process; import android.os.UserHandle; import android.service.voice.VoiceInteractionManagerInternal; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.function.DecFunction; @@ -52,9 +53,9 @@ import com.android.internal.util.function.TriFunction; import com.android.internal.util.function.UndecFunction; import com.android.server.LocalServices; +import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -67,11 +68,10 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat "android:activity_recognition_allow_listed_tags"; private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";"; - private static ArraySet<String> sExpectedTags = new ArraySet<>(new String[] { + private static final ArraySet<String> sExpectedTags = new ArraySet<>(new String[] { "awareness_provider", "activity_recognition_provider", "network_location_provider", "network_location_calibration", "fused_location_provider", "geofencer_provider"}); - @NonNull private final Object mLock = new Object(); @@ -96,13 +96,22 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat */ @GuardedBy("mLock - writes only - see above") @NonNull - private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>> mLocationTags = + private final ConcurrentHashMap<Integer, PackageTagsList> mLocationTags = new ConcurrentHashMap<>(); + // location tags can vary per uid - but we merge all tags under an app id into the final data + // structure above + @GuardedBy("mLock") + private final SparseArray<PackageTagsList> mPerUidLocationTags = new SparseArray<>(); + + // activity recognition currently only grabs tags from the APK manifest. we know that the + // manifest is the same for all users, so there's no need to track variations in tags across + // different users. if that logic ever changes, this might need to behave more like location + // tags above. @GuardedBy("mLock - writes only - see above") @NonNull - private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>> - mActivityRecognitionTags = new ConcurrentHashMap<>(); + private final ConcurrentHashMap<Integer, PackageTagsList> mActivityRecognitionTags = + new ConcurrentHashMap<>(); public AppOpsPolicy(@NonNull Context context) { mContext = context; @@ -112,13 +121,28 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat final LocationManagerInternal locationManagerInternal = LocalServices.getService( LocationManagerInternal.class); - locationManagerInternal.setOnProviderLocationTagsChangeListener((providerTagInfo) -> { - synchronized (mLock) { - updateAllowListedTagsForPackageLocked(providerTagInfo.getUid(), - providerTagInfo.getPackageName(), providerTagInfo.getTags(), - mLocationTags); - } - }); + locationManagerInternal.setLocationPackageTagsListener( + (uid, packageTagsList) -> { + synchronized (mLock) { + if (packageTagsList.isEmpty()) { + mPerUidLocationTags.remove(uid); + } else { + mPerUidLocationTags.set(uid, packageTagsList); + } + + int appId = UserHandle.getAppId(uid); + PackageTagsList.Builder appIdTags = new PackageTagsList.Builder(1); + int size = mPerUidLocationTags.size(); + for (int i = 0; i < size; i++) { + if (UserHandle.getAppId(mPerUidLocationTags.keyAt(i)) == appId) { + appIdTags.add(mPerUidLocationTags.valueAt(i)); + } + } + + updateAllowListedTagsForPackageLocked(appId, appIdTags.build(), + mLocationTags); + } + }); final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); @@ -306,95 +330,30 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat } final String tagsList = resolvedService.serviceInfo.metaData.getString( ACTIVITY_RECOGNITION_TAGS); - if (tagsList != null) { - final String[] tags = tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR); + if (!TextUtils.isEmpty(tagsList)) { + PackageTagsList packageTagsList = new PackageTagsList.Builder(1).add( + resolvedService.serviceInfo.packageName, + Arrays.asList(tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR))).build(); synchronized (mLock) { updateAllowListedTagsForPackageLocked( - resolvedService.serviceInfo.applicationInfo.uid, - resolvedService.serviceInfo.packageName, new ArraySet<>(tags), + UserHandle.getAppId(resolvedService.serviceInfo.applicationInfo.uid), + packageTagsList, mActivityRecognitionTags); } } } - private static void updateAllowListedTagsForPackageLocked(int uid, String packageName, - Set<String> allowListedTags, ConcurrentHashMap<Integer, ArrayMap<String, - ArraySet<String>>> datastore) { - final int appId = UserHandle.getAppId(uid); - // We make a copy of the per UID state to limit our mutation to one - // operation in the underlying concurrent data structure. - ArrayMap<String, ArraySet<String>> appIdTags = datastore.get(appId); - if (appIdTags != null) { - appIdTags = new ArrayMap<>(appIdTags); - } - - ArraySet<String> packageTags = (appIdTags != null) ? appIdTags.get(packageName) : null; - if (packageTags != null) { - packageTags = new ArraySet<>(packageTags); - } - - if (allowListedTags != null && !allowListedTags.isEmpty()) { - if (packageTags != null) { - packageTags.clear(); - packageTags.addAll(allowListedTags); - } else { - packageTags = new ArraySet<>(allowListedTags); - } - if (appIdTags == null) { - appIdTags = new ArrayMap<>(); - } - - // Remove any invalid tags - boolean nullRemoved = packageTags.remove(null); - boolean nullStrRemoved = packageTags.remove("null"); - boolean emptyRemoved = packageTags.remove(""); - if (nullRemoved || nullStrRemoved || emptyRemoved) { - Log.e(LOG_TAG, "Attempted to add invalid source attribution tag, removed " - + "null: " + nullRemoved + " removed \"null\": " + nullStrRemoved - + " removed empty string: " + emptyRemoved); - } - - appIdTags.put(packageName, packageTags); - datastore.put(appId, appIdTags); - } else if (appIdTags != null) { - appIdTags.remove(packageName); - if (!appIdTags.isEmpty()) { - datastore.put(appId, appIdTags); - } else { - datastore.remove(appId); - } - } + private static void updateAllowListedTagsForPackageLocked(int appId, + PackageTagsList packageTagsList, + ConcurrentHashMap<Integer, PackageTagsList> datastore) { + datastore.put(appId, packageTagsList); } private static boolean isDatasourceAttributionTag(int uid, @NonNull String packageName, - @NonNull String attributionTag, @NonNull Map<Integer, ArrayMap<String, - ArraySet<String>>> mappedOps) { + @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps) { // Only a single lookup from the underlying concurrent data structure - final int appId = UserHandle.getAppId(uid); - final ArrayMap<String, ArraySet<String>> appIdTags = mappedOps.get(appId); - if (appIdTags != null) { - final ArraySet<String> packageTags = appIdTags.get(packageName); - if (packageTags != null && packageTags.contains(attributionTag)) { - if (packageName.equals("com.google.android.gms") - && !sExpectedTags.contains(attributionTag)) { - Log.i("AppOpsDebugRemapping", packageName + " tag " - + attributionTag + " in " + packageTags); - } - return true; - } - if (packageName.equals("com.google.android.gms") - && sExpectedTags.contains(attributionTag)) { - Log.i("AppOpsDebugRemapping", packageName + " tag " + attributionTag - + " NOT in " + packageTags); - } - } else { - if (packageName.equals("com.google.android.gms")) { - Log.i("AppOpsDebugRemapping", "no package tags for uid " + uid - + " package " + packageName); - } - - } - return false; + final PackageTagsList appIdTags = mappedOps.get(UserHandle.getAppId(uid)); + return appIdTags != null && appIdTags.contains(packageName, attributionTag); } private static int resolveLocationOp(int code) { diff --git a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java index e1e6195ad260..f653e4b26438 100644 --- a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java +++ b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java @@ -30,6 +30,9 @@ public final class ProcfsMemoryUtil { "RssAnon:", "VmSwap:" }; + private static final String[] VMSTAT_KEYS = new String[] { + "oom_kill" + }; private ProcfsMemoryUtil() {} @@ -99,4 +102,22 @@ public final class ProcfsMemoryUtil { public int anonRssInKilobytes; public int swapInKilobytes; } + + /** Reads and parses selected entries of /proc/vmstat. */ + @Nullable + static VmStat readVmStat() { + long[] vmstat = new long[VMSTAT_KEYS.length]; + vmstat[0] = -1; + Process.readProcLines("/proc/vmstat", VMSTAT_KEYS, vmstat); + if (vmstat[0] == -1) { + return null; + } + VmStat result = new VmStat(); + result.oomKillCount = (int) vmstat[0]; + return result; + } + + static final class VmStat { + public int oomKillCount; + } } 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 dc868b325900..cd0ce2bd46a7 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -544,6 +544,8 @@ public class StatsPullAtomService extends SystemService { return pullProcessDmabufMemory(atomTag, data); case FrameworkStatsLog.SYSTEM_MEMORY: return pullSystemMemory(atomTag, data); + case FrameworkStatsLog.VMSTAT: + return pullVmStat(atomTag, data); case FrameworkStatsLog.TEMPERATURE: synchronized (mTemperatureLock) { return pullTemperatureLocked(atomTag, data); @@ -842,6 +844,7 @@ public class StatsPullAtomService extends SystemService { registerProcessSystemIonHeapSize(); registerSystemMemory(); registerProcessDmabufMemory(); + registerVmStat(); registerTemperature(); registerCoolingDevice(); registerBinderCallsStats(); @@ -2273,6 +2276,27 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } + private void registerVmStat() { + int tagId = FrameworkStatsLog.VMSTAT; + mStatsManager.setPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, + mStatsCallbackImpl + ); + } + + int pullVmStat(int atomTag, List<StatsEvent> pulledData) { + ProcfsMemoryUtil.VmStat vmStat = ProcfsMemoryUtil.readVmStat(); + if (vmStat != null) { + pulledData.add( + FrameworkStatsLog.buildStatsEvent( + atomTag, + vmStat.oomKillCount)); + } + return StatsManager.PULL_SUCCESS; + } + private void registerTemperature() { int tagId = FrameworkStatsLog.TEMPERATURE; mStatsManager.setPullAtomCallback( diff --git a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java index b695150d9ba3..e8ce4f336caa 100644 --- a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java +++ b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java @@ -16,6 +16,7 @@ package com.android.server.vibrator; +import android.content.Context; import android.os.VibrationEffect; import android.os.VibratorInfo; @@ -26,17 +27,16 @@ import java.util.List; final class DeviceVibrationEffectAdapter implements VibrationEffectAdapters.EffectAdapter<VibratorInfo> { - /** Duration of each step created to simulate a ramp segment. */ - private static final int RAMP_STEP_DURATION_MILLIS = 5; - private final List<VibrationEffectAdapters.SegmentsAdapter<VibratorInfo>> mSegmentAdapters; - DeviceVibrationEffectAdapter() { + DeviceVibrationEffectAdapter(Context context) { mSegmentAdapters = Arrays.asList( // TODO(b/167947076): add filter that removes unsupported primitives // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback - new RampToStepAdapter(RAMP_STEP_DURATION_MILLIS), - new StepToRampAdapter(), + new RampToStepAdapter(context.getResources().getInteger( + com.android.internal.R.integer.config_vibrationWaveformRampStepDuration)), + new StepToRampAdapter(context.getResources().getInteger( + com.android.internal.R.integer.config_vibrationWaveformRampDownDuration)), new ClippingAmplitudeAndFrequencyAdapter() ); } diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java index 1e05bdbdf082..64624a28c5da 100644 --- a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java +++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java @@ -29,7 +29,7 @@ import java.util.List; /** * Adapter that converts ramp segments that to a sequence of fixed step segments. * - * <p>This leaves the list unchanged if the device have compose PWLE capability. + * <p>This leaves the list unchanged if the device has compose PWLE capability. */ final class RampToStepAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> { diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java index f78df9208fbd..d439b94ec2fc 100644 --- a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java +++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java @@ -27,39 +27,221 @@ import java.util.List; /** * Adapter that converts step segments that should be handled as PWLEs to ramp segments. * - * <p>This leaves the list unchanged if the device do not have compose PWLE capability. + * <p>Each replaced {@link StepSegment} will be represented by a {@link RampSegment} with same + * start and end amplitudes/frequencies, which can then be converted to PWLE compositions. This + * adapter leaves the segments unchanged if the device doesn't have the PWLE composition capability. + * + * <p>This adapter also applies the ramp down duration config on devices with PWLE support. This + * prevents the device from ringing when it cannot handle abrupt changes between ON and OFF states. + * This will not change other types of abrupt amplitude changes in the original effect. + * + * <p>The effect overall duration is preserved by this transformation. Waveforms with ON/OFF + * segments are handled gracefully by the ramp down changes. Each OFF segment preceded by an ON + * segment will be shortened, and a ramp down will be added to the transition between ON and OFF. + * The ramps can be shorter than the configured duration in order to preserve the waveform timings, + * but they will still soften the ringing effect. */ final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> { + + private final int mRampDownDuration; + + StepToRampAdapter(int rampDownDuration) { + mRampDownDuration = rampDownDuration; + } + @Override public int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info) { if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) { - // The vibrator do not have PWLE capability, so keep the segments unchanged. + // The vibrator does not have PWLE capability, so keep the segments unchanged. return repeatIndex; } + convertStepsToRamps(segments); + int newRepeatIndex = addRampDownToZeroAmplitudeSegments(segments, repeatIndex); + newRepeatIndex = addRampDownToLoop(segments, newRepeatIndex); + return newRepeatIndex; + } + + private void convertStepsToRamps(List<VibrationEffectSegment> segments) { int segmentCount = segments.size(); + if (mRampDownDuration > 0) { + // Convert all steps to ramps if the device requires ramp down. + for (int i = 0; i < segmentCount; i++) { + if (isStep(segments.get(i))) { + segments.set(i, apply((StepSegment) segments.get(i))); + } + } + return; + } // Convert steps that require frequency control to ramps. for (int i = 0; i < segmentCount; i++) { VibrationEffectSegment segment = segments.get(i); - if ((segment instanceof StepSegment) - && ((StepSegment) segment).getFrequency() != 0) { + if (isStep(segment) && ((StepSegment) segment).getFrequency() != 0) { segments.set(i, apply((StepSegment) segment)); } } // Convert steps that are next to ramps to also become ramps, so they can be composed // together in the same PWLE waveform. - for (int i = 1; i < segmentCount; i++) { + for (int i = 0; i < segmentCount; i++) { if (segments.get(i) instanceof RampSegment) { - for (int j = i - 1; j >= 0 && (segments.get(j) instanceof StepSegment); j--) { + for (int j = i - 1; j >= 0 && isStep(segments.get(j)); j--) { segments.set(j, apply((StepSegment) segments.get(j))); } + for (int j = i + 1; j < segmentCount && isStep(segments.get(j)); j++) { + segments.set(j, apply((StepSegment) segments.get(j))); + } + } + } + } + + /** + * This will add a ramp to zero as follows: + * + * <ol> + * <li>Remove the {@link VibrationEffectSegment} that starts and ends at zero amplitude + * and follows a segment that ends at non-zero amplitude; + * <li>Add a ramp down to zero starting at the previous segment end amplitude and frequency, + * with min between the removed segment duration and the configured ramp down duration; + * <li>Add a zero amplitude segment following the ramp with the remaining duration, if + * necessary; + * </ol> + */ + private int addRampDownToZeroAmplitudeSegments(List<VibrationEffectSegment> segments, + int repeatIndex) { + if (mRampDownDuration <= 0) { + // Nothing to do, no ramp down duration configured. + return repeatIndex; + } + int newRepeatIndex = repeatIndex; + int newSegmentCount = segments.size(); + for (int i = 1; i < newSegmentCount; i++) { + if (!isOffRampSegment(segments.get(i)) + || !endsWithNonZeroAmplitude(segments.get(i - 1))) { + continue; + } + + // We know the previous segment is a ramp that ends at non-zero amplitude. + float previousAmplitude = ((RampSegment) segments.get(i - 1)).getEndAmplitude(); + float previousFrequency = ((RampSegment) segments.get(i - 1)).getEndFrequency(); + RampSegment ramp = (RampSegment) segments.get(i); + + if (ramp.getDuration() <= mRampDownDuration) { + // Replace the zero amplitude segment with a ramp down of same duration, to + // preserve waveform timings and still soften the transition to zero. + segments.set(i, createRampDown(previousAmplitude, previousFrequency, + ramp.getDuration())); + } else { + // Make the zero amplitude segment shorter, to preserve waveform timings, and add a + // ramp down to zero segment right before it. + segments.set(i, updateDuration(ramp, ramp.getDuration() - mRampDownDuration)); + segments.add(i, createRampDown(previousAmplitude, previousFrequency, + mRampDownDuration)); + if (repeatIndex > i) { + newRepeatIndex++; + } + i++; + newSegmentCount++; + } + } + return newRepeatIndex; + } + + /** + * This will add a ramp to zero at the repeating index of the given effect, if set, only if + * the last segment ends at a non-zero amplitude and the repeating segment starts and ends at + * zero amplitude. The update is described as: + * + * <ol> + * <li>Add a ramp down to zero following the last segment, with the min between the + * removed segment duration and the configured ramp down duration; + * <li>Skip the zero-amplitude segment by incrementing the repeat index, splitting it if + * necessary to skip the correct amount; + * </ol> + */ + private int addRampDownToLoop(List<VibrationEffectSegment> segments, int repeatIndex) { + if (repeatIndex < 0) { + // Non-repeating compositions should remain unchanged so duration will be preserved. + return repeatIndex; + } + + int segmentCount = segments.size(); + if (mRampDownDuration <= 0 || !endsWithNonZeroAmplitude(segments.get(segmentCount - 1))) { + // Nothing to do, no ramp down duration configured or composition already ends at zero. + return repeatIndex; + } + + // We know the last segment is a ramp that ends at non-zero amplitude. + RampSegment lastRamp = (RampSegment) segments.get(segmentCount - 1); + float previousAmplitude = lastRamp.getEndAmplitude(); + float previousFrequency = lastRamp.getEndFrequency(); + + if (isOffRampSegment(segments.get(repeatIndex))) { + // Repeating from a non-zero to a zero amplitude segment, we know the next segment is a + // ramp with zero amplitudes. + RampSegment nextRamp = (RampSegment) segments.get(repeatIndex); + + if (nextRamp.getDuration() <= mRampDownDuration) { + // Skip the zero amplitude segment and append a ramp down of same duration to the + // end of the composition, to preserve waveform timings and still soften the + // transition to zero. + // This will update the waveform as follows: + // R R+1 + // | ____ | ____ + // _|_/ => __|/ \ + segments.add(createRampDown(previousAmplitude, previousFrequency, + nextRamp.getDuration())); + repeatIndex++; + } else { + // Append a ramp down to the end of the composition, split the zero amplitude + // segment and start repeating from the second half, to preserve waveform timings. + // This will update the waveform as follows: + // R R+1 + // | ____ | ____ + // _|__/ => __|_/ \ + segments.add(createRampDown(previousAmplitude, previousFrequency, + mRampDownDuration)); + segments.set(repeatIndex, updateDuration(nextRamp, + nextRamp.getDuration() - mRampDownDuration)); + segments.add(repeatIndex, updateDuration(nextRamp, mRampDownDuration)); + repeatIndex++; } } + return repeatIndex; } - private RampSegment apply(StepSegment segment) { + private static RampSegment apply(StepSegment segment) { return new RampSegment(segment.getAmplitude(), segment.getAmplitude(), segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration()); } + + private static RampSegment createRampDown(float amplitude, float frequency, long duration) { + return new RampSegment(amplitude, /* endAmplitude= */ 0, frequency, frequency, + (int) duration); + } + + private static RampSegment updateDuration(RampSegment ramp, long newDuration) { + return new RampSegment(ramp.getStartAmplitude(), ramp.getEndAmplitude(), + ramp.getStartFrequency(), ramp.getEndFrequency(), (int) newDuration); + } + + private static boolean isStep(VibrationEffectSegment segment) { + return segment instanceof StepSegment; + } + + /** Returns true if the segment is a ramp that starts and ends at zero amplitude. */ + private static boolean isOffRampSegment(VibrationEffectSegment segment) { + if (segment instanceof RampSegment) { + RampSegment ramp = (RampSegment) segment; + return ramp.getStartAmplitude() == 0 && ramp.getEndAmplitude() == 0; + } + return false; + } + + private static boolean endsWithNonZeroAmplitude(VibrationEffectSegment segment) { + if (segment instanceof RampSegment) { + return ((RampSegment) segment).getEndAmplitude() != 0; + } + return false; + } } diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index e3672f4d497c..150fde99b706 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -95,8 +95,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private final WorkSource mWorkSource = new WorkSource(); private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStatsService; - private final DeviceVibrationEffectAdapter mDeviceEffectAdapter = - new DeviceVibrationEffectAdapter(); + private final DeviceVibrationEffectAdapter mDeviceEffectAdapter; private final Vibration mVibration; private final VibrationCallbacks mCallbacks; private final SparseArray<VibratorController> mVibrators = new SparseArray<>(); @@ -104,10 +103,11 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private volatile boolean mForceStop; - VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators, - PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService, - VibrationCallbacks callbacks) { + VibrationThread(Vibration vib, DeviceVibrationEffectAdapter effectAdapter, + SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock, + IBatteryStats batteryStatsService, VibrationCallbacks callbacks) { mVibration = vib; + mDeviceEffectAdapter = effectAdapter; mCallbacks = callbacks; mWakeLock = wakeLock; mWorkSource.set(vib.uid); diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 06a5077bec82..2f0ed19d867a 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -132,6 +132,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private final VibrationSettings mVibrationSettings; private final VibrationScaler mVibrationScaler; private final InputDeviceDelegate mInputDeviceDelegate; + private final DeviceVibrationEffectAdapter mDeviceVibrationEffectAdapter; private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override @@ -175,6 +176,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mVibrationSettings = new VibrationSettings(mContext, mHandler); mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings); mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler); + mDeviceVibrationEffectAdapter = new DeviceVibrationEffectAdapter(mContext); VibrationCompleteListener listener = new VibrationCompleteListener(this); mNativeWrapper = injector.getNativeWrapper(); @@ -512,8 +514,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return Vibration.Status.FORWARDED_TO_INPUT_DEVICES; } - VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock, - mBatteryStatsService, mVibrationCallbacks); + VibrationThread vibThread = new VibrationThread(vib, mDeviceVibrationEffectAdapter, + mVibrators, mWakeLock, mBatteryStatsService, mVibrationCallbacks); if (mCurrentVibration == null) { return startVibrationThreadLocked(vibThread); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 71e31c3a10df..ac0665a37c0b 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -703,7 +703,7 @@ final class AccessibilityController { Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation) + " displayId: " + displayContent.getDisplayId()); } - mMagnifedViewport.onRotationChanged(displayContent.getPendingTransaction()); + mMagnifedViewport.onRotationChanged(); mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED); } @@ -858,7 +858,7 @@ final class AccessibilityController { private final RectF mTempRectF = new RectF(); - private final Point mTempPoint = new Point(); + private final Point mScreenSize = new Point(); private final Matrix mTempMatrix = new Matrix(); @@ -887,8 +887,8 @@ final class AccessibilityController { if (mDisplayContext.getResources().getConfiguration().isScreenRound()) { mCircularPath = new Path(); - mDisplay.getRealSize(mTempPoint); - final int centerXY = mTempPoint.x / 2; + mDisplay.getRealSize(mScreenSize); + final int centerXY = mScreenSize.x / 2; mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW); } else { mCircularPath = null; @@ -917,9 +917,9 @@ final class AccessibilityController { } void recomputeBounds() { - mDisplay.getRealSize(mTempPoint); - final int screenWidth = mTempPoint.x; - final int screenHeight = mTempPoint.y; + mDisplay.getRealSize(mScreenSize); + final int screenWidth = mScreenSize.x; + final int screenHeight = mScreenSize.y; mMagnificationRegion.set(0, 0, 0, 0); final Region availableBounds = mTempRegion1; @@ -1052,7 +1052,7 @@ final class AccessibilityController { || windowType == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; } - void onRotationChanged(SurfaceControl.Transaction t) { + void onRotationChanged() { // If we are showing the magnification border, hide it immediately so // the user does not see strange artifacts during rotation. The screenshot // used for rotation already has the border. After the rotation is complete @@ -1066,7 +1066,7 @@ final class AccessibilityController { mHandler.sendMessageDelayed(message, delay); } recomputeBounds(); - mWindow.updateSize(t); + mWindow.updateSize(); } void setMagnifiedRegionBorderShown(boolean shown, boolean animate) { @@ -1148,9 +1148,9 @@ final class AccessibilityController { /* ignore */ } mSurfaceControl = surfaceControl; - mDisplay.getRealSize(mTempPoint); + mDisplay.getRealSize(mScreenSize); mBlastBufferQueue = new BLASTBufferQueue(SURFACE_TITLE, mSurfaceControl, - mTempPoint.x, mTempPoint.y, PixelFormat.RGBA_8888); + mScreenSize.x, mScreenSize.y, PixelFormat.RGBA_8888); final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); final int layer = @@ -1224,10 +1224,11 @@ final class AccessibilityController { } } - void updateSize(SurfaceControl.Transaction t) { + void updateSize() { synchronized (mService.mGlobalLock) { - mDisplay.getRealSize(mTempPoint); - t.setBufferSize(mSurfaceControl, mTempPoint.x, mTempPoint.y); + mDisplay.getRealSize(mScreenSize); + mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y, + PixelFormat.RGBA_8888); invalidate(mDirtyRect); } } @@ -1296,8 +1297,8 @@ final class AccessibilityController { pw.println(prefix + " mBounds= " + mBounds + " mDirtyRect= " + mDirtyRect - + " mWidth= " + mSurfaceControl.getWidth() - + " mHeight= " + mSurfaceControl.getHeight()); + + " mWidth= " + mScreenSize.x + + " mHeight= " + mScreenSize.y); } private final class AnimationController extends Handler { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 64d683e1e74d..daf693322b66 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -311,6 +311,7 @@ import android.view.animation.Animation; import android.window.IRemoteTransition; import android.window.SizeConfigurationBuckets; import android.window.SplashScreen; +import android.window.SplashScreenView; import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.TaskSnapshot; import android.window.WindowContainerToken; @@ -728,25 +729,29 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe boolean startingMoved; boolean mHandleExitSplashScreen; - @TransferSplashScreenState int mTransferringSplashScreenState = - TRANSFER_SPLASH_SCREEN_IDLE; + @TransferSplashScreenState + int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE; - // Idle, can be triggered to do transfer if needed. + /** Idle, can be triggered to do transfer if needed. */ static final int TRANSFER_SPLASH_SCREEN_IDLE = 0; - // requesting a copy from shell. + + /** Requesting a copy from shell. */ static final int TRANSFER_SPLASH_SCREEN_COPYING = 1; - // attach the splash screen view to activity. + + /** Attach the splash screen view to activity. */ static final int TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT = 2; - // client has taken over splash screen view. + + /** Client has taken over splash screen view. */ static final int TRANSFER_SPLASH_SCREEN_FINISH = 3; - @IntDef(prefix = { "TRANSFER_SPLASH_SCREEN_" }, value = { + @IntDef(prefix = {"TRANSFER_SPLASH_SCREEN_"}, value = { TRANSFER_SPLASH_SCREEN_IDLE, TRANSFER_SPLASH_SCREEN_COPYING, TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT, TRANSFER_SPLASH_SCREEN_FINISH, }) - @interface TransferSplashScreenState {} + @interface TransferSplashScreenState { + } // How long we wait until giving up transfer splash screen. private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000; @@ -2215,6 +2220,23 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe removeStartingWindowAnimation(false /* prepareAnimation */); } + /** + * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} it should clean up any + * remaining reference to this {@link ActivityRecord}'s splash screen. + * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int) + * @see SplashScreenView#remove() + */ + void cleanUpSplashScreen() { + // We only clean up the splash screen if we were supposed to handle it. If it was + // transferred to another activity, the next one will handle the clean up. + if (mHandleExitSplashScreen && !startingMoved + && (mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH + || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_IDLE)) { + ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Cleaning splash screen token=%s", this); + mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask()); + } + } + void removeStartingWindow() { removeStartingWindowAnimation(true /* prepareAnimation */); } @@ -3370,6 +3392,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe task.cleanUpActivityReferences(this); clearLastParentBeforePip(); + // Clean up the splash screen if it was still displayed. + cleanUpSplashScreen(); + deferRelaunchUntilPaused = false; frozenBeforeDestroy = false; @@ -7628,10 +7653,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe if (getUid() == SYSTEM_UID) { return false; } - // Do not sandbox to activity window bounds if the feature is disabled. - if (mDisplayContent != null && !mDisplayContent.sandboxDisplayApis()) { - return false; - } // Never apply sandboxing to an app that should be explicitly excluded from the config. if (info != null && info.neverSandboxDisplayApis()) { return false; diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index eaebb6f1ce74..4acadb21b5e3 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -623,7 +623,11 @@ public class AppTransitionController { siblings.add(current); boolean canPromote = true; - if (parent == null || !parent.canCreateRemoteAnimationTarget()) { + if (parent == null || !parent.canCreateRemoteAnimationTarget() + // We cannot promote the animation on Task's parent when the task is in + // clearing task in case the animating get stuck when performing the opening + // task that behind it. + || (current.asTask() != null && current.asTask().mInRemoveTask)) { canPromote = false; } else { // In case a descendant of the parent belongs to the other group, we cannot promote diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 81992d8934ed..e0bae9d8de32 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -358,13 +358,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean mIsSizeForced = false; /** - * Overridden display size and metrics to activity window bounds. Set via - * "adb shell wm set-sandbox-display-apis". Default to true, since only disable for debugging. - * @see WindowManagerService#setSandboxDisplayApis(int, boolean) - */ - private boolean mSandboxDisplayApis = true; - - /** * Overridden display density for current user. Initialized with {@link #mInitialDisplayDensity} * but can be set from Settings or via shell command "adb shell wm density". * @see WindowManagerService#setForcedDisplayDensityForUser(int, int, int) @@ -5810,21 +5803,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return true; } - /** - * Sets if Display APIs should be sandboxed to the activity window bounds. - */ - void setSandboxDisplayApis(boolean sandboxDisplayApis) { - mSandboxDisplayApis = sandboxDisplayApis; - } - - /** - * Returns {@code true} is Display APIs should be sandboxed to the activity window bounds, - * {@code false} otherwise. Default to true, unless set for debugging purposes. - */ - boolean sandboxDisplayApis() { - return mSandboxDisplayApis; - } - /** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */ class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 0112f797d937..f8238c1d154a 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -48,6 +48,7 @@ import android.os.Trace; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; +import android.view.Display; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -163,16 +164,27 @@ class KeyguardController { aodShowing ? 1 : 0, mKeyguardGoingAway ? 1 : 0, "setKeyguardShown"); + + // Update the task snapshot if the screen will not be turned off. To make sure that the + // unlocking animation can animate consistent content. The conditions are: + // - Either AOD or keyguard changes to be showing. So if the states change individually, + // the later one can be skipped to avoid taking snapshot again. While it still accepts + // if both of them change to show at the same time. + // - Keyguard was not going away. Because if it was, the closing transition is able to + // handle the snapshot. + // - The display state is ON. Because if AOD is not on or pulsing, the display state will + // be OFF or DOZE (the path of screen off may have handled it). + if (((aodShowing ^ keyguardShowing) || (aodShowing && aodChanged && keyguardChanged)) + && !mKeyguardGoingAway && Display.isOnState( + mRootWindowContainer.getDefaultDisplay().getDisplayInfo().state)) { + mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY); + } + mKeyguardShowing = keyguardShowing; mAodShowing = aodShowing; if (aodChanged) { // Ensure the new state takes effect. mWindowManager.mWindowPlacerLocked.performSurfacePlacement(); - // If the device can enter AOD and keyguard at the same time, the screen will not be - // turned off, so the snapshot needs to be refreshed when these states are changed. - if (aodShowing && keyguardShowing && keyguardChanged) { - mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY); - } } if (keyguardChanged) { diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index eb7087cbc722..7174e68b06f4 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -21,6 +21,7 @@ import android.content.Context; import android.graphics.Color; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -104,20 +105,12 @@ final class LetterboxConfiguration { * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and * the framework implementation will be used to determine the aspect ratio. */ + @VisibleForTesting void setFixedOrientationLetterboxAspectRatio(float aspectRatio) { mFixedOrientationLetterboxAspectRatio = aspectRatio; } /** - * Resets the aspect ratio of letterbox for fixed orientation to {@link - * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}. - */ - void resetFixedOrientationLetterboxAspectRatio() { - mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat( - com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio); - } - - /** * Gets the aspect ratio of letterbox for fixed orientation. */ float getFixedOrientationLetterboxAspectRatio() { @@ -125,25 +118,6 @@ final class LetterboxConfiguration { } /** - * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0, - * both it and a value of {@link - * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and - * and corners of the activity won't be rounded. - */ - void setLetterboxActivityCornersRadius(int cornersRadius) { - mLetterboxActivityCornersRadius = cornersRadius; - } - - /** - * Resets corners raidus for activities presented in the letterbox mode to {@link - * com.android.internal.R.integer.config_letterboxActivityCornersRadius}. - */ - void resetLetterboxActivityCornersRadius() { - mLetterboxActivityCornersRadius = mContext.getResources().getInteger( - com.android.internal.R.integer.config_letterboxActivityCornersRadius); - } - - /** * Whether corners of letterboxed activities are rounded. */ boolean isLetterboxActivityCornersRounded() { @@ -166,25 +140,6 @@ final class LetterboxConfiguration { return mLetterboxBackgroundColor; } - - /** - * Sets color of letterbox background which is used when {@link - * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as - * fallback for other backfround types. - */ - void setLetterboxBackgroundColor(Color color) { - mLetterboxBackgroundColor = color; - } - - /** - * Resets color of letterbox background to {@link - * com.android.internal.R.color.config_letterboxBackgroundColor}. - */ - void resetLetterboxBackgroundColor() { - mLetterboxBackgroundColor = Color.valueOf(mContext.getResources().getColor( - com.android.internal.R.color.config_letterboxBackgroundColor)); - } - /** * Gets {@link LetterboxBackgroundType} specified in {@link * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command. @@ -194,19 +149,6 @@ final class LetterboxConfiguration { return mLetterboxBackgroundType; } - /** Sets letterbox background type. */ - void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) { - mLetterboxBackgroundType = backgroundType; - } - - /** - * Resets cletterbox background type to {@link - * com.android.internal.R.integer.config_letterboxBackgroundType}. - */ - void resetLetterboxBackgroundType() { - mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext); - } - /** Returns a string representing the given {@link LetterboxBackgroundType}. */ static String letterboxBackgroundTypeToString( @LetterboxBackgroundType int backgroundType) { @@ -236,27 +178,6 @@ final class LetterboxConfiguration { } /** - * Overrides alpha of a black scrim shown over wallpaper for {@link - * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}. - * - * <p>If given value is < 0 or >= 1, both it and a value of {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored - * and 0.0 (transparent) is instead. - */ - void setLetterboxBackgroundWallpaperDarkScrimAlpha(float alpha) { - mLetterboxBackgroundWallpaperDarkScrimAlpha = alpha; - } - - /** - * Resets alpha of a black scrim shown over wallpaper letterbox background to {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha}. - */ - void resetLetterboxBackgroundWallpaperDarkScrimAlpha() { - mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat( - com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha); - } - - /** * Gets alpha of a black scrim shown over wallpaper letterbox background. */ float getLetterboxBackgroundWallpaperDarkScrimAlpha() { @@ -264,28 +185,6 @@ final class LetterboxConfiguration { } /** - * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in - * {@link mLetterboxBackgroundType}. - * - * <p> If given value <= 0, both it and a value of {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored - * and 0 is used instead. - */ - void setLetterboxBackgroundWallpaperBlurRadius(int radius) { - mLetterboxBackgroundWallpaperBlurRadius = radius; - } - - /** - * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link - * mLetterboxBackgroundType} to {@link - * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}. - */ - void resetLetterboxBackgroundWallpaperBlurRadius() { - mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius); - } - - /** * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link * mLetterboxBackgroundType}. */ @@ -312,17 +211,9 @@ final class LetterboxConfiguration { * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and * central position (0.5) is used. */ + @VisibleForTesting void setLetterboxHorizontalPositionMultiplier(float multiplier) { mLetterboxHorizontalPositionMultiplier = multiplier; } - /** - * Resets horizontal position of a center of the letterboxed app window to {@link - * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}. - */ - void resetLetterboxHorizontalPositionMultiplier() { - mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat( - com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier); - } - } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index b77a38015a29..100967ce1638 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -6740,24 +6740,26 @@ class Task extends WindowContainer<WindowContainer> { } } - // TODO(185200798): Persist theme name instead of theme if - int splashScreenThemeResId = options != null - ? options.getSplashScreenThemeResId() : 0; - - // User can override the splashscreen theme. The theme name is used to persist - // the setting, so if no theme is set in the ActivityOptions, we check if has - // been persisted here. - if (splashScreenThemeResId == 0) { + // Find the splash screen theme. User can override the persisted theme by + // ActivityOptions. + String splashScreenThemeResName = options != null + ? options.getSplashScreenThemeResName() : null; + if (splashScreenThemeResName == null || splashScreenThemeResName.isEmpty()) { try { - String themeName = mAtmService.getPackageManager() + splashScreenThemeResName = mAtmService.getPackageManager() .getSplashScreenTheme(r.packageName, r.mUserId); - if (themeName != null) { - Context packageContext = mAtmService.mContext - .createPackageContext(r.packageName, 0); - splashScreenThemeResId = packageContext.getResources() - .getIdentifier(themeName, null, null); - } - } catch (RemoteException | PackageManager.NameNotFoundException + } catch (RemoteException ignore) { + // Just use the default theme + } + } + int splashScreenThemeResId = 0; + if (splashScreenThemeResName != null && !splashScreenThemeResName.isEmpty()) { + try { + final Context packageContext = mAtmService.mContext + .createPackageContext(r.packageName, 0); + splashScreenThemeResId = packageContext.getResources() + .getIdentifier(splashScreenThemeResName, null, null); + } catch (PackageManager.NameNotFoundException | Resources.NotFoundException ignore) { // Just use the default theme } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index f23028f6f67a..3a2ca80f2e12 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -43,6 +43,7 @@ import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; +import android.window.SplashScreenView; import android.window.StartingWindowInfo; import android.window.TaskAppearedInfo; import android.window.TaskSnapshot; @@ -215,6 +216,14 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } + void onAppSplashScreenViewRemoved(Task task) { + try { + mTaskOrganizer.onAppSplashScreenViewRemoved(task.mTaskId); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onAppSplashScreenViewRemoved callback", e); + } + } + SurfaceControl prepareLeash(Task task, String reason) { return new SurfaceControl(task.getSurfaceControl(), reason); } @@ -314,6 +323,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mOrganizer.copySplashScreenView(t); } + public void onAppSplashScreenViewRemoved(Task t) { + mOrganizer.onAppSplashScreenViewRemoved(t); + } + /** * Register this task with this state, but doesn't trigger the task appeared callback to * the organizer. @@ -566,6 +579,22 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return true; } + /** + * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} that the client has + * removed the splash screen view. + * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int) + * @see SplashScreenView#remove() + */ + public void onAppSplashScreenViewRemoved(Task task) { + final Task rootTask = task.getRootTask(); + if (rootTask == null || rootTask.mTaskOrganizer == null) { + return; + } + final TaskOrganizerState state = + mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder()); + state.onAppSplashScreenViewRemoved(task); + } + void onTaskAppeared(ITaskOrganizer organizer, Task task) { final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); if (state != null && state.addTask(task)) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9249c3d65e65..def7c3e6b13f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5411,25 +5411,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void setSandboxDisplayApis(int displayId, boolean sandboxDisplayApis) { - if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS); - } - - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent != null) { - displayContent.setSandboxDisplayApis(sandboxDisplayApis); - } - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - /** The global settings only apply to default display. */ private boolean applyForcedPropertiesForDefaultDisplay() { boolean changed = false; diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index d59654949a27..a94fd074ff2e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -19,12 +19,6 @@ package com.android.server.wm; import static android.os.Build.IS_USER; import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; - -import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.os.ParcelFileDescriptor; @@ -42,7 +36,6 @@ import com.android.internal.os.ByteTransferPipe; import com.android.internal.protolog.ProtoLogImpl; import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; import java.io.IOException; import java.io.PrintWriter; @@ -65,12 +58,10 @@ public class WindowManagerShellCommand extends ShellCommand { // Internal service impl -- must perform security checks before touching. private final WindowManagerService mInternal; - private final LetterboxConfiguration mLetterboxConfiguration; public WindowManagerShellCommand(WindowManagerService service) { mInterface = service; mInternal = service; - mLetterboxConfiguration = service.mLetterboxConfiguration; } @Override @@ -122,14 +113,6 @@ public class WindowManagerShellCommand extends ShellCommand { return runGetIgnoreOrientationRequest(pw); case "dump-visible-window-views": return runDumpVisibleWindowViews(pw); - case "set-letterbox-style": - return runSetLetterboxStyle(pw); - case "get-letterbox-style": - return runGetLetterboxStyle(pw); - case "reset-letterbox-style": - return runResetLetterboxStyle(pw); - case "set-sandbox-display-apis": - return runSandboxDisplayApis(pw); case "set-multi-window-config": return runSetMultiWindowConfig(); case "get-multi-window-config": @@ -348,37 +331,6 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - /** - * Override display size and metrics to reflect the DisplayArea of the calling activity. - */ - private int runSandboxDisplayApis(PrintWriter pw) throws RemoteException { - int displayId = Display.DEFAULT_DISPLAY; - String arg = getNextArgRequired(); - if ("-d".equals(arg)) { - displayId = Integer.parseInt(getNextArgRequired()); - arg = getNextArgRequired(); - } - - final boolean sandboxDisplayApis; - switch (arg) { - case "true": - case "1": - sandboxDisplayApis = true; - break; - case "false": - case "0": - sandboxDisplayApis = false; - break; - default: - getErrPrintWriter().println("Error: expecting true, 1, false, 0, but we " - + "get " + arg); - return -1; - } - - mInternal.setSandboxDisplayApis(displayId, sandboxDisplayApis); - return 0; - } - private int runDismissKeyguard(PrintWriter pw) throws RemoteException { mInterface.dismissKeyguard(null /* callback */, null /* message */); return 0; @@ -596,231 +548,6 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - private int runSetFixedOrientationLetterboxAspectRatio(PrintWriter pw) throws RemoteException { - final float aspectRatio; - try { - String arg = getNextArgRequired(); - aspectRatio = Float.parseFloat(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad aspect ratio format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or aspect ratio should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(aspectRatio); - } - return 0; - } - - private int runSetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException { - final int cornersRadius; - try { - String arg = getNextArgRequired(); - cornersRadius = Integer.parseInt(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad corners radius format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or corners radius should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxActivityCornersRadius(cornersRadius); - } - return 0; - } - - private int runSetLetterboxBackgroundType(PrintWriter pw) throws RemoteException { - @LetterboxBackgroundType final int backgroundType; - try { - String arg = getNextArgRequired(); - switch (arg) { - case "solid_color": - backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR; - break; - case "app_color_background": - backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; - break; - case "app_color_background_floating": - backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; - break; - case "wallpaper": - backgroundType = LETTERBOX_BACKGROUND_WALLPAPER; - break; - default: - getErrPrintWriter().println( - "Error: 'reset', 'solid_color', 'app_color_background' or " - + "'wallpaper' should be provided as an argument"); - return -1; - } - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset', 'solid_color', 'app_color_background' or " - + "'wallpaper' should be provided as an argument" + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundType(backgroundType); - } - return 0; - } - - private int runSetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException { - final Color color; - try { - String arg = getNextArgRequired(); - color = Color.valueOf(Color.parseColor(arg)); - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or color in #RRGGBB format should be provided as " - + "an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundColor(color); - } - return 0; - } - - private int runSetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw) - throws RemoteException { - final int radius; - try { - String arg = getNextArgRequired(); - radius = Integer.parseInt(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: blur radius format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or blur radius should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius); - } - return 0; - } - - private int runSetLetterboxBackgroundWallpaperDarkScrimAlpha(PrintWriter pw) - throws RemoteException { - final float alpha; - try { - String arg = getNextArgRequired(); - alpha = Float.parseFloat(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad alpha format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or alpha should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha); - } - return 0; - } - - private int runSeLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException { - final float multiplier; - try { - String arg = getNextArgRequired(); - multiplier = Float.parseFloat(arg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: bad multiplier format " + e); - return -1; - } catch (IllegalArgumentException e) { - getErrPrintWriter().println( - "Error: 'reset' or multiplier should be provided as an argument " + e); - return -1; - } - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier); - } - return 0; - } - - private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException { - if (peekNextArg() == null) { - getErrPrintWriter().println("Error: No arguments provided."); - } - while (peekNextArg() != null) { - String arg = getNextArg(); - switch (arg) { - case "--aspectRatio": - runSetFixedOrientationLetterboxAspectRatio(pw); - break; - case "--cornerRadius": - runSetLetterboxActivityCornersRadius(pw); - break; - case "--backgroundType": - runSetLetterboxBackgroundType(pw); - break; - case "--backgroundColor": - runSetLetterboxBackgroundColor(pw); - break; - case "--wallpaperBlurRadius": - runSetLetterboxBackgroundWallpaperBlurRadius(pw); - break; - case "--wallpaperDarkScrimAlpha": - runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw); - break; - case "--horizontalPositionMultiplier": - runSeLetterboxHorizontalPositionMultiplier(pw); - break; - default: - getErrPrintWriter().println( - "Error: Unrecognized letterbox style option: " + arg); - return -1; - } - } - return 0; - } - - private int runResetLetterboxStyle(PrintWriter pw) throws RemoteException { - if (peekNextArg() == null) { - resetLetterboxStyle(); - } - synchronized (mInternal.mGlobalLock) { - while (peekNextArg() != null) { - String arg = getNextArg(); - switch (arg) { - case "aspectRatio": - mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio(); - break; - case "cornerRadius": - mLetterboxConfiguration.resetLetterboxActivityCornersRadius(); - break; - case "backgroundType": - mLetterboxConfiguration.resetLetterboxBackgroundType(); - break; - case "backgroundColor": - mLetterboxConfiguration.resetLetterboxBackgroundColor(); - break; - case "wallpaperBlurRadius": - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius(); - break; - case "wallpaperDarkScrimAlpha": - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); - break; - case "horizontalPositionMultiplier": - mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); - break; - default: - getErrPrintWriter().println( - "Error: Unrecognized letterbox style option: " + arg); - return -1; - } - } - } - return 0; - } - private int runSetMultiWindowConfig() { if (peekNextArg() == null) { getErrPrintWriter().println("Error: No arguments provided."); @@ -895,40 +622,6 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - private void resetLetterboxStyle() { - synchronized (mInternal.mGlobalLock) { - mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio(); - mLetterboxConfiguration.resetLetterboxActivityCornersRadius(); - mLetterboxConfiguration.resetLetterboxBackgroundType(); - mLetterboxConfiguration.resetLetterboxBackgroundColor(); - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius(); - mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); - mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); - } - } - - private int runGetLetterboxStyle(PrintWriter pw) throws RemoteException { - synchronized (mInternal.mGlobalLock) { - pw.println("Corner radius: " - + mLetterboxConfiguration.getLetterboxActivityCornersRadius()); - pw.println("Horizontal position multiplier: " - + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier()); - pw.println("Aspect ratio: " - + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio()); - - pw.println("Background type: " - + LetterboxConfiguration.letterboxBackgroundTypeToString( - mLetterboxConfiguration.getLetterboxBackgroundType())); - pw.println(" Background color: " + Integer.toHexString( - mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb())); - pw.println(" Wallpaper blur radius: " - + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius()); - pw.println(" Wallpaper dark scrim alpha: " - + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha()); - } - return 0; - } - private int runReset(PrintWriter pw) throws RemoteException { int displayId = getDisplayId(getNextArg()); @@ -953,12 +646,6 @@ public class WindowManagerShellCommand extends ShellCommand { // set-ignore-orientation-request mInterface.setIgnoreOrientationRequest(displayId, false /* ignoreOrientationRequest */); - // set-letterbox-style - resetLetterboxStyle(); - - // set-sandbox-display-apis - mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true); - // set-multi-window-config runResetMultiWindowConfig(); @@ -993,12 +680,7 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]"); pw.println(" get-ignore-orientation-request [-d DISPLAY_ID] "); pw.println(" If app requested orientation should be ignored."); - pw.println(" set-sandbox-display-apis [true|1|false|0]"); - pw.println(" Sets override of Display APIs getRealSize / getRealMetrics to reflect "); - pw.println(" DisplayArea of the activity, or the window bounds if in letterbox or"); - pw.println(" Size Compat Mode."); - printLetterboxHelp(pw); printMultiWindowConfigHelp(pw); pw.println(" reset [-d DISPLAY_ID]"); @@ -1011,49 +693,6 @@ public class WindowManagerShellCommand extends ShellCommand { } } - private void printLetterboxHelp(PrintWriter pw) { - pw.println(" set-letterbox-style"); - pw.println(" Sets letterbox style using the following options:"); - pw.println(" --aspectRatio aspectRatio"); - pw.println(" Aspect ratio of letterbox for fixed orientation. If aspectRatio <= " - + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); - pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will"); - pw.println(" be ignored and framework implementation will determine aspect ratio."); - pw.println(" --cornerRadius radius"); - pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,"); - pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be"); - pw.println(" ignored and corners of the activity won't be rounded."); - pw.println(" --backgroundType [reset|solid_color|app_color_background"); - pw.println(" |app_color_background_floating|wallpaper]"); - pw.println(" Type of background used in the letterbox mode."); - pw.println(" --backgroundColor color"); - pw.println(" Color of letterbox which is be used when letterbox background type"); - pw.println(" is 'solid-color'. Use (set)get-letterbox-style to check and control"); - pw.println(" letterbox background type. See Color#parseColor for allowed color"); - pw.println(" formats (#RRGGBB and some colors by name, e.g. magenta or olive)."); - pw.println(" --wallpaperBlurRadius radius"); - pw.println(" Blur radius for 'wallpaper' letterbox background. If radius <= 0"); - pw.println(" both it and R.dimen.config_letterboxBackgroundWallpaperBlurRadius"); - pw.println(" are ignored and 0 is used."); - pw.println(" --wallpaperDarkScrimAlpha alpha"); - pw.println(" Alpha of a black translucent scrim shown over 'wallpaper'"); - pw.println(" letterbox background. If alpha < 0 or >= 1 both it and"); - pw.println(" R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored"); - pw.println(" and 0.0 (transparent) is used instead."); - pw.println(" --horizontalPositionMultiplier multiplier"); - pw.println(" Horizontal position of app window center. If multiplier < 0 or > 1,"); - pw.println(" both it and R.dimen.config_letterboxHorizontalPositionMultiplier"); - pw.println(" are ignored and central position (0.5) is used."); - pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType"); - pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha"); - pw.println(" |horizontalPositionMultiplier]"); - pw.println(" Resets overrides to default values for specified properties separated"); - pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'."); - pw.println(" If no arguments provided, all values will be reset."); - pw.println(" get-letterbox-style"); - pw.println(" Prints letterbox style configuration."); - } - private void printMultiWindowConfigHelp(PrintWriter pw) { pw.println(" set-multi-window-config"); pw.println(" Sets options to determine if activity should be shown in multi window:"); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 03762b3ee8bf..1a468d9f4ee9 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -61,6 +61,7 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; @@ -5385,6 +5386,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP || mAttrs.type == TYPE_NAVIGATION_BAR_PANEL) { return false; } + if ((mAttrs.privateFlags & PRIVATE_FLAG_NOT_MAGNIFIABLE) != 0) { + return false; + } return true; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 0128d350bd10..2439760e3f67 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -169,6 +169,7 @@ import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason; import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.DevicePolicySafetyChecker; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; @@ -1748,6 +1749,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mTransferOwnershipMetadataManager = mInjector.newTransferOwnershipMetadataManager(); mBugreportCollectionManager = new RemoteBugreportManager(this, mInjector); + // "Lite" interface is available even when the device doesn't have the feature + LocalServices.addService(DevicePolicyManagerLiteInternal.class, mLocalService); if (!mHasFeature) { // Skip the rest of the initialization mSetupContentObserver = null; @@ -6832,7 +6835,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void wipeDataWithReason(int flags, String wipeReasonForUser, boolean calledOnParentInstance) { - if (!mHasFeature) { + if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) { return; } final CallerIdentity caller = getCallerIdentity(); @@ -8027,6 +8030,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { inForeground = true; receiverComponent = resolveDelegateReceiver( DELEGATION_SECURITY_LOGGING, action, userId); + // STOPSHIP(b/185004808): remove excessive log. + Slogf.d(LOG_TAG, "Delegate for security logs broadcast: " + receiverComponent); } if (receiverComponent == null) { receiverComponent = getOwnerComponent(userId); @@ -12612,7 +12617,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @VisibleForTesting - final class LocalService extends DevicePolicyManagerInternal { + final class LocalService extends DevicePolicyManagerInternal + implements DevicePolicyManagerLiteInternal { private List<OnCrossProfileWidgetProvidersChangeListener> mWidgetProviderListeners; @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java index 86437a27a64d..1cbc634b7152 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java @@ -21,7 +21,7 @@ import static android.app.admin.DevicePolicyManager.operationToString; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; import android.app.admin.DevicePolicyManager.OperationSafetyReason; -import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.DevicePolicySafetyChecker; import android.os.Handler; import android.os.Looper; @@ -80,8 +80,8 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { + ", should be " + operationToString(mOperation)); } String reasonName = operationSafetyReasonToString(reason); - DevicePolicyManagerInternal dpmi = LocalServices - .getService(DevicePolicyManagerInternal.class); + DevicePolicyManagerLiteInternal dpmi = LocalServices + .getService(DevicePolicyManagerLiteInternal.class); Slog.i(TAG, "notifying " + reasonName + " is UNSAFE"); dpmi.notifyUnsafeOperationStateChanged(this, reason, /* isSafe= */ false); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java index c29de905d370..0741c81bb528 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java @@ -64,7 +64,7 @@ class SecurityLogMonitor implements Runnable { mLastForceNanos = System.nanoTime(); } - private static final boolean DEBUG = false; // STOPSHIP if true. + private static final boolean DEBUG = true; // STOPSHIP if true. private static final String TAG = "SecurityLogMonitor"; /** * Each log entry can hold up to 4K bytes (but as of {@link android.os.Build.VERSION_CODES#N} @@ -427,7 +427,7 @@ class SecurityLogMonitor implements Runnable { while (!Thread.currentThread().isInterrupted()) { try { final boolean force = mForceSemaphore.tryAcquire(POLLING_INTERVAL_MS, MILLISECONDS); - + if (DEBUG) Slog.d(TAG, "Retrieving next batch, force=" + force); getNextBatch(newLogs); mLock.lockInterruptibly(); @@ -469,6 +469,11 @@ class SecurityLogMonitor implements Runnable { return; } final int logSize = mPendingLogs.size(); + if (DEBUG) { + Slog.d(TAG, String.format( + "notifyDeviceOwnerOrProfileOwnerIfNeeded, size: %d now: %d next: %d", + logSize, SystemClock.elapsedRealtime(), mNextAllowedRetrievalTimeMillis)); + } if (logSize >= BUFFER_ENTRIES_NOTIFICATION_LEVEL || (force && logSize > 0)) { // Allow DO to retrieve logs if too many pending logs or if forced. if (!mAllowedToRetrieve) { @@ -507,6 +512,7 @@ class SecurityLogMonitor implements Runnable { synchronized (mForceSemaphore) { final long toWaitNanos = mLastForceNanos + FORCE_FETCH_THROTTLE_NS - nowNanos; if (toWaitNanos > 0) { + if (DEBUG) Slog.d(TAG, "Forcing security logs throttled"); return NANOSECONDS.toMillis(toWaitNanos) + 1; // Round up. } mLastForceNanos = nowNanos; @@ -515,6 +521,7 @@ class SecurityLogMonitor implements Runnable { if (mForceSemaphore.availablePermits() == 0) { mForceSemaphore.release(); } + if (DEBUG) Slog.d(TAG, "Forcing security logs semaphore released"); return 0; } } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 21de7916e23d..280204dfd481 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -2309,12 +2309,14 @@ public class AlarmManagerServiceTest { public void minWindowChangeDisabled() { mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, false); final long minWindow = 73; + final long futurity = 10_000; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. for (int window = 1; window <= minWindow; window++) { final PendingIntent pi = getNewMockPendingIntent(); - setTestAlarm(ELAPSED_REALTIME, 0, window, pi, 0, 0, TEST_CALLING_UID, null); + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, + TEST_CALLING_UID, null); assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); @@ -2323,18 +2325,61 @@ public class AlarmManagerServiceTest { } @Test + public void minWindowExempted() { + mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true); + final long minWindow = 73; + final long futurity = 10_000; + + setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); + + final int coreUid = 2312; + doReturn(true).when(() -> UserHandle.isCore(coreUid)); + + final int allowlisted = 54239; + when(mDeviceIdleInternal.isAppOnWhitelist(UserHandle.getAppId(allowlisted))).thenReturn( + true); + + for (final int callingUid : new int[]{SYSTEM_UI_UID, coreUid, coreUid}) { + // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. + for (int window = 1; window <= minWindow; window++) { + final PendingIntent pi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, + callingUid, null); + + assertEquals(1, mService.mAlarmStore.size()); + final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); + assertEquals(window, a.windowLength); + } + } + + // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. + for (int window = 1; window <= minWindow; window++) { + final PendingIntent pi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, + TEST_CALLING_UID, null); + + assertEquals(1, mService.mAlarmStore.size()); + final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); + assertEquals(minWindow, a.windowLength); + } + } + + @Test public void minWindowPriorityAlarm() { mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true); final long minWindow = 73; + final long futurity = 10_000; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. for (int window = 1; window <= minWindow; window++) { - setPrioritizedAlarm(ELAPSED_REALTIME, 0, window, new IAlarmListener.Stub() { - @Override - public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { - } - }); + setPrioritizedAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, + new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) + throws RemoteException { + } + }); assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); assertEquals(window, a.windowLength); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java index cf5db2e0db98..8532dbb7f38a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java @@ -158,7 +158,7 @@ public class MockableLocationProviderTest { @Test public void testSetState() { - assertThat(mProvider.isAllowed()).isFalse(); + assertThat(mProvider.getState().allowed).isFalse(); AbstractLocationProvider.State newState; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java index 1ac4a8ed96d0..a71b481372d8 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java @@ -19,6 +19,7 @@ package com.android.server.accessibility; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_UP; +import static android.view.WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY; import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER; import static org.hamcrest.CoreMatchers.allOf; @@ -186,9 +187,9 @@ public class MotionEventInjectorTest { verifyNoMoreInteractions(next); mMessageCapturingHandler.sendOneMessage(); // Send a motion event - verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(FLAG_PASS_TO_USER)); - verify(next).onMotionEvent(argThat(mIsLineStart), argThat(mIsLineStart), - eq(FLAG_PASS_TO_USER)); + final int expectedFlags = FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY; + verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(expectedFlags)); + verify(next).onMotionEvent(argThat(mIsLineStart), argThat(mIsLineStart), eq(expectedFlags)); verifyNoMoreInteractions(next); reset(next); @@ -196,7 +197,7 @@ public class MotionEventInjectorTest { mMessageCapturingHandler.sendOneMessage(); // Send a motion event verify(next).onMotionEvent(argThat(allOf(mIsLineMiddle, hasRightDownTime)), - argThat(allOf(mIsLineMiddle, hasRightDownTime)), eq(FLAG_PASS_TO_USER)); + argThat(allOf(mIsLineMiddle, hasRightDownTime)), eq(expectedFlags)); verifyNoMoreInteractions(next); reset(next); @@ -204,7 +205,7 @@ public class MotionEventInjectorTest { mMessageCapturingHandler.sendOneMessage(); // Send a motion event verify(next).onMotionEvent(argThat(allOf(mIsLineEnd, hasRightDownTime)), - argThat(allOf(mIsLineEnd, hasRightDownTime)), eq(FLAG_PASS_TO_USER)); + argThat(allOf(mIsLineEnd, hasRightDownTime)), eq(expectedFlags)); verifyNoMoreInteractions(next); verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, true); @@ -242,7 +243,8 @@ public class MotionEventInjectorTest { mMessageCapturingHandler.sendAllMessages(); // Send all motion events reset(next); mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0); - verify(next).onMotionEvent(argThat(mIsClickDown), argThat(mIsClickDown), eq(0)); + verify(next).onMotionEvent(argThat(mIsClickDown), argThat(mIsClickDown), + eq(FLAG_INJECTED_FROM_ACCESSIBILITY)); } @Test @@ -258,7 +260,8 @@ public class MotionEventInjectorTest { mMessageCapturingHandler.sendOneMessage(); // Send a motion event verify(next).onMotionEvent( - argThat(mIsLineStart), argThat(mIsLineStart), eq(FLAG_PASS_TO_USER)); + argThat(mIsLineStart), argThat(mIsLineStart), + eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY)); } @Test @@ -289,9 +292,11 @@ public class MotionEventInjectorTest { reset(next); mMessageCapturingHandler.sendOneMessage(); // Send a motion event - verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(FLAG_PASS_TO_USER)); + verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), + eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY)); verify(next).onMotionEvent( - argThat(mIsLineStart), argThat(mIsLineStart), eq(FLAG_PASS_TO_USER)); + argThat(mIsLineStart), argThat(mIsLineStart), + eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java index 3bcbcbdbd2c8..8666fa6ac35a 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java @@ -57,6 +57,11 @@ import java.util.Map; /** This tests AppSearchImpl when it's running with a platform-backed VisibilityStore. */ public class AppSearchImplPlatformTest { + /** + * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class. + */ + private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true; + @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); private final Map<UserHandle, PackageManager> mMockPackageManagers = new ArrayMap<>(); private Context mContext; @@ -88,7 +93,8 @@ public class AppSearchImplPlatformTest { AppSearchImpl.create( mTemporaryFolder.newFolder(), mContext, - /*logger=*/ null); + /*logger=*/ null, + ALWAYS_OPTIMIZE); mGlobalQuerierUid = mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0); diff --git a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java index 6ac4d138c9d1..4faffc025e2f 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java @@ -56,6 +56,11 @@ import java.util.Collections; import java.util.Map; public class VisibilityStoreTest { + /** + * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class. + */ + private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true; + @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); private final Map<UserHandle, PackageManager> mMockPackageManagers = new ArrayMap<>(); private Context mContext; @@ -87,7 +92,8 @@ public class VisibilityStoreTest { AppSearchImpl.create( mTemporaryFolder.newFolder(), mContext, - /*logger=*/ null); + /*logger=*/ null, + ALWAYS_OPTIMIZE); mUid = mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0); mVisibilityStore = appSearchImpl.getVisibilityStoreLocked(); diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java index 5a8c44caa0a3..e26cfeab1529 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java @@ -76,17 +76,22 @@ import java.util.Set; public class AppSearchImplTest { @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); private AppSearchImpl mAppSearchImpl; + /** + * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class. + */ + private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true; @Before public void setUp() throws Exception { Context context = ApplicationProvider.getApplicationContext(); - // Give ourselves global query permissions + // Give ourselves global query permissions. mAppSearchImpl = AppSearchImpl.create( mTemporaryFolder.newFolder(), context, - /*logger=*/ null); + /*logger=*/ null, + ALWAYS_OPTIMIZE); } /** @@ -423,7 +428,7 @@ public class AppSearchImplTest { } @Test - public void testOptimize() throws Exception { + public void testTriggerCheckOptimizeByMutationSize() throws Exception { // Insert schema List<AppSearchSchema> schemas = Collections.singletonList(new AppSearchSchema.Builder("type").build()); @@ -436,56 +441,30 @@ public class AppSearchImplTest { /*forceOverride=*/ false, /*version=*/ 0); - // Insert enough documents. - for (int i = 0; - i - < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; - i++) { - GenericDocument document = - new GenericDocument.Builder<>("namespace", "id" + i, "type").build(); - mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null); - } + // Insert a document and then remove it to generate garbage. + GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build(); + mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null); + mAppSearchImpl.remove( + "package", "database", "namespace", "id", /*removeStatsBuilder=*/ null); - // Check optimize() will release 0 docs since there is no deletion. + // Verify there is garbage documents. GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked(); - assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0); - - // delete 999 documents, we will reach the threshold to trigger optimize() in next - // deletion. - for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1; i++) { - mAppSearchImpl.remove( - "package", "database", "namespace", "id" + i, /*removeStatsBuilder=*/ null); - } + assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(1); - // Updates the check for optimize counter, checkForOptimize() will be triggered since - // CHECK_OPTIMIZE_INTERVAL is reached but optimize() won't since - // OPTIMIZE_THRESHOLD_DOC_COUNT is not. - mAppSearchImpl.checkForOptimize( - /*mutateBatchSize=*/ AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1); + // Increase mutation counter and stop before reach the threshold + mAppSearchImpl.checkForOptimize(AppSearchImpl.CHECK_OPTIMIZE_INTERVAL - 1); - // Verify optimize() still not be triggered. + // Verify the optimize() isn't triggered. optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked(); - assertThat(optimizeInfo.getOptimizableDocs()) - .isEqualTo(AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1); - - // Keep delete docs - for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT; - i - < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; - i++) { - mAppSearchImpl.remove( - "package", "database", "namespace", "id" + i, /*removeStatsBuilder=*/ null); - } - // updates the check for optimize counter, will reach both CHECK_OPTIMIZE_INTERVAL and - // OPTIMIZE_THRESHOLD_DOC_COUNT this time and trigger a optimize(). - mAppSearchImpl.checkForOptimize(/*mutateBatchSize*/ AppSearchImpl.CHECK_OPTIMIZE_INTERVAL); + assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(1); - // Verify optimize() is triggered + // Increase the counter and reach the threshold, optimize() should be triggered. + mAppSearchImpl.checkForOptimize(/*mutateBatchSize=*/ 1); + + // Verify optimize() is triggered. optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked(); - assertThat(optimizeInfo.getOptimizableDocs()) - .isLessThan(AppSearchImpl.CHECK_OPTIMIZE_INTERVAL); + assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0); + assertThat(optimizeInfo.getEstimatedOptimizableBytes()).isEqualTo(0); } @Test @@ -493,7 +472,12 @@ public class AppSearchImplTest { // Setup the index Context context = ApplicationProvider.getApplicationContext(); File appsearchDir = mTemporaryFolder.newFolder(); - AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null); + AppSearchImpl appSearchImpl = + AppSearchImpl.create( + appsearchDir, + context, + /*logger=*/ null, + ALWAYS_OPTIMIZE); // Insert schema List<AppSearchSchema> schemas = @@ -522,7 +506,7 @@ public class AppSearchImplTest { /*queryExpression=*/ "", new SearchSpec.Builder().addFilterSchemas("Type1").build(), context.getPackageName(), - VisibilityStore.NO_OP_USER_ID, + VisibilityStore.NO_OP_UID, /*logger=*/ null); assertThat(results.getResults()).hasSize(1); assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc); @@ -554,7 +538,9 @@ public class AppSearchImplTest { // Initialize AppSearchImpl. This should cause a reset. appSearchImpl.close(); - appSearchImpl = AppSearchImpl.create(appsearchDir, context, testLogger); + appSearchImpl = + AppSearchImpl.create( + appsearchDir, context, testLogger, ALWAYS_OPTIMIZE); // Check recovery state InitializeStats initStats = testLogger.mInitializeStats; @@ -582,7 +568,7 @@ public class AppSearchImplTest { /*queryExpression=*/ "", new SearchSpec.Builder().addFilterSchemas("Type1").build(), context.getPackageName(), - VisibilityStore.NO_OP_USER_ID, + VisibilityStore.NO_OP_UID, /*logger=*/ null); assertThat(results.getResults()).isEmpty(); @@ -606,7 +592,7 @@ public class AppSearchImplTest { /*queryExpression=*/ "", new SearchSpec.Builder().addFilterSchemas("Type1").build(), context.getPackageName(), - VisibilityStore.NO_OP_USER_ID, + VisibilityStore.NO_OP_UID, /*logger=*/ null); assertThat(results.getResults()).hasSize(1); assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc); @@ -862,7 +848,7 @@ public class AppSearchImplTest { "", searchSpec, /*callerPackageName=*/ "", - /*callerUid=*/ 0, + VisibilityStore.NO_OP_UID, /*logger=*/ null); assertThat(searchResultPage.getResults()).isEmpty(); } @@ -1683,7 +1669,8 @@ public class AppSearchImplTest { AppSearchImpl.create( mTemporaryFolder.newFolder(), context, - /*logger=*/ null); + /*logger=*/ null, + ALWAYS_OPTIMIZE); // Initial check that we could do something at first. List<AppSearchSchema> schemas = @@ -1758,7 +1745,7 @@ public class AppSearchImplTest { .setTermMatch(TermMatchType.Code.PREFIX_VALUE) .build(), "package", - /*callerUid=*/ 1, + VisibilityStore.NO_OP_UID, /*logger=*/ null); }); @@ -1830,7 +1817,12 @@ public class AppSearchImplTest { // Setup the index Context context = ApplicationProvider.getApplicationContext(); File appsearchDir = mTemporaryFolder.newFolder(); - AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null); + AppSearchImpl appSearchImpl = + AppSearchImpl.create( + appsearchDir, + context, + /*logger=*/ null, + ALWAYS_OPTIMIZE); List<AppSearchSchema> schemas = Collections.singletonList(new AppSearchSchema.Builder("type").build()); @@ -1856,7 +1848,11 @@ public class AppSearchImplTest { // That document should be visible even from another instance. AppSearchImpl appSearchImpl2 = - AppSearchImpl.create(appsearchDir, context, /*logger=*/ null); + AppSearchImpl.create( + appsearchDir, + context, + /*logger=*/ null, + ALWAYS_OPTIMIZE); getResult = appSearchImpl2.getDocument( "package", "database", "namespace1", "id1", Collections.emptyMap()); @@ -1868,7 +1864,12 @@ public class AppSearchImplTest { // Setup the index Context context = ApplicationProvider.getApplicationContext(); File appsearchDir = mTemporaryFolder.newFolder(); - AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null); + AppSearchImpl appSearchImpl = + AppSearchImpl.create( + appsearchDir, + context, + /*logger=*/ null, + ALWAYS_OPTIMIZE); List<AppSearchSchema> schemas = Collections.singletonList(new AppSearchSchema.Builder("type").build()); @@ -1918,7 +1919,11 @@ public class AppSearchImplTest { // Only the second document should be retrievable from another instance. AppSearchImpl appSearchImpl2 = - AppSearchImpl.create(appsearchDir, context, /*logger=*/ null); + AppSearchImpl.create( + appsearchDir, + context, + /*logger=*/ null, + ALWAYS_OPTIMIZE); expectThrows( AppSearchException.class, () -> @@ -1939,7 +1944,12 @@ public class AppSearchImplTest { // Setup the index Context context = ApplicationProvider.getApplicationContext(); File appsearchDir = mTemporaryFolder.newFolder(); - AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null); + AppSearchImpl appSearchImpl = + AppSearchImpl.create( + appsearchDir, + context, + /*logger=*/ null, + ALWAYS_OPTIMIZE); List<AppSearchSchema> schemas = Collections.singletonList(new AppSearchSchema.Builder("type").build()); @@ -1997,7 +2007,11 @@ public class AppSearchImplTest { // Only the second document should be retrievable from another instance. AppSearchImpl appSearchImpl2 = - AppSearchImpl.create(appsearchDir, context, /*logger=*/ null); + AppSearchImpl.create( + appsearchDir, + context, + /*logger=*/ null, + ALWAYS_OPTIMIZE); expectThrows( AppSearchException.class, () -> diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java index f0a6ef13b212..c6726c69c7fd 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java @@ -53,6 +53,10 @@ public class AppSearchLoggerTest { @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); private AppSearchImpl mAppSearchImpl; private TestLogger mLogger; + /** + * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class. + */ + private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true; @Before public void setUp() throws Exception { @@ -63,7 +67,8 @@ public class AppSearchLoggerTest { AppSearchImpl.create( mTemporaryFolder.newFolder(), context, - /*logger=*/ null); + /*logger=*/ null, + ALWAYS_OPTIMIZE); mLogger = new TestLogger(); } @@ -286,11 +291,13 @@ public class AppSearchLoggerTest { public void testLoggingStats_initialize() throws Exception { Context context = ApplicationProvider.getApplicationContext(); + // Create an unused AppSearchImpl to generated an InitializeStats. AppSearchImpl appSearchImpl = AppSearchImpl.create( mTemporaryFolder.newFolder(), context, - mLogger); + mLogger, + ALWAYS_OPTIMIZE); InitializeStats iStats = mLogger.mInitializeStats; assertThat(iStats).isNotNull(); @@ -325,9 +332,9 @@ public class AppSearchLoggerTest { PutDocumentStats pStats = mLogger.mPutDocumentStats; assertThat(pStats).isNotNull(); - assertThat(pStats.getGeneralStats().getPackageName()).isEqualTo(testPackageName); - assertThat(pStats.getGeneralStats().getDatabase()).isEqualTo(testDatabase); - assertThat(pStats.getGeneralStats().getStatusCode()).isEqualTo(AppSearchResult.RESULT_OK); + assertThat(pStats.getPackageName()).isEqualTo(testPackageName); + assertThat(pStats.getDatabase()).isEqualTo(testDatabase); + assertThat(pStats.getStatusCode()).isEqualTo(AppSearchResult.RESULT_OK); // The rest of native stats have been tested in testCopyNativeStats assertThat(pStats.getNativeDocumentSizeBytes()).isGreaterThan(0); } diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategyTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategyTest.java new file mode 100644 index 000000000000..f30cbb8c5158 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategyTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appsearch.external.localstorage; + +import static com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy.BYTES_OPTIMIZE_THRESHOLD; +import static com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy.DOC_COUNT_OPTIMIZE_THRESHOLD; +import static com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy.TIME_OPTIMIZE_THRESHOLD_MILLIS; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.server.appsearch.proto.GetOptimizeInfoResultProto; +import com.android.server.appsearch.proto.StatusProto; + +import org.junit.Test; + +public class FrameworkOptimizeStrategyTest { + FrameworkOptimizeStrategy mFrameworkOptimizeStrategy = new FrameworkOptimizeStrategy(); + + @Test + public void testShouldOptimize_docCountThreshold() { + GetOptimizeInfoResultProto optimizeInfo = + GetOptimizeInfoResultProto.newBuilder() + .setTimeSinceLastOptimizeMs(0) + .setEstimatedOptimizableBytes(BYTES_OPTIMIZE_THRESHOLD) + .setOptimizableDocs(0) + .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK).build()) + .build(); + assertThat(mFrameworkOptimizeStrategy.shouldOptimize(optimizeInfo)).isTrue(); + } + + @Test + public void testShouldOptimize_byteThreshold() { + GetOptimizeInfoResultProto optimizeInfo = + GetOptimizeInfoResultProto.newBuilder() + .setTimeSinceLastOptimizeMs(TIME_OPTIMIZE_THRESHOLD_MILLIS) + .setEstimatedOptimizableBytes(0) + .setOptimizableDocs(0) + .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK).build()) + .build(); + assertThat(mFrameworkOptimizeStrategy.shouldOptimize(optimizeInfo)).isTrue(); + } + + @Test + public void testShouldNotOptimize_timeThreshold() { + GetOptimizeInfoResultProto optimizeInfo = + GetOptimizeInfoResultProto.newBuilder() + .setTimeSinceLastOptimizeMs(0) + .setEstimatedOptimizableBytes(0) + .setOptimizableDocs(DOC_COUNT_OPTIMIZE_THRESHOLD) + .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK).build()) + .build(); + assertThat(mFrameworkOptimizeStrategy.shouldOptimize(optimizeInfo)).isTrue(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java index a71e53233848..6d9068675a72 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java @@ -29,57 +29,28 @@ public class AppSearchStatsTest { static final int TEST_TOTAL_LATENCY_MILLIS = 20; @Test - public void testAppSearchStats_GeneralStats() { - final GeneralStats gStats = - new GeneralStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE) - .setStatusCode(TEST_STATUS_CODE) - .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS) - .build(); - - assertThat(gStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME); - assertThat(gStats.getDatabase()).isEqualTo(TEST_DATA_BASE); - assertThat(gStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE); - assertThat(gStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS); - } - - /** Make sure status code is UNKNOWN if not set in {@link GeneralStats} */ - @Test - public void testAppSearchStats_GeneralStats_defaultStatsCode_Unknown() { - final GeneralStats gStats = - new GeneralStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE) - .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS) - .build(); - - assertThat(gStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME); - assertThat(gStats.getDatabase()).isEqualTo(TEST_DATA_BASE); - assertThat(gStats.getStatusCode()).isEqualTo(AppSearchResult.RESULT_UNKNOWN_ERROR); - assertThat(gStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS); - } - - @Test public void testAppSearchStats_CallStats() { final int estimatedBinderLatencyMillis = 1; final int numOperationsSucceeded = 2; final int numOperationsFailed = 3; final @CallStats.CallType int callType = CallStats.CALL_TYPE_PUT_DOCUMENTS; - final CallStats.Builder cStatsBuilder = - new CallStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE) + final CallStats cStats = + new CallStats.Builder() + .setPackageName(TEST_PACKAGE_NAME) + .setDatabase(TEST_DATA_BASE) + .setStatusCode(TEST_STATUS_CODE) + .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS) .setCallType(callType) .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis) .setNumOperationsSucceeded(numOperationsSucceeded) - .setNumOperationsFailed(numOperationsFailed); - cStatsBuilder - .getGeneralStatsBuilder() - .setStatusCode(TEST_STATUS_CODE) - .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS); - final CallStats cStats = cStatsBuilder.build(); + .setNumOperationsFailed(numOperationsFailed) + .build(); - assertThat(cStats.getGeneralStats().getPackageName()).isEqualTo(TEST_PACKAGE_NAME); - assertThat(cStats.getGeneralStats().getDatabase()).isEqualTo(TEST_DATA_BASE); - assertThat(cStats.getGeneralStats().getStatusCode()).isEqualTo(TEST_STATUS_CODE); - assertThat(cStats.getGeneralStats().getTotalLatencyMillis()) - .isEqualTo(TEST_TOTAL_LATENCY_MILLIS); + assertThat(cStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME); + assertThat(cStats.getDatabase()).isEqualTo(TEST_DATA_BASE); + assertThat(cStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE); + assertThat(cStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS); assertThat(cStats.getEstimatedBinderLatencyMillis()) .isEqualTo(estimatedBinderLatencyMillis); assertThat(cStats.getCallType()).isEqualTo(callType); @@ -88,6 +59,19 @@ public class AppSearchStatsTest { } @Test + public void testAppSearchCallStats_nullValues() { + final @CallStats.CallType int callType = CallStats.CALL_TYPE_PUT_DOCUMENTS; + + final CallStats.Builder cStatsBuilder = new CallStats.Builder().setCallType(callType); + + final CallStats cStats = cStatsBuilder.build(); + + assertThat(cStats.getPackageName()).isNull(); + assertThat(cStats.getDatabase()).isNull(); + assertThat(cStats.getCallType()).isEqualTo(callType); + } + + @Test public void testAppSearchStats_PutDocumentStats() { final int generateDocumentProtoLatencyMillis = 1; final int rewriteDocumentTypesLatencyMillis = 2; @@ -100,6 +84,8 @@ public class AppSearchStatsTest { final boolean nativeExceededMaxNumTokens = true; final PutDocumentStats.Builder pStatsBuilder = new PutDocumentStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE) + .setStatusCode(TEST_STATUS_CODE) + .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS) .setGenerateDocumentProtoLatencyMillis(generateDocumentProtoLatencyMillis) .setRewriteDocumentTypesLatencyMillis(rewriteDocumentTypesLatencyMillis) .setNativeLatencyMillis(nativeLatencyMillis) @@ -109,17 +95,13 @@ public class AppSearchStatsTest { .setNativeDocumentSizeBytes(nativeDocumentSize) .setNativeNumTokensIndexed(nativeNumTokensIndexed) .setNativeExceededMaxNumTokens(nativeExceededMaxNumTokens); - pStatsBuilder - .getGeneralStatsBuilder() - .setStatusCode(TEST_STATUS_CODE) - .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS); + final PutDocumentStats pStats = pStatsBuilder.build(); - assertThat(pStats.getGeneralStats().getPackageName()).isEqualTo(TEST_PACKAGE_NAME); - assertThat(pStats.getGeneralStats().getDatabase()).isEqualTo(TEST_DATA_BASE); - assertThat(pStats.getGeneralStats().getStatusCode()).isEqualTo(TEST_STATUS_CODE); - assertThat(pStats.getGeneralStats().getTotalLatencyMillis()) - .isEqualTo(TEST_TOTAL_LATENCY_MILLIS); + assertThat(pStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME); + assertThat(pStats.getDatabase()).isEqualTo(TEST_DATA_BASE); + assertThat(pStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE); + assertThat(pStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS); assertThat(pStats.getGenerateDocumentProtoLatencyMillis()) .isEqualTo(generateDocumentProtoLatencyMillis); assertThat(pStats.getRewriteDocumentTypesLatencyMillis()) diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index fa3f45c08202..a2b1c1cb1a49 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -34,6 +34,7 @@ import static org.mockito.Mockito.when; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyManagerLiteInternal; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; @@ -158,6 +159,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { final long ident = mContext.binder.clearCallingIdentity(); try { + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); @@ -271,6 +273,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { final long ident = mContext.binder.clearCallingIdentity(); try { + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); @@ -339,6 +342,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { // (Need clearCallingIdentity() to pass permission checks.) final long ident = mContext.binder.clearCallingIdentity(); try { + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); @@ -499,6 +503,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { DevicePolicyManagerServiceTestable dpms; final long ident = mContext.binder.clearCallingIdentity(); try { + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); 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 5447a58a1643..cedf6361e33b 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -84,6 +84,7 @@ import android.app.PendingIntent; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.PasswordMetrics; import android.app.admin.SystemUpdatePolicy; @@ -280,6 +281,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { private void initializeDpms() { // Need clearCallingIdentity() to pass permission checks. final long ident = mContext.binder.clearCallingIdentity(); + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); @@ -369,10 +371,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { .thenReturn(false); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class); new DevicePolicyManagerServiceTestable(getServices(), mContext); // If the device has no DPMS feature, it shouldn't register the local service. assertThat(LocalServices.getService(DevicePolicyManagerInternal.class)).isNull(); + + // But should still register the lite one + assertThat(LocalServices.getService(DevicePolicyManagerLiteInternal.class)).isNotNull(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 7241fa00ecf7..90a127701505 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -253,6 +253,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setPerson(makePerson("person", "personKey", "personUri")) .setLongLived(true) .setExtras(pb) + .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen) .build(); si.addFlags(ShortcutInfo.FLAG_PINNED); si.setBitmapPath("abc"); @@ -288,6 +289,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getTextResName()); assertEquals(0, si.getDisabledMessageResourceId()); assertEquals(null, si.getDisabledMessageResName()); + assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen", + si.getStartingThemeResName()); } public void testShortcutInfoParcel_resId() { @@ -308,6 +311,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) .setRank(123) .setExtras(pb) + .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen) .build(); si.addFlags(ShortcutInfo.FLAG_PINNED); si.setBitmapPath("abc"); @@ -339,6 +343,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(456, si.getIconResourceId()); assertEquals("string/r456", si.getIconResName()); assertEquals("test_uri", si.getIconUri()); + assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen", + si.getStartingThemeResName()); } public void testShortcutInfoClone() { @@ -2210,6 +2216,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { android.R.drawable.alert_dark_frame, true, getTestContext().getPackageName())); assertEquals("" + android.R.string.cancel, ShortcutInfo.lookUpResourceName(res, android.R.string.cancel, false, getTestContext().getPackageName())); + assertEquals("" + android.R.style.Theme_Black_NoTitleBar_Fullscreen, + ShortcutInfo.lookUpResourceName( + res, android.R.style.Theme_Black_NoTitleBar_Fullscreen, true, + getTestContext().getPackageName())); } public void testLookUpResourceName_appResources() { @@ -2236,6 +2246,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(android.R.drawable.alert_dark_frame, ShortcutInfo.lookUpResourceId(res, "" + android.R.drawable.alert_dark_frame, null, getTestContext().getPackageName())); + assertEquals(android.R.style.Theme_Black_NoTitleBar_Fullscreen, + ShortcutInfo.lookUpResourceId( + res, "" + android.R.style.Theme_Black_NoTitleBar_Fullscreen, + null, getTestContext().getPackageName())); } // Test for a ShortcutInfo method. diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java index 00b05d4ad10c..14cab021edb0 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java @@ -17,6 +17,7 @@ package com.android.server.vibrator; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import android.hardware.vibrator.IVibrator; import android.os.VibrationEffect; @@ -25,8 +26,11 @@ import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.RampSegment; import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.platform.test.annotations.Presubmit; +import androidx.test.InstrumentationRegistry; + import org.junit.Before; import org.junit.Test; @@ -58,7 +62,7 @@ public class DeviceVibrationEffectAdapterTest { @Before public void setUp() throws Exception { - mAdapter = new DeviceVibrationEffectAdapter(); + mAdapter = new DeviceVibrationEffectAdapter(InstrumentationRegistry.getContext()); } @Test @@ -84,28 +88,19 @@ public class DeviceVibrationEffectAdapterTest { new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f, /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10), new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f, - /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 11), + /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 100), new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f, - /* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 200)), + /* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 1000)), /* repeatIndex= */ 3); - VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList( - new StepSegment(/* amplitude= */ 0, Float.NaN, /* duration= */ 10), - new StepSegment(/* amplitude= */ 0.5f, Float.NaN, /* duration= */ 100), - // 10ms ramp becomes 2 steps - new StepSegment(/* amplitude= */ 1, Float.NaN, /* duration= */ 5), - new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 5), - // 11ms ramp becomes 3 steps - new StepSegment(/* amplitude= */ 0.8f, Float.NaN, /* duration= */ 5), - new StepSegment(/* amplitude= */ 0.6f, Float.NaN, /* duration= */ 5), - new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 1), - // 200ms ramp with same amplitude becomes a single step - new StepSegment(/* amplitude= */ 0.65f, Float.NaN, /* duration= */ 200)), - // Repeat index fixed after intermediate steps added - /* repeatIndex= */ 4); - - VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING); - assertEquals(expected, mAdapter.apply(effect, info)); + VibrationEffect.Composed adaptedEffect = (VibrationEffect.Composed) mAdapter.apply(effect, + createVibratorInfo(EMPTY_FREQUENCY_MAPPING)); + assertTrue(adaptedEffect.getSegments().size() > effect.getSegments().size()); + assertTrue(adaptedEffect.getRepeatIndex() >= effect.getRepeatIndex()); + + for (VibrationEffectSegment adaptedSegment : adaptedEffect.getSegments()) { + assertTrue(adaptedSegment instanceof StepSegment); + } } @Test @@ -136,33 +131,6 @@ public class DeviceVibrationEffectAdapterTest { } @Test - public void testStepAndRampSegments_withPwleCapabilityAndNoFrequency_keepsOriginalSteps() { - VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( - new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 10), - new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100), - new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10), - new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, - /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50), - new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f, - /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)), - /* repeatIndex= */ 2); - - VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList( - new StepSegment(/* amplitude= */ 0, /* frequency= */ 150, /* duration= */ 10), - new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 150, /* duration= */ 100), - new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10), - new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f, - /* startFrequency= */ 50, /* endFrequency= */ 200, /* duration= */ 50), - new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f, - /* startFrequency= */ 200, /* endFrequency= */ 50, /* duration= */ 20)), - /* repeatIndex= */ 2); - - VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING, - IVibrator.CAP_COMPOSE_PWLE_EFFECTS); - assertEquals(expected, mAdapter.apply(effect, info)); - } - - @Test public void testStepAndRampSegments_withEmptyFreqMapping_returnsSameAmplitudesAndZeroFreq() { VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10), diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java new file mode 100644 index 000000000000..95c3bd93e69b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java @@ -0,0 +1,122 @@ +/* + * 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.vibrator; + +import static org.junit.Assert.assertEquals; + +import android.hardware.vibrator.IVibrator; +import android.os.VibrationEffect; +import android.os.VibratorInfo; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.platform.test.annotations.Presubmit; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +/** + * Tests for {@link RampToStepAdapter}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:RampToStepAdapterTest + */ +@Presubmit +public class RampToStepAdapterTest { + private static final int TEST_STEP_DURATION = 5; + + private RampToStepAdapter mAdapter; + + @Before + public void setUp() throws Exception { + mAdapter = new RampToStepAdapter(TEST_STEP_DURATION); + } + + @Test + public void testStepAndPrebakedAndPrimitiveSegments_keepsListUnchanged() { + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10), + new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT), + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10))); + List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments); + + assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo())); + assertEquals(1, mAdapter.apply(segments, 1, createVibratorInfo())); + + assertEquals(originalSegments, segments); + } + + @Test + public void testRampSegments_withPwleCapability_keepsListUnchanged() { + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f, + /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20))); + List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo)); + assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo)); + + assertEquals(originalSegments, segments); + } + + @Test + public void testRampSegments_withoutPwleCapability_convertsRampsToSteps() { + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f, + /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f, + /* startFrequency= */ -3, /* endFrequency= */ 0, /* duration= */ 11), + new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f, + /* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 200))); + + List<VibrationEffectSegment> expectedSegments = Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100), + // 10ms ramp becomes 2 steps + new StepSegment(/* amplitude= */ 1, /* frequency= */ -4, /* duration= */ 5), + new StepSegment(/* amplitude= */ 0.2f, /* frequency= */ 2, /* duration= */ 5), + // 11ms ramp becomes 3 steps + new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ -3, /* duration= */ 5), + new StepSegment(/* amplitude= */ 0.6f, /* frequency= */ -2, /* duration= */ 5), + new StepSegment(/* amplitude= */ 0.2f, /* frequency= */ 0, /* duration= */ 1), + // 200ms ramp with same amplitude becomes a single step + new StepSegment(/* amplitude= */ 0.65f, /* frequency= */ 0, /* duration= */ 200)); + + // Repeat index fixed after intermediate steps added + assertEquals(4, mAdapter.apply(segments, 3, createVibratorInfo())); + + assertEquals(expectedSegments, segments); + } + + private static VibratorInfo createVibratorInfo(int... capabilities) { + return new VibratorInfo.Builder(0) + .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0)) + .build(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java new file mode 100644 index 000000000000..f4eb2ded5a9d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java @@ -0,0 +1,312 @@ +/* + * 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.vibrator; + +import static org.junit.Assert.assertEquals; + +import android.hardware.vibrator.IVibrator; +import android.os.VibrationEffect; +import android.os.VibratorInfo; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.platform.test.annotations.Presubmit; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +/** + * Tests for {@link StepToRampAdapter}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:StepToRampAdapterTest + */ +@Presubmit +public class StepToRampAdapterTest { + private StepToRampAdapter mAdapter; + + @Before + public void setUp() throws Exception { + mAdapter = new StepToRampAdapter(/* rampDownDuration= */ 0); + } + + @Test + public void testRampAndPrebakedAndPrimitiveSegments_returnsOriginalSegments() { + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f, + /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10), + new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT), + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10))); + List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments); + + assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo())); + assertEquals(1, mAdapter.apply(segments, 1, createVibratorInfo())); + + assertEquals(originalSegments, segments); + } + + @Test + public void testStepAndRampSegments_withoutPwleCapability_keepsListUnchanged() { + mAdapter = new StepToRampAdapter(50); + + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f, + /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20))); + List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments); + + assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo())); + assertEquals(0, mAdapter.apply(segments, 0, createVibratorInfo())); + + assertEquals(originalSegments, segments); + } + + @Test + public void testStepAndRampSegments_withPwleCapabilityAndNoFrequency_keepsOriginalSteps() { + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100), + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, + /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f, + /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20))); + List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo)); + assertEquals(3, mAdapter.apply(segments, 3, vibratorInfo)); + + assertEquals(originalSegments, segments); + } + + @Test + public void testStepAndRampSegments_withPwleCapabilityAndStepNextToRamp_convertsStepsToRamps() { + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, + /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f, + /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20), + new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ -1, /* duration= */ 60))); + + List<VibrationEffectSegment> expectedSegments = Arrays.asList( + new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f, + /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, + /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f, + /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f, + /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 60)); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo)); + assertEquals(2, mAdapter.apply(segments, 2, vibratorInfo)); + + assertEquals(expectedSegments, segments); + } + + @Test + public void testStepSegments_withPwleCapabilityAndFrequency_convertsStepsToRamps() { + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ -1, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 1, /* duration= */ 100))); + List<VibrationEffectSegment> expectedSegments = Arrays.asList( + new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0, + /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 100)); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo)); + assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo)); + + assertEquals(expectedSegments, segments); + } + + @Test + public void testStepSegments_withRampDownEndingAtNonZero_noRampDownAdded() { + int rampDownDuration = 50; + mAdapter = new StepToRampAdapter(rampDownDuration); + + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 1, /* duration= */ 100))); + List<VibrationEffectSegment> expectedSegments = Arrays.asList( + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f, + /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 100)); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo)); + + assertEquals(expectedSegments, segments); + } + + @Test + public void testStepSegments_withRampDownAndShortZeroSegment_replaceWithRampDown() { + mAdapter = new StepToRampAdapter(50); + + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 20), + new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30))); + List<VibrationEffectSegment> expectedSegments = Arrays.asList( + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f, + /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0, + /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 20), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30)); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + assertEquals(2, mAdapter.apply(segments, 2, vibratorInfo)); + + assertEquals(expectedSegments, segments); + } + + @Test + public void testStepSegments_withRampDownAndLongZeroSegment_splitAndAddRampDown() { + mAdapter = new StepToRampAdapter(50); + + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 150), + new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30))); + List<VibrationEffectSegment> expectedSegments = Arrays.asList( + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f, + /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0, + /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 50), + new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30)); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + // Repeat index fixed after intermediate steps added + assertEquals(3, mAdapter.apply(segments, 2, vibratorInfo)); + + assertEquals(expectedSegments, segments); + } + + @Test + public void testStepSegments_withRampDownAndNoZeroSegment_noRampDownAdded() { + mAdapter = new StepToRampAdapter(50); + + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10), + new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30), + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10))); + List<VibrationEffectSegment> expectedSegments = Arrays.asList( + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f, + /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30), + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo)); + + assertEquals(expectedSegments, segments); + } + + @Test + public void testStepSegments_withRampDownAndRepeatToNonZeroSegment_noRampDownAdded() { + mAdapter = new StepToRampAdapter(50); + + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10), + new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30))); + List<VibrationEffectSegment> expectedSegments = Arrays.asList( + new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f, + /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30)); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo)); + + assertEquals(expectedSegments, segments); + } + + @Test + public void testStepSegments_withRampDownAndRepeatToShortZeroSegment_skipAndAppendRampDown() { + mAdapter = new StepToRampAdapter(50); + + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 20), + new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30))); + List<VibrationEffectSegment> expectedSegments = Arrays.asList( + new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 20), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 20)); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + // Shift repeat index to the right to use append instead of zero segment. + assertEquals(1, mAdapter.apply(segments, 0, vibratorInfo)); + + assertEquals(expectedSegments, segments); + } + + @Test + public void testStepSegments_withRampDownAndRepeatToLongZeroSegment_splitAndAppendRampDown() { + mAdapter = new StepToRampAdapter(50); + + List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 120), + new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30))); + List<VibrationEffectSegment> expectedSegments = Arrays.asList( + // Split long zero segment to skip part of it. + new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 50), + new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 70), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0, + /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 50)); + + VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); + // Shift repeat index to the right to use append with part of the zero segment. + assertEquals(1, mAdapter.apply(segments, 0, vibratorInfo)); + + assertEquals(expectedSegments, segments); + } + + private static VibratorInfo createVibratorInfo(int... capabilities) { + return new VibratorInfo.Builder(0) + .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0)) + .build(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index ac3e05d27962..f02e2f081e3b 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; import android.hardware.vibrator.Braking; import android.hardware.vibrator.IVibrator; import android.hardware.vibrator.IVibratorManager; @@ -98,13 +99,17 @@ public class VibrationThreadTest { private IBatteryStats mIBatteryStatsMock; private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>(); + private DeviceVibrationEffectAdapter mEffectAdapter; private PowerManager.WakeLock mWakeLock; private TestLooper mTestLooper; @Before public void setUp() throws Exception { mTestLooper = new TestLooper(); - mWakeLock = InstrumentationRegistry.getContext().getSystemService( + + Context context = InstrumentationRegistry.getContext(); + mEffectAdapter = new DeviceVibrationEffectAdapter(context); + mWakeLock = context.getSystemService( PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); mockVibrators(VIBRATOR_ID); @@ -985,8 +990,8 @@ public class VibrationThreadTest { } private VibrationThread startThreadAndDispatcher(Vibration vib) { - VibrationThread thread = new VibrationThread(vib, createVibratorControllers(), mWakeLock, - mIBatteryStatsMock, mThreadCallbacks); + VibrationThread thread = new VibrationThread(vib, mEffectAdapter, + createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks); doAnswer(answer -> { thread.vibratorComplete(answer.getArgument(0)); return null; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index 5614aa2a165d..577e36c7d5db 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -394,7 +394,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { "disabledMessage", 0, "disabledMessageResName", null, null, 0, null, 0, 0, 0, "iconResName", "bitmapPath", null, 0, - null, null, 0); + null, null, null); return si; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 70ba2cf17880..a522b5c6161e 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6472,7 +6472,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { zenPolicy, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled); try { - mBinderService.addAutomaticZenRule(rule); + mBinderService.addAutomaticZenRule(rule, mContext.getPackageName()); fail("Zen policy only applies to priority only mode"); } catch (IllegalArgumentException e) { // yay @@ -6480,11 +6480,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled); - mBinderService.addAutomaticZenRule(rule); + mBinderService.addAutomaticZenRule(rule, mContext.getPackageName()); rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), null, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled); - mBinderService.addAutomaticZenRule(rule); + mBinderService.addAutomaticZenRule(rule, mContext.getPackageName()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index 5262465a399c..d0bf63a1680f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -190,7 +190,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.configurationActivity = new ComponentName("a", "a"); - rule.component = new ComponentName("a", "b"); + rule.component = new ComponentName("b", "b"); rule.conditionId = new Uri.Builder().scheme("hello").build(); rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE); rule.enabled = true; @@ -200,6 +200,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.modified = true; rule.name = "name"; rule.snoozing = true; + rule.pkg = "b"; TypedXmlSerializer out = Xml.newFastSerializer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -215,8 +216,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { new ByteArrayInputStream(baos.toByteArray())), null); parser.nextTag(); ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser); - // read from backing component - assertEquals("a", fromXml.pkg); + assertEquals("b", fromXml.pkg); // always resets on reboot assertFalse(fromXml.snoozing); //should all match original @@ -232,6 +232,55 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.zenMode, fromXml.zenMode); } + @Test + public void testRuleXml_pkg_component() throws Exception { + String tag = "tag"; + + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = new ComponentName("a", "a"); + rule.component = new ComponentName("b", "b"); + + TypedXmlSerializer out = Xml.newFastSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + out.setOutput(new BufferedOutputStream(baos), "utf-8"); + out.startDocument(null, true); + out.startTag(null, tag); + ZenModeConfig.writeRuleXml(rule, out); + out.endTag(null, tag); + out.endDocument(); + + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser); + assertEquals("b", fromXml.pkg); + } + + @Test + public void testRuleXml_pkg_configActivity() throws Exception { + String tag = "tag"; + + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = new ComponentName("a", "a"); + + TypedXmlSerializer out = Xml.newFastSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + out.setOutput(new BufferedOutputStream(baos), "utf-8"); + out.startDocument(null, true); + out.startTag(null, tag); + ZenModeConfig.writeRuleXml(rule, out); + out.endTag(null, tag); + out.endDocument(); + + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser); + assertNull(fromXml.pkg); + } + private ZenModeConfig getMutedRingerConfig() { ZenModeConfig config = new ZenModeConfig(); // Allow alarms, media diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 72c6028e5ad3..00dbaf649ca2 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -1597,7 +1597,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ComponentName("android", "ScheduleConditionProvider"), ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelperSpy.addAutomaticZenRule(zenRule, "test"); + String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test"); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelperSpy.mConfig.automaticRules.get(id); @@ -1617,12 +1617,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ComponentName("android", "ScheduleConditionProvider"), sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelperSpy.addAutomaticZenRule(zenRule, "test"); + String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test"); AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", new ComponentName("android", "ScheduleConditionProvider"), sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id2 = mZenModeHelperSpy.addAutomaticZenRule(zenRule2, "test"); + String id2 = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule2, "test"); Condition condition = new Condition(sharedUri, "", Condition.STATE_TRUE); mZenModeHelperSpy.setAutomaticZenRuleState(sharedUri, condition); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index d2270b55b954..4e261deb67f6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1534,30 +1534,6 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testSandboxDisplayApis_unresizableAppNotSandboxed() { - // Set up a display in landscape with an unresizable app. - setUpDisplaySizeWithApp(2500, 1000); - mActivity.mDisplayContent.setSandboxDisplayApis(false /* sandboxDisplayApis */); - prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE); - assertFitted(); - - // Activity max bounds not be sandboxed since sandboxing is disabled. - assertMaxBoundsInheritDisplayAreaBounds(); - } - - @Test - public void testSandboxDisplayApis_unresizableAppSandboxed() { - // Set up a display in landscape with an unresizable app. - setUpDisplaySizeWithApp(2500, 1000); - mActivity.mDisplayContent.setSandboxDisplayApis(true /* sandboxDisplayApis */); - prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE); - assertFitted(); - - // Activity max bounds should be sandboxed since sandboxing is enabled. - assertActivityMaxBoundsSandboxed(); - } - - @Test public void testResizableApp_notSandboxed() { // Set up a display in landscape with a fully resizable app. setUpDisplaySizeWithApp(2500, 1000); 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 3f1248a5fff7..a1b3159825fb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -796,6 +796,9 @@ public class WindowOrganizerTests extends WindowTestsBase { @Override public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { } + @Override + public void onAppSplashScreenViewRemoved(int taskId) { + } }; private ActivityRecord makePipableActivity() { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index a14912e5593d..bc812c2ae4a7 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -562,11 +562,10 @@ public class VoiceInteractionManagerService extends SystemService { } void switchImplementationIfNeededLocked(boolean force) { - if (!mCurUserSupported || mTemporarilyDisabled) { + if (!mCurUserSupported) { if (DEBUG_USER) { Slog.d(TAG, "switchImplementationIfNeeded(): skipping: force= " + force - + "mCurUserSupported=" + mCurUserSupported - + "mTemporarilyDisabled=" + mTemporarilyDisabled); + + "mCurUserSupported=" + mCurUserSupported); } if (mImpl != null) { mImpl.shutdownLocked(); @@ -1048,13 +1047,16 @@ public class VoiceInteractionManagerService extends SystemService { if (DEBUG) Slog.d(TAG, "setDisabled(): already " + disabled); return; } - Slog.i(TAG, "setDisabled(): changing to " + disabled); - final long caller = Binder.clearCallingIdentity(); - try { - mTemporarilyDisabled = disabled; - switchImplementationIfNeeded(/* force= */ false); - } finally { - Binder.restoreCallingIdentity(caller); + mTemporarilyDisabled = disabled; + if (mTemporarilyDisabled) { + Slog.i(TAG, "setDisabled(): temporarily disabling and hiding current session"); + try { + hideCurrentSession(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to call hideCurrentSession", e); + } + } else { + Slog.i(TAG, "setDisabled(): re-enabling"); } } } @@ -1508,12 +1510,20 @@ public class VoiceInteractionManagerService extends SystemService { public boolean showSessionForActiveService(Bundle args, int sourceFlags, IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) { enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + if (DEBUG_USER) Slog.d(TAG, "showSessionForActiveService()"); + synchronized (this) { if (mImpl == null) { Slog.w(TAG, "showSessionForActiveService without running voice interaction" + "service"); return false; } + if (mTemporarilyDisabled) { + Slog.i(TAG, "showSessionForActiveService(): ignored while temporarily " + + "disabled"); + return false; + } + final long caller = Binder.clearCallingIdentity(); try { return mImpl.showSessionLocked(args, @@ -1530,22 +1540,21 @@ public class VoiceInteractionManagerService extends SystemService { @Override public void hideCurrentSession() throws RemoteException { enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); - synchronized (this) { - if (mImpl == null) { - return; - } - final long caller = Binder.clearCallingIdentity(); - try { - if (mImpl.mActiveSession != null && mImpl.mActiveSession.mSession != null) { - try { - mImpl.mActiveSession.mSession.closeSystemDialogs(); - } catch (RemoteException e) { - Log.w(TAG, "Failed to call closeSystemDialogs", e); - } + + if (mImpl == null) { + return; + } + final long caller = Binder.clearCallingIdentity(); + try { + if (mImpl.mActiveSession != null && mImpl.mActiveSession.mSession != null) { + try { + mImpl.mActiveSession.mSession.closeSystemDialogs(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to call closeSystemDialogs", e); } - } finally { - Binder.restoreCallingIdentity(caller); } + } finally { + Binder.restoreCallingIdentity(caller); } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java index 6c355a3b4b30..2e3ca0157a3b 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java @@ -71,6 +71,7 @@ final class VoiceInteractionManagerServiceShellCommand extends ShellCommand { pw.println(""); pw.println(" hide"); pw.println(" Hides the current session"); + pw.println(""); pw.println(" disable [true|false]"); pw.println(" Temporarily disable (when true) service"); pw.println(""); |