diff options
288 files changed, 8148 insertions, 2718 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 175fb38bafc7..65b2511bb30f 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -22,7 +22,7 @@ checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPL strings_lint_hook = ${REPO_ROOT}/frameworks/base/tools/stringslint/stringslint_sha.sh ${PREUPLOAD_COMMIT} -hidden_api_txt_checksorted_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} +hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} diff --git a/StubLibraries.bp b/StubLibraries.bp index 23dc7207343c..bc3f131743f9 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -96,6 +96,8 @@ stubs_defaults { ], api_levels_annotations_enabled: false, filter_packages: packages_to_document, + defaults_visibility: ["//visibility:private"], + visibility: ["//frameworks/base/api"], } ///////////////////////////////////////////////////////////////////// @@ -352,6 +354,8 @@ java_defaults { tag: ".jar", dest: "android-non-updatable.jar", }, + defaults_visibility: ["//visibility:private"], + visibility: ["//visibility:private"], } java_library_static { @@ -405,6 +409,8 @@ java_defaults { system_modules: "none", java_version: "1.8", compile_dex: true, + defaults_visibility: ["//visibility:private"], + visibility: ["//visibility:public"], } java_defaults { @@ -417,6 +423,7 @@ java_defaults { tag: ".jar", dest: "android.jar", }, + defaults_visibility: ["//frameworks/base/services"], } java_library_static { @@ -516,6 +523,7 @@ droidstubs { "metalava-manual", ], args: priv_apps, + visibility: ["//visibility:private"], } java_library_static { @@ -525,4 +533,5 @@ java_library_static { srcs: [ ":hwbinder-stubs-docs", ], + visibility: ["//visibility:public"], } diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java index 35cea3e2a67b..6c624264b9be 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java @@ -26,6 +26,7 @@ import com.android.internal.util.Preconditions; import java.util.Collections; import java.util.Map; +import java.util.Objects; /** * Provides results for AppSearch batch operations which encompass multiple documents. @@ -180,7 +181,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl public Builder<KeyType, ValueType> setSuccess( @NonNull KeyType key, @Nullable ValueType result) { Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkNotNull(key); + Objects.requireNonNull(key); return setResult(key, AppSearchResult.newSuccessfulResult(result)); } @@ -198,7 +199,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl @AppSearchResult.ResultCode int resultCode, @Nullable String errorMessage) { Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkNotNull(key); + Objects.requireNonNull(key); return setResult(key, AppSearchResult.newFailedResult(resultCode, errorMessage)); } @@ -214,8 +215,8 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl public Builder<KeyType, ValueType> setResult( @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) { Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkNotNull(key); - Preconditions.checkNotNull(result); + Objects.requireNonNull(key); + Objects.requireNonNull(result); if (result.isSuccess()) { mSuccesses.put(key, result.getResultValue()); mFailures.remove(key); diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java index ac91bdb2dce7..c85c4c3a8a7f 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -19,9 +19,9 @@ package android.app.appsearch; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.appsearch.exceptions.AppSearchException; import android.app.appsearch.util.SchemaMigrationUtil; import android.os.Bundle; -import android.os.ParcelableException; import android.os.RemoteException; import android.os.SystemClock; import android.util.ArrayMap; @@ -274,12 +274,14 @@ public final class AppSearchSession implements Closeable { mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId, /*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(), new IAppSearchBatchResultCallback.Stub() { + @Override public void onResult(AppSearchBatchResult result) { executor.execute(() -> callback.onResult(result)); } - public void onSystemError(ParcelableException exception) { - executor.execute(() -> callback.onSystemError(exception.getCause())); + @Override + public void onSystemError(AppSearchResult result) { + executor.execute(() -> sendSystemErrorToCallback(result, callback)); } }); mIsMutated = true; @@ -321,6 +323,7 @@ public final class AppSearchSession implements Closeable { request.getProjectionsInternal(), mUserId, new IAppSearchBatchResultCallback.Stub() { + @Override public void onResult(AppSearchBatchResult result) { executor.execute(() -> { AppSearchBatchResult.Builder<String, GenericDocument> @@ -359,8 +362,9 @@ public final class AppSearchSession implements Closeable { }); } - public void onSystemError(ParcelableException exception) { - executor.execute(() -> callback.onSystemError(exception.getCause())); + @Override + public void onSystemError(AppSearchResult result) { + executor.execute(() -> sendSystemErrorToCallback(result, callback)); } }); } catch (RemoteException e) { @@ -515,12 +519,14 @@ public final class AppSearchSession implements Closeable { mService.removeByUri(mPackageName, mDatabaseName, request.getNamespace(), new ArrayList<>(request.getUris()), mUserId, new IAppSearchBatchResultCallback.Stub() { + @Override public void onResult(AppSearchBatchResult result) { executor.execute(() -> callback.onResult(result)); } - public void onSystemError(ParcelableException exception) { - executor.execute(() -> callback.onSystemError(exception.getCause())); + @Override + public void onSystemError(AppSearchResult result) { + executor.execute(() -> sendSystemErrorToCallback(result, callback)); } }); mIsMutated = true; @@ -817,4 +823,21 @@ public final class AppSearchSession implements Closeable { } }); } + + /** + * Calls {@link BatchResultCallback#onSystemError} with a throwable derived from the given + * failed {@link AppSearchResult}. + * + * <p>The {@link AppSearchResult} generally comes from + * {@link IAppSearchBatchResultCallback#onSystemError}. + * + * <p>This method should be called from the callback executor thread. + */ + private void sendSystemErrorToCallback( + @NonNull AppSearchResult<?> failedResult, @NonNull BatchResultCallback<?, ?> callback) { + Preconditions.checkArgument(!failedResult.isSuccess()); + Throwable throwable = new AppSearchException( + failedResult.getResultCode(), failedResult.getErrorMessage()); + callback.onSystemError(throwable); + } } diff --git a/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java b/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java index 49049b641839..28f8a7a78f4a 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java +++ b/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java @@ -36,13 +36,23 @@ public interface BatchResultCallback<KeyType, ValueType> { void onResult(@NonNull AppSearchBatchResult<KeyType, ValueType> result); /** - * Called when a system error occurred. + * Called when a system error occurs. * - * @param throwable The cause throwable. + * <p>This method is only called the infrastructure is fundamentally broken or unavailable, such + * that none of the requests could be started. For example, it will be called if the AppSearch + * service unexpectedly fails to initialize and can't be recovered by any means, or if + * communicating to the server over Binder fails (e.g. system service crashed or device is + * rebooting). + * + * <p>The error is not expected to be recoverable and there is no specific recommended action + * other than displaying a permanent message to the user. + * + * <p>Normal errors that are caused by invalid inputs or recoverable/retriable situations + * are reported associated with the input that caused them via the {@link #onResult} method. + * + * @param throwable an exception describing the system error */ default void onSystemError(@Nullable Throwable throwable) { - if (throwable != null) { - throw new RuntimeException(throwable); - } + throw new RuntimeException("Unrecoverable system error", throwable); } } diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl index b1bbd18b98e2..64b331ea374c 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl @@ -16,10 +16,10 @@ package android.app.appsearch; import android.app.appsearch.AppSearchBatchResult; -import android.os.ParcelableException; +import android.app.appsearch.AppSearchResult; /** {@hide} */ oneway interface IAppSearchBatchResultCallback { void onResult(in AppSearchBatchResult result); - void onSystemError(in ParcelableException exception); -}
\ No newline at end of file + void onSystemError(in AppSearchResult result); +} diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl index 27729a5ad058..299c9957974e 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl @@ -16,9 +16,8 @@ package android.app.appsearch; import android.app.appsearch.AppSearchResult; -import android.os.ParcelableException; /** {@hide} */ oneway interface IAppSearchResultCallback { void onResult(in AppSearchResult result); -}
\ No newline at end of file +} 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 c3f197853872..f6f5c98856c7 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -44,7 +44,6 @@ import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.Bundle; import android.os.ParcelFileDescriptor; -import android.os.ParcelableException; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -55,7 +54,6 @@ import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.appsearch.external.localstorage.AppSearchImpl; @@ -76,7 +74,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; -import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -95,7 +93,7 @@ public class AppSearchManagerService extends SystemService { // mutate requests will need to gain write lock and query requests need to gain read lock. private static final Executor EXECUTOR = new ThreadPoolExecutor(/*corePoolSize=*/1, Runtime.getRuntime().availableProcessors(), /*keepAliveTime*/ 60L, TimeUnit.SECONDS, - new SynchronousQueue<Runnable>()); + new LinkedBlockingQueue<>()); // Cache of unlocked user ids so we don't have to query UserManager service each time. The // "locked" suffix refers to the fact that access to the field should be locked; unrelated to @@ -182,10 +180,10 @@ public class AppSearchManagerService extends SystemService { int schemaVersion, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(schemaBundles); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(schemaBundles); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -231,9 +229,9 @@ public class AppSearchManagerService extends SystemService { @NonNull String databaseName, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -258,9 +256,9 @@ public class AppSearchManagerService extends SystemService { @NonNull String databaseName, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -286,10 +284,10 @@ public class AppSearchManagerService extends SystemService { @UserIdInt int userId, @ElapsedRealtimeLong long binderCallStartTimeMillis, @NonNull IAppSearchBatchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(documentBundles); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(documentBundles); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -360,11 +358,11 @@ public class AppSearchManagerService extends SystemService { @NonNull Map<String, List<String>> typePropertyPaths, @UserIdInt int userId, @NonNull IAppSearchBatchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(namespace); - Preconditions.checkNotNull(uris); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(namespace); + Objects.requireNonNull(uris); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -405,11 +403,11 @@ public class AppSearchManagerService extends SystemService { @NonNull Bundle searchSpecBundle, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(queryExpression); - Preconditions.checkNotNull(searchSpecBundle); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(queryExpression); + Objects.requireNonNull(searchSpecBundle); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -440,10 +438,10 @@ public class AppSearchManagerService extends SystemService { @NonNull Bundle searchSpecBundle, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(queryExpression); - Preconditions.checkNotNull(searchSpecBundle); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(queryExpression); + Objects.requireNonNull(searchSpecBundle); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -472,7 +470,7 @@ public class AppSearchManagerService extends SystemService { long nextPageToken, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(callback); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); // TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally @@ -645,10 +643,10 @@ public class AppSearchManagerService extends SystemService { @NonNull List<String> uris, @UserIdInt int userId, @NonNull IAppSearchBatchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(uris); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(uris); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -685,11 +683,11 @@ public class AppSearchManagerService extends SystemService { @NonNull Bundle searchSpecBundle, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(queryExpression); - Preconditions.checkNotNull(searchSpecBundle); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(queryExpression); + Objects.requireNonNull(searchSpecBundle); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -718,9 +716,9 @@ public class AppSearchManagerService extends SystemService { @NonNull String databaseName, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -758,7 +756,7 @@ public class AppSearchManagerService extends SystemService { @Override public void initialize(@UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(callback); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -789,7 +787,7 @@ public class AppSearchManagerService extends SystemService { } private void verifyCallingPackage(int callingUid, @NonNull String callingPackage) { - Preconditions.checkNotNull(callingPackage); + Objects.requireNonNull(callingPackage); if (mPackageManagerInternal.getPackageUid( callingPackage, /*flags=*/ 0, UserHandle.getUserId(callingUid)) != callingUid) { @@ -837,13 +835,12 @@ public class AppSearchManagerService extends SystemService { /** * Invokes the {@link IAppSearchBatchResultCallback} with an unexpected internal throwable. * - * <p>The throwable is converted to {@link ParcelableException}. + * <p>The throwable is converted to {@link AppSearchResult}. */ private void invokeCallbackOnError( - IAppSearchBatchResultCallback callback, Throwable throwable) { + @NonNull IAppSearchBatchResultCallback callback, @NonNull Throwable throwable) { try { - //TODO(b/175067650) verify ParcelableException could propagate throwable correctly. - callback.onSystemError(new ParcelableException(throwable)); + callback.onSystemError(throwableToFailedResult(throwable)); } catch (RemoteException e) { Log.e(TAG, "Unable to send error to the callback", e); } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java index c1b829428467..4de52fb65d93 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java @@ -35,7 +35,6 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; -import com.android.internal.util.Preconditions; import com.android.server.appsearch.external.localstorage.util.PrefixUtil; import java.util.ArrayList; @@ -43,6 +42,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -333,9 +333,9 @@ public class VisibilityStore { @NonNull Set<String> schemasNotPlatformSurfaceable, @NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible) throws AppSearchException { - Preconditions.checkNotNull(prefix); - Preconditions.checkNotNull(schemasNotPlatformSurfaceable); - Preconditions.checkNotNull(schemasPackageAccessible); + Objects.requireNonNull(prefix); + Objects.requireNonNull(schemasNotPlatformSurfaceable); + Objects.requireNonNull(schemasPackageAccessible); // Persist the document GenericDocument.Builder<?> visibilityDocument = @@ -383,8 +383,8 @@ public class VisibilityStore { /** Checks whether {@code prefixedSchema} can be searched over by the {@code callerUid}. */ public boolean isSchemaSearchableByCaller( @NonNull String prefix, @NonNull String prefixedSchema, int callerUid) { - Preconditions.checkNotNull(prefix); - Preconditions.checkNotNull(prefixedSchema); + Objects.requireNonNull(prefix); + Objects.requireNonNull(prefixedSchema); // We compare appIds here rather than direct uids because the package's uid may change based // on the user that's running. 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 1c04d99ac2c7..731ab35bf367 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 @@ -29,7 +29,6 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import com.android.internal.util.Preconditions; import com.android.server.appsearch.external.localstorage.AppSearchLogger; import com.android.server.appsearch.external.localstorage.stats.CallStats; import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats; @@ -38,6 +37,7 @@ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Map; +import java.util.Objects; import java.util.Random; /** @@ -162,15 +162,15 @@ public final class PlatformLogger implements AppSearchLogger { * Westworld constructor */ public PlatformLogger(@NonNull Context context, int userId, @NonNull Config config) { - mContext = Preconditions.checkNotNull(context); - mConfig = Preconditions.checkNotNull(config); + mContext = Objects.requireNonNull(context); + mConfig = Objects.requireNonNull(config); mUserId = userId; } /** Logs {@link CallStats}. */ @Override public void logStats(@NonNull CallStats stats) { - Preconditions.checkNotNull(stats); + Objects.requireNonNull(stats); synchronized (mLock) { if (shouldLogForTypeLocked(stats.getCallType())) { logStatsImplLocked(stats); @@ -181,7 +181,7 @@ public final class PlatformLogger implements AppSearchLogger { /** Logs {@link PutDocumentStats}. */ @Override public void logStats(@NonNull PutDocumentStats stats) { - Preconditions.checkNotNull(stats); + Objects.requireNonNull(stats); synchronized (mLock) { if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) { logStatsImplLocked(stats); @@ -197,7 +197,7 @@ public final class PlatformLogger implements AppSearchLogger { */ public int removeCachedUidForPackage(@NonNull String packageName) { // TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent - Preconditions.checkNotNull(packageName); + Objects.requireNonNull(packageName); synchronized (mLock) { Integer uid = mPackageUidCacheLocked.remove(packageName); return uid != null ? uid : Process.INVALID_UID; diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java index f0de4962ad3c..61933672a1e1 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java @@ -40,11 +40,11 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; -import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -81,8 +81,8 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { private AppSearchSessionShimImpl( @NonNull AppSearchSession session, @NonNull ExecutorService executor) { - mAppSearchSession = Preconditions.checkNotNull(session); - mExecutor = Preconditions.checkNotNull(executor); + mAppSearchSession = Objects.requireNonNull(session); + mExecutor = Objects.requireNonNull(executor); } @Override diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java index 5042ce0efbd8..c35849d23c26 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java @@ -30,11 +30,11 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; -import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -69,8 +69,8 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim { private GlobalSearchSessionShimImpl( @NonNull GlobalSearchSession session, @NonNull ExecutorService executor) { - mGlobalSearchSession = Preconditions.checkNotNull(session); - mExecutor = Preconditions.checkNotNull(executor); + mGlobalSearchSession = Objects.requireNonNull(session); + mExecutor = Objects.requireNonNull(executor); } @NonNull diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java index 5f26e8cba585..72078f855d70 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java @@ -22,12 +22,12 @@ import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchResultsShim; -import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -40,8 +40,8 @@ public class SearchResultsShimImpl implements SearchResultsShim { private final SearchResults mSearchResults; SearchResultsShimImpl(@NonNull SearchResults searchResults, @NonNull Executor executor) { - mExecutor = Preconditions.checkNotNull(executor); - mSearchResults = Preconditions.checkNotNull(searchResults); + mExecutor = Objects.requireNonNull(executor); + mSearchResults = Objects.requireNonNull(searchResults); } @NonNull 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 31da2017bdef..03d9a968f790 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -1684,17 +1684,13 @@ public class AlarmManagerService extends SystemService { } } - if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) { - // Do not support windows for idle-until alarms. - windowLength = AlarmManager.WINDOW_EXACT; - } - // Snap the window to reasonable limits. if (windowLength > INTERVAL_DAY) { Slog.w(TAG, "Window length " + windowLength + "ms suspiciously long; limiting to 1 day"); windowLength = INTERVAL_DAY; - } else if (windowLength > 0 && windowLength < mConstants.MIN_WINDOW) { + } else if (windowLength > 0 && windowLength < mConstants.MIN_WINDOW + && (flags & FLAG_PRIORITIZE) == 0) { if (CompatChanges.isChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, callingPackage, UserHandle.getUserHandleForUid(callingUid))) { Slog.w(TAG, "Window length " + windowLength + "ms too short; expanding to " @@ -1741,7 +1737,7 @@ public class AlarmManagerService extends SystemService { final long triggerElapsed = (nominalTrigger > minTrigger) ? nominalTrigger : minTrigger; final long maxElapsed; - if (windowLength == AlarmManager.WINDOW_EXACT) { + if (windowLength == 0) { maxElapsed = triggerElapsed; } else if (windowLength < 0) { maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval); @@ -2145,17 +2141,63 @@ public class AlarmManagerService extends SystemService { + " does not belong to the calling uid " + callingUid); } + // Repeating alarms must use PendingIntent, not direct listener + if (interval != 0 && directReceiver != null) { + throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers"); + } + + if (workSource != null) { + getContext().enforcePermission( + android.Manifest.permission.UPDATE_DEVICE_STATS, + Binder.getCallingPid(), callingUid, "AlarmManager.set"); + } + + if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) { + // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm + // manager when to come out of idle mode, which is only for DeviceIdleController. + if (callingUid != Process.SYSTEM_UID) { + // TODO (b/169463012): Throw instead of tolerating this mistake. + flags &= ~AlarmManager.FLAG_IDLE_UNTIL; + } else { + // Do not support windows for idle-until alarms. + windowLength = 0; + } + } + + // Remove flags reserved for the service, we will apply those later as appropriate. + flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED + | FLAG_ALLOW_WHILE_IDLE_COMPAT); + + // If this alarm is for an alarm clock, then it must be exact and we will + // use it to wake early from idle if needed. + if (alarmClock != null) { + flags |= FLAG_WAKE_FROM_IDLE; + windowLength = 0; + + // If the caller is a core system component or on the user's allowlist, and not calling + // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED. + // This means we will allow these alarms to go off as normal even while idle, with no + // timing restrictions. + } else if (workSource == null && (UserHandle.isCore(callingUid) + || UserHandle.isSameApp(callingUid, mSystemUiUid) + || ((mAppStateTracker != null) + && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) { + flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; + flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_PRIORITIZE); + } + final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0; - final boolean exact = (windowLength == AlarmManager.WINDOW_EXACT); + final boolean exact = (windowLength == 0); - // make sure the caller is allowed to use the requested kind of alarm, and also + // Make sure the caller is allowed to use the requested kind of alarm, and also // decide what quota and broadcast options to use. Bundle idleOptions = null; if ((flags & FLAG_PRIORITIZE) != 0) { getContext().enforcePermission( Manifest.permission.SCHEDULE_PRIORITIZED_ALARM, Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized"); - flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT); + // The API doesn't allow using both together. + flags &= ~FLAG_ALLOW_WHILE_IDLE; } else if (exact || allowWhileIdle) { final boolean needsPermission; boolean lowerQuota; @@ -2193,55 +2235,11 @@ public class AlarmManagerService extends SystemService { } } - // Repeating alarms must use PendingIntent, not direct listener - if (interval != 0) { - if (directReceiver != null) { - throw new IllegalArgumentException( - "Repeating alarms cannot use AlarmReceivers"); - } - } - - if (workSource != null) { - getContext().enforcePermission( - android.Manifest.permission.UPDATE_DEVICE_STATS, - Binder.getCallingPid(), callingUid, "AlarmManager.set"); - } - - // No incoming callers can request either WAKE_FROM_IDLE or - // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate. - flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED); - - // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm - // manager when to come out of idle mode, which is only for DeviceIdleController. - if (callingUid != Process.SYSTEM_UID) { - flags &= ~AlarmManager.FLAG_IDLE_UNTIL; - } - // If this is an exact time alarm, then it can't be batched with other alarms. - if (windowLength == AlarmManager.WINDOW_EXACT) { + if (exact) { flags |= AlarmManager.FLAG_STANDALONE; } - // If this alarm is for an alarm clock, then it must be standalone and we will - // use it to wake early from idle if needed. - if (alarmClock != null) { - flags |= FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE; - - // If the caller is a core system component or on the user's whitelist, and not calling - // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED. - // This means we will allow these alarms to go off as normal even while idle, with no - // timing restrictions. - } else if (workSource == null && (UserHandle.isCore(callingUid) - || UserHandle.isSameApp(callingUid, mSystemUiUid) - || ((mAppStateTracker != null) - && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) { - flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; - flags &= ~FLAG_ALLOW_WHILE_IDLE; - flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT; - flags &= ~FLAG_PRIORITIZE; - idleOptions = null; - } - setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver, listenerTag, flags, workSource, alarmClock, callingUid, callingPackage, idleOptions); diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java index c37d2c36b068..9b1b06658a98 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java @@ -16,7 +16,6 @@ package com.android.server.alarm; -import static com.android.server.alarm.AlarmManagerService.TAG; import static com.android.server.alarm.AlarmManagerService.dumpAlarmList; import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index d94d638a7021..131783f9e04e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -16,6 +16,7 @@ package com.android.server.job; +import static com.android.server.job.JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.annotation.IntDef; @@ -39,7 +40,9 @@ import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; +import android.util.Pools; import android.util.Slog; +import android.util.SparseArrayMap; import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TimeUtils; @@ -60,6 +63,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Iterator; import java.util.List; +import java.util.function.Consumer; /** * This class decides, given the various configuration and the system status, which jobs can start @@ -73,6 +77,12 @@ class JobConcurrencyManager { private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS = CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; + private static final String KEY_PKG_CONCURRENCY_LIMIT_EJ = + CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej"; + private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3; + private static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR = + CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular"; + private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = MAX_JOB_CONTEXTS_COUNT / 2; /** * Set of possible execution types that a job can have. The actual type(s) of a job are based @@ -165,8 +175,6 @@ class JobConcurrencyManager { private long mLastScreenOnRealtime; private long mLastScreenOffRealtime; - private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; - private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON = new WorkConfigLimitsPerMemoryTrimLevel( new WorkTypeConfig("screen_on_normal", 11, @@ -274,11 +282,28 @@ class JobConcurrencyManager { private final WorkCountTracker mWorkCountTracker = new WorkCountTracker(); + private final Pools.Pool<PackageStats> mPkgStatsPool = + new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT); + + private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>(); + private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal; /** Wait for this long after screen off before adjusting the job concurrency. */ private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS; + /** + * The maximum number of expedited jobs a single userId-package can have running simultaneously. + * TOP apps are not limited. + */ + private long mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ; + + /** + * The maximum number of regular jobs a single userId-package can have running simultaneously. + * TOP apps are not limited. + */ + private long mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR; + /** Current memory trim level. */ private int mLastMemoryTrimLevel; @@ -286,6 +311,9 @@ class JobConcurrencyManager { private long mNextSystemStateRefreshTime; private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000; + private final Consumer<PackageStats> mPackageStatsStagingCountClearer = + PackageStats::resetStagedCount; + private final StatLogger mStatLogger = new StatLogger(new String[]{ "assignJobsToContexts", "refreshSystemState", @@ -330,6 +358,21 @@ class JobConcurrencyManager { onInteractiveStateChanged(mPowerManager.isInteractive()); } + @GuardedBy("mLock") + void onAppRemovedLocked(String pkgName, int uid) { + final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName); + if (packageStats != null) { + if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) { + // Don't delete the object just yet. We'll remove it in onJobCompleted() when the + // jobs officially stop running. + Slog.w(TAG, + pkgName + "(" + uid + ") marked as removed before jobs stopped running"); + } else { + mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName); + } + } + } + void onUserRemoved(int userId) { mGracePeriodObserver.onUserRemoved(userId); } @@ -557,6 +600,7 @@ class JobConcurrencyManager { boolean startingJob = false; int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; String preemptReason = null; + final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending); // TODO(141645789): rewrite this to look at empty contexts first so we don't // unnecessarily preempt for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) { @@ -566,7 +610,7 @@ class JobConcurrencyManager { final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) || (preferredUid == JobServiceContext.NO_PREFERRED_UID); - if (preferredUidOkay && workType != WORK_TYPE_NONE) { + if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) { // This slot is free, and we haven't yet hit the limit on // concurrent jobs... we can just throw the job in to here. selectedContextId = j; @@ -579,9 +623,11 @@ class JobConcurrencyManager { continue; } if (job.getUid() != nextPending.getUid()) { - // Maybe stop the job if it has had its day in the sun. + // Maybe stop the job if it has had its day in the sun. Don't let a different + // app preempt jobs started for TOP apps though. final String reason = shouldStopJobReason[j]; - if (reason != null && mWorkCountTracker.canJobStart(allWorkTypes, + if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP + && reason != null && mWorkCountTracker.canJobStart(allWorkTypes, activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) { // Right now, the way the code is set up, we don't need to explicitly // assign the new job to this context since we'll reassign when the @@ -608,23 +654,27 @@ class JobConcurrencyManager { // actually starting a job, so don't set startingJob. } } + final PackageStats packageStats = getPkgStatsLocked( + nextPending.getSourceUserId(), nextPending.getSourcePackageName()); if (selectedContextId != -1) { contextIdToJobMap[selectedContextId] = nextPending; slotChanged[selectedContextId] = true; preemptReasonCodeForContext[selectedContextId] = preemptReasonCode; preemptReasonForContext[selectedContextId] = preemptReason; + packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob()); } if (startingJob) { // Increase the counters when we're going to start a job. workTypeForContext[selectedContextId] = workType; mWorkCountTracker.stageJob(workType, allWorkTypes); + mActivePkgStats.add( + nextPending.getSourceUserId(), nextPending.getSourcePackageName(), + packageStats); } } if (DEBUG) { Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); - } - if (DEBUG) { Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString()); } @@ -660,6 +710,7 @@ class JobConcurrencyManager { } } mWorkCountTracker.resetStagingCount(); + mActivePkgStats.forEach(mPackageStatsStagingCountClearer); noteConcurrency(); } @@ -702,18 +753,66 @@ class JobConcurrencyManager { } @GuardedBy("mLock") + @NonNull + private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) { + PackageStats packageStats = mActivePkgStats.get(userId, packageName); + if (packageStats == null) { + packageStats = mPkgStatsPool.acquire(); + if (packageStats == null) { + packageStats = new PackageStats(); + } + packageStats.setPackage(userId, packageName); + } + return packageStats; + } + + @GuardedBy("mLock") + private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) { + if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { + // Don't restrict top apps' concurrency. The work type limits will make sure + // background jobs have slots to run if the system has resources. + return false; + } + // Use < instead of <= as that gives us a little wiggle room in case a new job comes + // along very shortly. + if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) { + // Don't artificially limit a single package if we don't even have enough jobs to use + // the maximum number of slots. We'll preempt the job later if we need the slot. + return false; + } + final PackageStats packageStats = + mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); + if (packageStats == null) { + // No currently running jobs. + return false; + } + if (jobStatus.shouldTreatAsExpeditedJob()) { + return packageStats.numRunningEj + packageStats.numStagedEj < mPkgConcurrencyLimitEj; + } else { + return packageStats.numRunningRegular + packageStats.numStagedRegular + < mPkgConcurrencyLimitRegular; + } + } + + @GuardedBy("mLock") private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType) { final List<StateController> controllers = mService.mControllers; for (int ic = 0; ic < controllers.size(); ic++) { controllers.get(ic).prepareForExecutionLocked(jobStatus); } + final PackageStats packageStats = + getPkgStatsLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); + packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob()); if (!worker.executeRunnableJob(jobStatus, workType)) { Slog.e(TAG, "Error executing " + jobStatus); mWorkCountTracker.onStagedJobFailed(workType); } else { mRunningJobs.add(jobStatus); mWorkCountTracker.onJobStarted(workType); + packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob()); + mActivePkgStats.add( + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats); } final List<JobStatus> pendingJobs = mService.mPendingJobs; if (pendingJobs.remove(jobStatus)) { @@ -726,6 +825,18 @@ class JobConcurrencyManager { @WorkType final int workType) { mWorkCountTracker.onJobFinished(workType); mRunningJobs.remove(jobStatus); + final PackageStats packageStats = + mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); + if (packageStats == null) { + Slog.wtf(TAG, "Running job didn't have an active PackageStats object"); + } else { + packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob); + if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) { + mActivePkgStats.delete(packageStats.userId, packageStats.packageName); + mPkgStatsPool.release(packageStats); + } + } + final List<JobStatus> pendingJobs = mService.mPendingJobs; if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) { updateCounterConfigLocked(); @@ -746,7 +857,7 @@ class JobConcurrencyManager { } if (worker.getPreferredUid() != nextPending.getUid()) { - if (backupJob == null) { + if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) { int allWorkTypes = getJobWorkTypes(nextPending); int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); if (workAsType != WORK_TYPE_NONE) { @@ -758,6 +869,13 @@ class JobConcurrencyManager { continue; } + // Only bypass the concurrent limit if we had preempted the job due to a higher + // priority job. + if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority + && isPkgConcurrencyLimitedLocked(nextPending)) { + continue; + } + if (highestPriorityJob == null || highestPriorityJob.lastEvaluatedPriority < nextPending.lastEvaluatedPriority) { @@ -815,6 +933,10 @@ class JobConcurrencyManager { continue; } + if (isPkgConcurrencyLimitedLocked(nextPending)) { + continue; + } + final int allWorkTypes = getJobWorkTypes(nextPending); final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); if (workAsType == WORK_TYPE_NONE) { @@ -979,8 +1101,16 @@ class JobConcurrencyManager { CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties); CONFIG_LIMITS_SCREEN_OFF.low.update(properties); CONFIG_LIMITS_SCREEN_OFF.critical.update(properties); + + // Package concurrency limits must in the range [1, MAX_JOB_CONTEXTS_COUNT]. + mPkgConcurrencyLimitEj = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, + properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ))); + mPkgConcurrencyLimitRegular = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, + properties.getInt( + KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR))); } + @GuardedBy("mLock") public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) { pw.println("Concurrency:"); @@ -989,6 +1119,8 @@ class JobConcurrencyManager { pw.println("Configuration:"); pw.increaseIndent(); pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println(); + pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println(); + pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println(); pw.println(); CONFIG_LIMITS_SCREEN_ON.normal.dump(pw); pw.println(); @@ -1033,6 +1165,12 @@ class JobConcurrencyManager { pw.println(mLastMemoryTrimLevel); pw.println(); + pw.println("Active Package stats:"); + pw.increaseIndent(); + mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw)); + pw.decreaseIndent(); + pw.println(); + pw.print("User Grace Period: "); pw.println(mGracePeriodObserver.mGracePeriodExpiration); pw.println(); @@ -1620,4 +1758,53 @@ class JobConcurrencyManager { return sb.toString(); } } + + private static class PackageStats { + public int userId; + public String packageName; + public int numRunningEj; + public int numRunningRegular; + public int numStagedEj; + public int numStagedRegular; + + private void setPackage(int userId, @NonNull String packageName) { + this.userId = userId; + this.packageName = packageName; + numRunningEj = numRunningRegular = 0; + resetStagedCount(); + } + + private void resetStagedCount() { + numStagedEj = numStagedRegular = 0; + } + + private void adjustRunningCount(boolean add, boolean forEj) { + if (forEj) { + numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1)); + } else { + numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1)); + } + } + + private void adjustStagedCount(boolean add, boolean forEj) { + if (forEj) { + numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1)); + } else { + numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1)); + } + } + + @GuardedBy("mLock") + private void dumpLocked(IndentingPrintWriter pw) { + pw.print("PackageStats{"); + pw.print(userId); + pw.print("-"); + pw.print(packageName); + pw.print("#runEJ", numRunningEj); + pw.print("#runReg", numRunningRegular); + pw.print("#stagedEJ", numStagedEj); + pw.print("#stagedReg", numStagedRegular); + pw.println("}"); + } + } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index d72f565be4a0..181566169ed7 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -816,6 +816,7 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); } mDebuggableApps.remove(pkgName); + mConcurrencyManager.onAppRemovedLocked(pkgName, pkgUid); } } else if (Intent.ACTION_USER_ADDED.equals(action)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); @@ -2233,7 +2234,8 @@ public class JobSchedulerService extends com.android.server.SystemService } /** Returns true if both the calling and source users for the job are started. */ - private boolean areUsersStartedLocked(final JobStatus job) { + @GuardedBy("mLock") + public boolean areUsersStartedLocked(final JobStatus job) { boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId()); if (job.getUserId() == job.getSourceUserId()) { return sourceStarted; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java index 999c53fb7daf..12d9c7f52fb5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java @@ -34,6 +34,7 @@ import android.util.Slog; import android.util.SparseArrayMap; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.GuardedBy; import com.android.server.job.JobSchedulerService; import java.util.Objects; @@ -58,13 +59,28 @@ public class ComponentController extends StateController { return; } switch (action) { + case Intent.ACTION_PACKAGE_ADDED: + if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + // Only do this for app updates since new installs won't have any jobs + // scheduled. + final Uri uri = intent.getData(); + final String pkg = uri != null ? uri.getSchemeSpecificPart() : null; + if (pkg != null) { + final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); + final int userId = UserHandle.getUserId(pkgUid); + updateComponentStateForPackage(userId, pkg); + } + } + break; case Intent.ACTION_PACKAGE_CHANGED: final Uri uri = intent.getData(); final String pkg = uri != null ? uri.getSchemeSpecificPart() : null; final String[] changedComponents = intent.getStringArrayExtra( Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); if (pkg != null && changedComponents != null && changedComponents.length > 0) { - updateComponentStateForPackage(pkg); + final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); + final int userId = UserHandle.getUserId(pkgUid); + updateComponentStateForPackage(userId, pkg); } break; case Intent.ACTION_USER_UNLOCKED: @@ -86,6 +102,7 @@ public class ComponentController extends StateController { super(service); final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); mContext.registerReceiverAsUser( @@ -99,6 +116,7 @@ public class ComponentController extends StateController { } @Override + @GuardedBy("mLock") public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { updateComponentEnabledStateLocked(jobStatus); } @@ -108,30 +126,53 @@ public class ComponentController extends StateController { boolean forUpdate) { } + @Override + @GuardedBy("mLock") + public void onAppRemovedLocked(String packageName, int uid) { + clearComponentsForPackageLocked(UserHandle.getUserId(uid), packageName); + } + + @Override + @GuardedBy("mLock") + public void onUserRemovedLocked(int userId) { + mServiceInfoCache.delete(userId); + } + @Nullable - private ServiceInfo getServiceInfo(JobStatus jobStatus) { + @GuardedBy("mLock") + private ServiceInfo getServiceInfoLocked(JobStatus jobStatus) { final ComponentName service = jobStatus.getServiceComponent(); final int userId = jobStatus.getUserId(); - ServiceInfo si = mServiceInfoCache.get(userId, service); - if (si == null) { - try { - // createContextAsUser may potentially be expensive - // TODO: cache user context or improve ContextImpl implementation if this becomes - // a problem - si = mContext.createContextAsUser(UserHandle.of(userId), 0) - .getPackageManager() - .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO); - } catch (NameNotFoundException e) { + if (mServiceInfoCache.contains(userId, service)) { + // Return whatever is in the cache, even if it's null. When something changes, we + // clear the cache. + return mServiceInfoCache.get(userId, service); + } + + ServiceInfo si; + try { + // createContextAsUser may potentially be expensive + // TODO: cache user context or improve ContextImpl implementation if this becomes + // a problem + si = mContext.createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO); + } catch (NameNotFoundException e) { + if (mService.areUsersStartedLocked(jobStatus)) { + // User is fully unlocked but PM still says the package doesn't exist. Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName()); - return null; } - mServiceInfoCache.add(userId, service, si); + // Write null to the cache so we don't keep querying PM. + si = null; } + mServiceInfoCache.add(userId, service, si); + return si; } + @GuardedBy("mLock") private boolean updateComponentEnabledStateLocked(JobStatus jobStatus) { - final ServiceInfo service = getServiceInfo(jobStatus); + final ServiceInfo service = getServiceInfoLocked(jobStatus); if (DEBUG && service == null) { Slog.v(TAG, jobStatus.toShortString() + " component not present"); @@ -141,20 +182,26 @@ public class ComponentController extends StateController { return !Objects.equals(ogService, service); } - private void updateComponentStateForPackage(final String pkg) { - synchronized (mLock) { - for (int u = mServiceInfoCache.numMaps() - 1; u >= 0; --u) { - final int userId = mServiceInfoCache.keyAt(u); - - for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) { - final ComponentName cn = mServiceInfoCache.keyAt(u, c); - if (cn.getPackageName().equals(pkg)) { - mServiceInfoCache.delete(userId, cn); - } - } + @GuardedBy("mLock") + private void clearComponentsForPackageLocked(final int userId, final String pkg) { + final int uIdx = mServiceInfoCache.indexOfKey(userId); + for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) { + final ComponentName cn = mServiceInfoCache.keyAt(uIdx, c); + if (cn.getPackageName().equals(pkg)) { + mServiceInfoCache.delete(userId, cn); } - updateComponentStatesLocked( - jobStatus -> jobStatus.getServiceComponent().getPackageName().equals(pkg)); + } + } + + private void updateComponentStateForPackage(final int userId, final String pkg) { + synchronized (mLock) { + clearComponentsForPackageLocked(userId, pkg); + updateComponentStatesLocked(jobStatus -> { + // Using user ID instead of source user ID because the service will run under the + // user ID, not source user ID. + return jobStatus.getUserId() == userId + && jobStatus.getServiceComponent().getPackageName().equals(pkg); + }); } } @@ -169,6 +216,7 @@ public class ComponentController extends StateController { } } + @GuardedBy("mLock") private void updateComponentStatesLocked(@NonNull Predicate<JobStatus> filter) { mComponentStateUpdateFunctor.reset(); mService.getJobStore().forEachJob(filter, mComponentStateUpdateFunctor); @@ -178,24 +226,40 @@ public class ComponentController extends StateController { } final class ComponentStateUpdateFunctor implements Consumer<JobStatus> { + @GuardedBy("mLock") boolean mChanged; @Override + @GuardedBy("mLock") public void accept(JobStatus jobStatus) { mChanged |= updateComponentEnabledStateLocked(jobStatus); } + @GuardedBy("mLock") private void reset() { mChanged = false; } } @Override + @GuardedBy("mLock") public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { - + for (int u = 0; u < mServiceInfoCache.numMaps(); ++u) { + final int userId = mServiceInfoCache.keyAt(u); + for (int p = 0; p < mServiceInfoCache.numElementsForKey(userId); ++p) { + final ComponentName componentName = mServiceInfoCache.keyAt(u, p); + pw.print(userId); + pw.print("-"); + pw.print(componentName); + pw.print(": "); + pw.print(mServiceInfoCache.valueAt(u, p)); + pw.println(); + } + } } @Override + @GuardedBy("mLock") public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) { diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt index bebf0190191b..b7d7ed866c89 100644 --- a/apex/media/framework/api/current.txt +++ b/apex/media/framework/api/current.txt @@ -10,7 +10,6 @@ package android.media { method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes(); method public boolean isFormatSpecified(@NonNull String); method public boolean isHdrTypeSupported(@NonNull String); - method public boolean isSlowMotionSupported(); method public boolean isVideoMimeTypeSupported(@NonNull String); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR; diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java index 3f30d3ea880e..97fa0eca0862 100644 --- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java +++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java @@ -242,7 +242,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { } }; - /* + /** * Query the video codec mime types supported by the application. * @return List of supported video codec mime types. The list will be empty if there are none. */ @@ -251,7 +251,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { return new ArrayList<>(mSupportedVideoMimeTypes); } - /* + /** * Query the video codec mime types that are not supported by the application. * @return List of unsupported video codec mime types. The list will be empty if there are none. */ @@ -260,7 +260,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { return new ArrayList<>(mUnsupportedVideoMimeTypes); } - /* + /** * Query all hdr types that are supported by the application. * @return List of supported hdr types. The list will be empty if there are none. */ @@ -269,7 +269,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { return new ArrayList<>(mSupportedHdrTypes); } - /* + /** * Query all hdr types that are not supported by the application. * @return List of unsupported hdr types. The list will be empty if there are none. */ @@ -278,7 +278,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { return new ArrayList<>(mUnsupportedHdrTypes); } - /* + /** * Whether handling of slow-motion video is supported * @hide */ diff --git a/core/api/current.txt b/core/api/current.txt index 3cd249423d11..04207145673f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -25218,7 +25218,6 @@ package android.media.session { method public float getPlaybackSpeed(); method public long getPosition(); method public int getState(); - method public boolean isActive(); method public void writeToParcel(android.os.Parcel, int); field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L field public static final long ACTION_PAUSE = 2L; // 0x2L diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 2be8c9aadcc6..b653410aeb8d 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -161,6 +161,10 @@ package android.media.session { method public void onVolumeChanged(@NonNull android.media.session.MediaSession.Token, int); } + public final class PlaybackState implements android.os.Parcelable { + method public boolean isActiveState(); + } + } package android.net { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index e956dee11526..5b978e5cdae2 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2321,7 +2321,7 @@ package android.content { field public static final String SYSTEM_CONFIG_SERVICE = "system_config"; field public static final String SYSTEM_UPDATE_SERVICE = "system_update"; field public static final String TETHERING_SERVICE = "tethering"; - field public static final String TRANSLATION_MANAGER_SERVICE = "transformer"; + field public static final String TRANSLATION_MANAGER_SERVICE = "translation"; field public static final String UI_TRANSLATION_SERVICE = "ui_translation"; field public static final String VR_SERVICE = "vrmanager"; field public static final String WIFI_NL80211_SERVICE = "wifinl80211"; @@ -7298,10 +7298,6 @@ package android.metrics { package android.net { - public class DnsResolverServiceManager { - method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public static android.os.IBinder getService(@NonNull android.content.Context); - } - public class EthernetManager { method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.TetheredInterfaceCallback); } @@ -7861,7 +7857,7 @@ package android.net.wifi.nl80211 { method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback); - method public boolean registerCountryCodeChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener); + method public boolean registerCountryCodeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener); method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback); method public void setOnServiceDeadCallback(@NonNull Runnable); method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback); @@ -7874,7 +7870,7 @@ package android.net.wifi.nl80211 { method public boolean tearDownClientInterface(@NonNull String); method public boolean tearDownInterfaces(); method public boolean tearDownSoftApInterface(@NonNull String); - method public void unregisterCountryCodeChangeListener(@NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener); + method public void unregisterCountryCodeChangedListener(@NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener); field public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR = "android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR"; field public static final int SCAN_TYPE_PNO_SCAN = 1; // 0x1 field public static final int SCAN_TYPE_SINGLE_SCAN = 0; // 0x0 @@ -7885,8 +7881,8 @@ package android.net.wifi.nl80211 { field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1 } - public static interface WifiNl80211Manager.CountryCodeChangeListener { - method public void onChanged(@NonNull String); + public static interface WifiNl80211Manager.CountryCodeChangedListener { + method public void onCountryCodeChanged(@NonNull String); } public static class WifiNl80211Manager.OemSecurityType { @@ -7944,16 +7940,16 @@ package android.nfc { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn(); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported(); - method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback); + method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean); method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int); - method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnStateCallback(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback); + method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener); field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1 } - public static interface NfcAdapter.ControllerAlwaysOnStateCallback { - method public void onStateChanged(boolean); + public static interface NfcAdapter.ControllerAlwaysOnListener { + method public void onControllerAlwaysOnChanged(boolean); } public static interface NfcAdapter.NfcUnlockHandler { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 0541934fd665..e449728ca0a8 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -161,6 +161,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void moveTaskToRootTask(int, int, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksInWindowingModes(@NonNull int[]); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksWithActivityTypes(@NonNull int[]); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean removeTask(int); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void resizeTask(int, android.graphics.Rect); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void startSystemLockTaskMode(int); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void stopSystemLockTaskMode(); @@ -1574,6 +1575,10 @@ package android.net { method public static void setServiceForTest(@Nullable android.os.IBinder); } + public class NetworkWatchlistManager { + method @Nullable public byte[] getWatchlistConfigHash(); + } + public class TrafficStats { method public static long getLoopbackRxBytes(); method public static long getLoopbackRxPackets(); @@ -2009,6 +2014,7 @@ package android.permission { public final class PermissionManager { method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(); + method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean); method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource); } diff --git a/core/java/android/annotation/RequiresPermission.java b/core/java/android/annotation/RequiresPermission.java index 1d89e31b2b99..303ab41bec8e 100644 --- a/core/java/android/annotation/RequiresPermission.java +++ b/core/java/android/annotation/RequiresPermission.java @@ -20,7 +20,7 @@ import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.SOURCE; +import static java.lang.annotation.RetentionPolicy.CLASS; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -76,7 +76,7 @@ import java.lang.annotation.Target; * * @hide */ -@Retention(SOURCE) +@Retention(CLASS) @Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER}) public @interface RequiresPermission { /** diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 627017ce0893..28d6fbb36a47 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -469,7 +469,8 @@ public class ActivityTaskManager { } } - /** @hide */ + /** Removes task by a given taskId */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean removeTask(int taskId) { try { return getService().removeTask(taskId); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index df9530fee68a..1cb46b1b6350 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -6475,7 +6475,7 @@ public class AppOpsManager { historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++)); } } - mDiscreteAccesses = historicalDiscreteAccesses; + mDiscreteAccesses = deduplicateDiscreteEvents(historicalDiscreteAccesses); } private void increaseAccessCount(@UidState int uidState, @OpFlags int flags, @@ -6996,7 +6996,7 @@ public class AppOpsManager { } result.add(entry); } - return result; + return deduplicateDiscreteEvents(result); } /** @@ -9819,4 +9819,52 @@ public class AppOpsManager { } } } + + private static List<AttributedOpEntry> deduplicateDiscreteEvents(List<AttributedOpEntry> list) { + int n = list.size(); + int i = 0; + for (int j = 0, k = 0; j < n; i++, j = k) { + long currentAccessTime = list.get(j).getLastAccessTime(OP_FLAGS_ALL); + k = j + 1; + while(k < n && list.get(k).getLastAccessTime(OP_FLAGS_ALL) == currentAccessTime) { + k++; + } + list.set(i, mergeAttributedOpEntries(list.subList(j, k))); + } + for (; i < n; i++) { + list.remove(list.size() - 1); + } + return list; + } + + private static AttributedOpEntry mergeAttributedOpEntries(List<AttributedOpEntry> opEntries) { + if (opEntries.size() == 1) { + return opEntries.get(0); + } + LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = new LongSparseArray<>(); + LongSparseArray<AppOpsManager.NoteOpEvent> rejectEvents = new LongSparseArray<>(); + int opCount = opEntries.size(); + for (int i = 0; i < opCount; i++) { + AttributedOpEntry a = opEntries.get(i); + ArraySet<Long> keys = a.collectKeys(); + final int keyCount = keys.size(); + for (int k = 0; k < keyCount; k++) { + final long key = keys.valueAt(k); + + final int uidState = extractUidStateFromKey(key); + final int flags = extractFlagsFromKey(key); + + NoteOpEvent access = a.getLastAccessEvent(uidState, uidState, flags); + NoteOpEvent reject = a.getLastRejectEvent(uidState, uidState, flags); + + if (access != null) { + accessEvents.append(key, access); + } + if (reject != null) { + rejectEvents.append(key, reject); + } + } + } + return new AttributedOpEntry(opEntries.get(0).mOp, false, accessEvents, rejectEvents); + } } diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index dfc105a2811a..8574678a5a02 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -614,7 +614,7 @@ public final class ApplicationExitInfo implements Parcelable { * tombstone traces will be returned for * {@link #REASON_CRASH_NATIVE}, with an InputStream containing a protobuf with * <a href="https://android.googlesource.com/platform/system/core/+/refs/heads/master/debuggerd/proto/tombstone.proto">this schema</a>. - * Note thatbecause these traces are kept in a separate global circular buffer, crashes may be + * Note that because these traces are kept in a separate global circular buffer, crashes may be * overwritten by newer crashes (including from other applications), so this may still return * null. * diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index e83557c18f8f..4f7c6841d6bb 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -190,4 +190,18 @@ interface IWallpaperManager { * Called from SystemUI when it shows the AoD UI. */ oneway void setInAmbientMode(boolean inAmbientMode, long animationDuration); + + /** + * Called from SystemUI when the device is waking up. + * + * @hide + */ + oneway void notifyWakingUp(int x, int y, in Bundle extras); + + /** + * Called from SystemUI when the device is going to sleep. + * + * @hide + */ + void notifyGoingToSleep(int x, int y, in Bundle extras); } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 4cf3a8059b3e..ca0868310dee 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -53,7 +53,6 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.AndroidException; import android.util.ArraySet; -import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.internal.os.IResultReceiver; @@ -371,19 +370,9 @@ public final class PendingIntent implements Parcelable { "Cannot set both FLAG_IMMUTABLE and FLAG_MUTABLE for PendingIntent"); } - // TODO(b/178092897) Remove the below instrumentation check and enforce - // the explicit mutability requirement for apps under instrumentation. - ActivityThread thread = ActivityThread.currentActivityThread(); - Instrumentation mInstrumentation = thread.getInstrumentation(); - if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED) && !flagImmutableSet && !flagMutableSet) { - - if (mInstrumentation.isInstrumenting()) { - Log.e(TAG, msg); - } else { throw new IllegalArgumentException(msg); - } } } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 6a71c92fbc37..8d332ab1d58b 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -190,6 +190,30 @@ public class WallpaperManager { public static final String COMMAND_DROP = "android.home.drop"; /** + * Command for {@link #sendWallpaperCommand}: reported by System UI when the device is waking + * up. The x and y arguments are a location (possibly very roughly) corresponding to the action + * that caused the device to wake up. For example, if the power button was pressed, this will be + * the location on the screen nearest the power button. + * + * If the location is unknown or not applicable, x and y will be -1. + * + * @hide + */ + public static final String COMMAND_WAKING_UP = "android.wallpaper.wakingup"; + + /** + * Command for {@link #sendWallpaperCommand}: reported by System UI when the device is going to + * sleep. The x and y arguments are a location (possibly very roughly) corresponding to the + * action that caused the device to go to sleep. For example, if the power button was pressed, + * this will be the location on the screen nearest the power button. + * + * If the location is unknown or not applicable, x and y will be -1. + * + * @hide + */ + public static final String COMMAND_GOING_TO_SLEEP = "android.wallpaper.goingtosleep"; + + /** * Command for {@link #sendWallpaperCommand}: reported when the wallpaper that was already * set is re-applied by the user. * @hide diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java index e93138bd1bc8..759597ce36dc 100644 --- a/core/java/android/app/admin/PasswordMetrics.java +++ b/core/java/android/app/admin/PasswordMetrics.java @@ -28,6 +28,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PASSWORD_SIZE; import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS; import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE; @@ -74,11 +75,8 @@ public final class PasswordMetrics implements Parcelable { // consider it a complex PIN/password. public static final int MAX_ALLOWED_SEQUENCE = 3; - // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN or CREDENTIAL_TYPE_PASSWORD. - // Note that this class still uses CREDENTIAL_TYPE_PASSWORD to represent both numeric PIN - // and alphabetic password. This is OK as long as this definition is only used internally, - // and the value never gets mixed up with credential types from other parts of the framework. - // TODO: fix this (ideally after we move logic to PasswordPolicy) + // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN, CREDENTIAL_TYPE_PIN or + // CREDENTIAL_TYPE_PASSWORD. public @CredentialType int credType; // Fields below only make sense when credType is PASSWORD. public int length = 0; @@ -192,13 +190,15 @@ public final class PasswordMetrics implements Parcelable { /** * Returns the {@code PasswordMetrics} for a given credential. * - * If the credential is a pin or a password, equivalent to {@link #computeForPassword(byte[])}. - * {@code credential} cannot be null when {@code type} is + * If the credential is a pin or a password, equivalent to + * {@link #computeForPasswordOrPin(byte[], boolean)}. {@code credential} cannot be null + * when {@code type} is * {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}. */ public static PasswordMetrics computeForCredential(LockscreenCredential credential) { if (credential.isPassword() || credential.isPin()) { - return PasswordMetrics.computeForPassword(credential.getCredential()); + return PasswordMetrics.computeForPasswordOrPin(credential.getCredential(), + credential.isPin()); } else if (credential.isPattern()) { return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN); } else if (credential.isNone()) { @@ -209,9 +209,9 @@ public final class PasswordMetrics implements Parcelable { } /** - * Returns the {@code PasswordMetrics} for a given password + * Returns the {@code PasswordMetrics} for a given password or pin */ - public static PasswordMetrics computeForPassword(@NonNull byte[] password) { + public static PasswordMetrics computeForPasswordOrPin(byte[] password, boolean isPin) { // Analyse the characters used int letters = 0; int upperCase = 0; @@ -245,8 +245,9 @@ public final class PasswordMetrics implements Parcelable { } } + final int credType = isPin ? CREDENTIAL_TYPE_PIN : CREDENTIAL_TYPE_PASSWORD; final int seqLength = maxLengthSequence(password); - return new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD, length, letters, upperCase, lowerCase, + return new PasswordMetrics(credType, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter, nonNumeric, seqLength); } @@ -353,7 +354,7 @@ public final class PasswordMetrics implements Parcelable { */ public void maxWith(PasswordMetrics other) { credType = Math.max(credType, other.credType); - if (credType != CREDENTIAL_TYPE_PASSWORD) { + if (credType != CREDENTIAL_TYPE_PASSWORD && credType != CREDENTIAL_TYPE_PIN) { return; } length = Math.max(length, other.length); @@ -408,7 +409,7 @@ public final class PasswordMetrics implements Parcelable { @Override boolean allowsCredType(int credType) { - return credType == CREDENTIAL_TYPE_PASSWORD; + return credType == CREDENTIAL_TYPE_PASSWORD || credType == CREDENTIAL_TYPE_PIN; } }, BUCKET_MEDIUM(PASSWORD_COMPLEXITY_MEDIUM) { @@ -424,7 +425,7 @@ public final class PasswordMetrics implements Parcelable { @Override boolean allowsCredType(int credType) { - return credType == CREDENTIAL_TYPE_PASSWORD; + return credType == CREDENTIAL_TYPE_PASSWORD || credType == CREDENTIAL_TYPE_PIN; } }, BUCKET_LOW(PASSWORD_COMPLEXITY_LOW) { @@ -489,7 +490,7 @@ public final class PasswordMetrics implements Parcelable { if (!bucket.allowsCredType(credType)) { return false; } - if (credType != CREDENTIAL_TYPE_PASSWORD) { + if (credType != CREDENTIAL_TYPE_PASSWORD && credType != CREDENTIAL_TYPE_PIN) { return true; } return (bucket.canHaveSequence() || seqLength <= MAX_ALLOWED_SEQUENCE) @@ -529,7 +530,7 @@ public final class PasswordMetrics implements Parcelable { new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0)); } - final PasswordMetrics enteredMetrics = computeForPassword(password); + final PasswordMetrics enteredMetrics = computeForPasswordOrPin(password, isPin); return validatePasswordMetrics(adminMetrics, minComplexity, isPin, enteredMetrics); } @@ -555,8 +556,8 @@ public final class PasswordMetrics implements Parcelable { || !bucket.allowsCredType(actualMetrics.credType)) { return Collections.singletonList(new PasswordValidationError(WEAK_CREDENTIAL_TYPE, 0)); } - // TODO: this needs to be modified if CREDENTIAL_TYPE_PIN is added. - if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD) { + if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD + && actualMetrics.credType != CREDENTIAL_TYPE_PIN) { return Collections.emptyList(); // Nothing to check for pattern or none. } diff --git a/core/java/android/app/admin/PasswordPolicy.java b/core/java/android/app/admin/PasswordPolicy.java index 13f11ad74d12..0544a3666696 100644 --- a/core/java/android/app/admin/PasswordPolicy.java +++ b/core/java/android/app/admin/PasswordPolicy.java @@ -20,6 +20,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; @@ -27,6 +28,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; /** * {@hide} @@ -58,14 +60,20 @@ public class PasswordPolicy { } else if (quality == PASSWORD_QUALITY_BIOMETRIC_WEAK || quality == PASSWORD_QUALITY_SOMETHING) { return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN); - } // quality is NUMERIC or stronger. + } else if (quality == PASSWORD_QUALITY_NUMERIC + || quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) { + PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PIN); + result.length = length; + if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) { + result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE; + } + return result; + } // quality is ALPHABETIC or stronger. PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); result.length = length; - if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) { - result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE; - } else if (quality == PASSWORD_QUALITY_ALPHABETIC) { + if (quality == PASSWORD_QUALITY_ALPHABETIC) { result.nonNumeric = 1; } else if (quality == PASSWORD_QUALITY_ALPHANUMERIC) { result.numeric = 1; diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index a72877e27943..fe81df0b6b3b 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -320,6 +320,15 @@ public class AppWidgetHost { } /** + * Set the host's interaction handler. + * + * @hide + */ + public void setInteractionHandler(InteractionHandler interactionHandler) { + mInteractionHandler = interactionHandler; + } + + /** * Gets a list of all the appWidgetIds that are bound to the current host */ public int[] getAppWidgetIds() { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 318913f3bec0..a88c9edd3017 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4705,10 +4705,9 @@ public abstract class Context { * @hide * @see #getSystemService(String) */ - // TODO(b/176208267): change it back to translation before S release. @SystemApi @SuppressLint("ServiceName") - public static final String TRANSLATION_MANAGER_SERVICE = "transformer"; + public static final String TRANSLATION_MANAGER_SERVICE = "translation"; /** * Official published name of the translation service which supports ui translation function. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 5ff11240db72..86a8a9d69782 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -8923,6 +8923,8 @@ public class PackageParser { private final @ParseFlags int mFlags; private AssetManager mCachedAssetManager; + private ApkAssets mBaseApkAssets; + DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) { mBaseCodePath = pkg.baseCodePath; mSplitCodePaths = pkg.splitCodePaths; @@ -8953,9 +8955,11 @@ public class PackageParser { ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null ? mSplitCodePaths.length : 0) + 1]; + mBaseApkAssets = loadApkAssets(mBaseCodePath, mFlags); + // Load the base. int splitIdx = 0; - apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags); + apkAssets[splitIdx++] = mBaseApkAssets; // Load any splits. if (!ArrayUtils.isEmpty(mSplitCodePaths)) { @@ -8982,6 +8986,11 @@ public class PackageParser { public void close() throws Exception { IoUtils.closeQuietly(mCachedAssetManager); } + + @Override + public ApkAssets getBaseApkAssets() { + return mBaseApkAssets; + } } /** @@ -9085,5 +9094,10 @@ public class PackageParser { IoUtils.closeQuietly(assets); } } + + @Override + public ApkAssets getBaseApkAssets() { + return mCachedSplitApks[0][0]; + } } } diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java index 98a20f73a120..52ee4de5bed6 100644 --- a/core/java/android/content/pm/PackagePartitions.java +++ b/core/java/android/content/pm/PackagePartitions.java @@ -47,7 +47,7 @@ public class PackagePartitions { public static final int PARTITION_PRODUCT = 4; public static final int PARTITION_SYSTEM_EXT = 5; - @IntDef(flag = true, prefix = { "PARTITION_" }, value = { + @IntDef(prefix = { "PARTITION_" }, value = { PARTITION_SYSTEM, PARTITION_VENDOR, PARTITION_ODM, diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index a1ffc0ca5378..0fc6b2bd4f2e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -383,10 +383,9 @@ public class ParsingPackageUtils { } try { - final AssetManager assets = assetLoader.getBaseAssetManager(); final File baseApk = new File(lite.getBaseApkPath()); final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk, - lite.getPath(), assets, flags); + lite.getPath(), assetLoader, flags); if (result.isError()) { return input.error(result); } @@ -442,7 +441,7 @@ public class ParsingPackageUtils { final ParseResult<ParsingPackage> result = parseBaseApk(input, apkFile, apkFile.getCanonicalPath(), - assetLoader.getBaseAssetManager(), flags); + assetLoader, flags); if (result.isError()) { return input.error(result); } @@ -458,7 +457,8 @@ public class ParsingPackageUtils { } private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile, - String codePath, AssetManager assets, int flags) { + String codePath, SplitAssetLoader assetLoader, int flags) + throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); String volumeUuid = null; @@ -469,6 +469,7 @@ public class ParsingPackageUtils { if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath); + final AssetManager assets = assetLoader.getBaseAssetManager(); final int cookie = assets.findCookieForPath(apkPath); if (cookie == 0) { return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, @@ -500,12 +501,19 @@ public class ParsingPackageUtils { } } - ApkAssets apkAssets = assets.getApkAssets()[0]; - if (apkAssets.definesOverlayable()) { + ApkAssets apkAssets = assetLoader.getBaseApkAssets(); + boolean definesOverlayable = false; + try { + definesOverlayable = apkAssets.definesOverlayable(); + } catch (IOException ignored) { + // Will fail if there's no packages in the ApkAssets, which can be treated as false + } + + if (definesOverlayable) { SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers(); int size = packageNames.size(); for (int index = 0; index < size; index++) { - String packageName = packageNames.get(index); + String packageName = packageNames.valueAt(index); Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName); if (overlayableToActor != null && !overlayableToActor.isEmpty()) { for (String overlayable : overlayableToActor.keySet()) { @@ -2799,7 +2807,15 @@ public class ParsingPackageUtils { } } + @SuppressWarnings("AndroidFrameworkCompatChange") private void convertSplitPermissions(ParsingPackage pkg) { + // STOPSHIP(b/183905675): REMOVE THIS TERRIBLE, HORRIBLE, NO GOOD, VERY BAD HACK + if ("com.android.chrome".equals(pkg.getPackageName()) + && pkg.getVersionCode() <= 445500399 + && pkg.getTargetSdkVersion() > Build.VERSION_CODES.R) { + pkg.setTargetSdkVersion(Build.VERSION_CODES.R); + } + final int listSize = mSplitPermissionInfos.size(); for (int is = 0; is < listSize; is++) { final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is); diff --git a/core/java/android/content/pm/parsing/component/ParsedAttribution.java b/core/java/android/content/pm/parsing/component/ParsedAttribution.java index 4ec2e73a0b83..3a4aae19d459 100644 --- a/core/java/android/content/pm/parsing/component/ParsedAttribution.java +++ b/core/java/android/content/pm/parsing/component/ParsedAttribution.java @@ -40,7 +40,7 @@ public class ParsedAttribution implements Parcelable { public static final int MAX_ATTRIBUTION_TAG_LEN = 50; /** Maximum amount of attributions per package */ - private static final int MAX_NUM_ATTRIBUTIONS = 10000; + private static final int MAX_NUM_ATTRIBUTIONS = 1000; /** Tag of the attribution */ public final @NonNull String tag; @@ -100,7 +100,7 @@ public class ParsedAttribution implements Parcelable { - // Code below generated by codegen v1.0.23. + // Code below generated by codegen v1.0.22. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -215,8 +215,8 @@ public class ParsedAttribution implements Parcelable { }; @DataClass.Generated( - time = 1618351459610L, - codegenVersion = "1.0.23", + time = 1607463855175L, + codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttribution.java", inputSignatures = "public static final int MAX_ATTRIBUTION_TAG_LEN\nprivate static final int MAX_NUM_ATTRIBUTIONS\npublic final @android.annotation.NonNull java.lang.String tag\npublic final @android.annotation.StringRes int label\npublic final @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\npublic static boolean isCombinationValid(java.util.List<android.content.pm.parsing.component.ParsedAttribution>)\nclass ParsedAttribution extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false)") @Deprecated diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java index f3caf603921f..c1a83967dea1 100644 --- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java +++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java @@ -43,6 +43,8 @@ public class DefaultSplitAssetLoader implements SplitAssetLoader { private final @ParseFlags int mFlags; private AssetManager mCachedAssetManager; + private ApkAssets mBaseApkAssets; + public DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) { mBaseApkPath = pkg.getBaseApkPath(); mSplitApkPaths = pkg.getSplitApkPaths(); @@ -76,7 +78,7 @@ public class DefaultSplitAssetLoader implements SplitAssetLoader { // Load the base. int splitIdx = 0; - apkAssets[splitIdx++] = loadApkAssets(mBaseApkPath, mFlags); + apkAssets[splitIdx++] = mBaseApkAssets = loadApkAssets(mBaseApkPath, mFlags); // Load any splits. if (!ArrayUtils.isEmpty(mSplitApkPaths)) { @@ -100,6 +102,11 @@ public class DefaultSplitAssetLoader implements SplitAssetLoader { } @Override + public ApkAssets getBaseApkAssets() { + return mBaseApkAssets; + } + + @Override public void close() throws Exception { IoUtils.closeQuietly(mCachedAssetManager); } diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java index 523ca405eec7..e5c2158fcd38 100644 --- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java +++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java @@ -128,6 +128,11 @@ public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackagePar } @Override + public ApkAssets getBaseApkAssets() { + return mCachedSplitApks[0][0]; + } + + @Override public void close() throws Exception { for (AssetManager assets : mCachedAssetManagers) { IoUtils.closeQuietly(assets); diff --git a/core/java/android/content/pm/split/SplitAssetLoader.java b/core/java/android/content/pm/split/SplitAssetLoader.java index 108fb95a150d..7584e15fb6d8 100644 --- a/core/java/android/content/pm/split/SplitAssetLoader.java +++ b/core/java/android/content/pm/split/SplitAssetLoader.java @@ -16,6 +16,7 @@ package android.content.pm.split; import android.content.pm.PackageParser; +import android.content.res.ApkAssets; import android.content.res.AssetManager; /** @@ -27,4 +28,6 @@ import android.content.res.AssetManager; public interface SplitAssetLoader extends AutoCloseable { AssetManager getBaseAssetManager() throws PackageParser.PackageParserException; AssetManager getSplitAssetManager(int splitIdx) throws PackageParser.PackageParserException; + + ApkAssets getBaseApkAssets(); } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 4ee5383a56be..f03da7cd390b 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -718,7 +718,7 @@ public class InputMethodService extends AbstractInputMethodService { public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, @NonNull IBinder startInputToken) { - mPrivOps.reportStartInput(startInputToken); + mPrivOps.reportStartInputAsync(startInputToken); if (restarting) { restartInput(inputConnection, editorInfo); diff --git a/core/java/android/net/DnsResolverServiceManager.java b/core/java/android/net/DnsResolverServiceManager.java deleted file mode 100644 index 15973224f10b..000000000000 --- a/core/java/android/net/DnsResolverServiceManager.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.net; - -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SystemApi; -import android.content.Context; -import android.os.IBinder; -import android.os.ServiceManager; - -import java.util.Objects; - -/** - * Provides a way to obtain the DnsResolver binder objects. - * - * @hide - */ -@SystemApi -public class DnsResolverServiceManager { - /** - * Name to retrieve a {@link android.net.IDnsResolver} IBinder. - */ - private static final String DNS_RESOLVER_SERVICE = "dnsresolver"; - - private DnsResolverServiceManager() {} - - /** - * Get an {@link IBinder} representing the DnsResolver stable AIDL interface - * - * @param context the context for permission check. - * @return {@link android.net.IDnsResolver} IBinder. - */ - @NonNull - @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) - public static IBinder getService(@NonNull final Context context) { - Objects.requireNonNull(context); - context.enforceCallingOrSelfPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - "DnsResolverServiceManager"); - try { - return ServiceManager.getServiceOrThrow(DNS_RESOLVER_SERVICE); - } catch (ServiceManager.ServiceNotFoundException e) { - // Catch ServiceManager#ServiceNotFoundException and rethrow IllegalStateException - // because ServiceManager#ServiceNotFoundException is @hide so that it can't be listed - // on the system api. Thus, rethrow IllegalStateException if dns resolver service cannot - // be found. - throw new IllegalStateException("Cannot find dns resolver service."); - } - } -} diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java index 8f6510ed3ea5..da01dcb83de4 100644 --- a/core/java/android/net/NetworkWatchlistManager.java +++ b/core/java/android/net/NetworkWatchlistManager.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; @@ -31,6 +32,7 @@ import com.android.internal.util.Preconditions; * Class that manage network watchlist in system. * @hide */ +@TestApi @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.NETWORK_WATCHLIST_SERVICE) public class NetworkWatchlistManager { diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index d5cc01aac0cc..cb9a3e43db81 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -24,7 +24,7 @@ import android.nfc.Tag; import android.nfc.TechListParcel; import android.nfc.IAppCallback; import android.nfc.INfcAdapterExtras; -import android.nfc.INfcControllerAlwaysOnStateCallback; +import android.nfc.INfcControllerAlwaysOnListener; import android.nfc.INfcTag; import android.nfc.INfcCardEmulation; import android.nfc.INfcFCardEmulation; @@ -76,6 +76,6 @@ interface INfcAdapter boolean setControllerAlwaysOn(boolean value); boolean isControllerAlwaysOn(); boolean isControllerAlwaysOnSupported(); - void registerControllerAlwaysOnStateCallback(in INfcControllerAlwaysOnStateCallback callback); - void unregisterControllerAlwaysOnStateCallback(in INfcControllerAlwaysOnStateCallback callback); + void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener); + void unregisterControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener); } diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl index 1e4fdd79e831..1bb7680d2fed 100644 --- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl +++ b/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl @@ -19,11 +19,11 @@ package android.nfc; /** * @hide */ -oneway interface INfcControllerAlwaysOnStateCallback { +oneway interface INfcControllerAlwaysOnListener { /** * Called whenever the controller always on state changes * * @param isEnabled true if the state is enabled, false otherwise */ - void onControllerAlwaysOnStateChanged(boolean isEnabled); + void onControllerAlwaysOnChanged(boolean isEnabled); } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index bbf802ca58d8..64c121194932 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -67,7 +67,7 @@ import java.util.concurrent.Executor; public final class NfcAdapter { static final String TAG = "NFC"; - private final NfcControllerAlwaysOnStateListener mControllerAlwaysOnStateListener; + private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener; /** * Intent to start an activity when a tag with NDEF payload is discovered. @@ -418,19 +418,19 @@ public final class NfcAdapter { } /** - * A callback to be invoked when NFC controller always on state changes. - * <p>Register your {@code ControllerAlwaysOnStateCallback} implementation with {@link - * NfcAdapter#registerControllerAlwaysOnStateCallback} and disable it with {@link - * NfcAdapter#unregisterControllerAlwaysOnStateCallback}. - * @see #registerControllerAlwaysOnStateCallback + * A listener to be invoked when NFC controller always on state changes. + * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link + * NfcAdapter#registerControllerAlwaysOnListener} and disable it with {@link + * NfcAdapter#unregisterControllerAlwaysOnListener}. + * @see #registerControllerAlwaysOnListener * @hide */ @SystemApi - public interface ControllerAlwaysOnStateCallback { + public interface ControllerAlwaysOnListener { /** * Called on NFC controller always on state changes */ - void onStateChanged(boolean isEnabled); + void onControllerAlwaysOnChanged(boolean isEnabled); } /** @@ -748,7 +748,7 @@ public final class NfcAdapter { mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>(); mTagRemovedListener = null; mLock = new Object(); - mControllerAlwaysOnStateListener = new NfcControllerAlwaysOnStateListener(getService()); + mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService()); } /** @@ -2246,12 +2246,12 @@ public final class NfcAdapter { * <p>This API is for the NFCC internal state management. It allows to discriminate * the controller function from the NFC function by keeping the NFC controller on without * any NFC RF enabled if necessary. - * <p>This call is asynchronous. Register a callback {@link #ControllerAlwaysOnStateCallback} - * by {@link #registerControllerAlwaysOnStateCallback} to find out when the operation is + * <p>This call is asynchronous. Register a listener {@link #ControllerAlwaysOnListener} + * by {@link #registerControllerAlwaysOnListener} to find out when the operation is * complete. * <p>If this returns true, then either NFCC always on state has been set based on the value, - * or a {@link ControllerAlwaysOnStateCallback#onStateChanged(boolean)} will be invoked to - * indicate the state change. + * or a {@link ControllerAlwaysOnListener#onControllerAlwaysOnChanged(boolean)} will be invoked + * to indicate the state change. * If this returns false, then there is some problem that prevents an attempt to turn NFCC * always on. * @param value if true the NFCC will be kept on (with no RF enabled if NFC adapter is @@ -2344,37 +2344,37 @@ public final class NfcAdapter { } /** - * Register a {@link ControllerAlwaysOnStateCallback} to listen for NFC controller always on + * Register a {@link ControllerAlwaysOnListener} to listen for NFC controller always on * state changes - * <p>The provided callback will be invoked by the given {@link Executor}. + * <p>The provided listener will be invoked by the given {@link Executor}. * - * @param executor an {@link Executor} to execute given callback - * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback} + * @param executor an {@link Executor} to execute given listener + * @param listener user implementation of the {@link ControllerAlwaysOnListener} * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) - public void registerControllerAlwaysOnStateCallback( + public void registerControllerAlwaysOnListener( @NonNull @CallbackExecutor Executor executor, - @NonNull ControllerAlwaysOnStateCallback callback) { - mControllerAlwaysOnStateListener.register(executor, callback); + @NonNull ControllerAlwaysOnListener listener) { + mControllerAlwaysOnListener.register(executor, listener); } /** - * Unregister the specified {@link ControllerAlwaysOnStateCallback} - * <p>The same {@link ControllerAlwaysOnStateCallback} object used when calling - * {@link #registerControllerAlwaysOnStateCallback(Executor, ControllerAlwaysOnStateCallback)} + * Unregister the specified {@link ControllerAlwaysOnListener} + * <p>The same {@link ControllerAlwaysOnListener} object used when calling + * {@link #registerControllerAlwaysOnListener(Executor, ControllerAlwaysOnListener)} * must be used. * - * <p>Callbacks are automatically unregistered when application process goes away + * <p>Listeners are automatically unregistered when application process goes away * - * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback} + * @param listener user implementation of the {@link ControllerAlwaysOnListener} * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) - public void unregisterControllerAlwaysOnStateCallback( - @NonNull ControllerAlwaysOnStateCallback callback) { - mControllerAlwaysOnStateListener.unregister(callback); + public void unregisterControllerAlwaysOnListener( + @NonNull ControllerAlwaysOnListener listener) { + mControllerAlwaysOnListener.unregister(listener); } } diff --git a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java b/core/java/android/nfc/NfcControllerAlwaysOnListener.java index 69a9ec79edb2..96707bb432db 100644 --- a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java +++ b/core/java/android/nfc/NfcControllerAlwaysOnListener.java @@ -17,7 +17,7 @@ package android.nfc; import android.annotation.NonNull; -import android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback; +import android.nfc.NfcAdapter.ControllerAlwaysOnListener; import android.os.Binder; import android.os.RemoteException; import android.util.Log; @@ -29,77 +29,77 @@ import java.util.concurrent.Executor; /** * @hide */ -public class NfcControllerAlwaysOnStateListener extends INfcControllerAlwaysOnStateCallback.Stub { - private static final String TAG = "NfcControllerAlwaysOnStateListener"; +public class NfcControllerAlwaysOnListener extends INfcControllerAlwaysOnListener.Stub { + private static final String TAG = NfcControllerAlwaysOnListener.class.getSimpleName(); private final INfcAdapter mAdapter; - private final Map<ControllerAlwaysOnStateCallback, Executor> mCallbackMap = new HashMap<>(); + private final Map<ControllerAlwaysOnListener, Executor> mListenerMap = new HashMap<>(); private boolean mCurrentState = false; private boolean mIsRegistered = false; - public NfcControllerAlwaysOnStateListener(@NonNull INfcAdapter adapter) { + public NfcControllerAlwaysOnListener(@NonNull INfcAdapter adapter) { mAdapter = adapter; } /** - * Register a {@link ControllerAlwaysOnStateCallback} with this - * {@link NfcControllerAlwaysOnStateListener} + * Register a {@link ControllerAlwaysOnListener} with this + * {@link NfcControllerAlwaysOnListener} * - * @param executor an {@link Executor} to execute given callback - * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback} + * @param executor an {@link Executor} to execute given listener + * @param listener user implementation of the {@link ControllerAlwaysOnListener} */ public void register(@NonNull Executor executor, - @NonNull ControllerAlwaysOnStateCallback callback) { + @NonNull ControllerAlwaysOnListener listener) { synchronized (this) { - if (mCallbackMap.containsKey(callback)) { + if (mListenerMap.containsKey(listener)) { return; } - mCallbackMap.put(callback, executor); + mListenerMap.put(listener, executor); if (!mIsRegistered) { try { - mAdapter.registerControllerAlwaysOnStateCallback(this); + mAdapter.registerControllerAlwaysOnListener(this); mIsRegistered = true; } catch (RemoteException e) { - Log.w(TAG, "Failed to register ControllerAlwaysOnStateListener"); + Log.w(TAG, "Failed to register"); } } } } /** - * Unregister the specified {@link ControllerAlwaysOnStateCallback} + * Unregister the specified {@link ControllerAlwaysOnListener} * - * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback} + * @param listener user implementation of the {@link ControllerAlwaysOnListener} */ - public void unregister(@NonNull ControllerAlwaysOnStateCallback callback) { + public void unregister(@NonNull ControllerAlwaysOnListener listener) { synchronized (this) { - if (!mCallbackMap.containsKey(callback)) { + if (!mListenerMap.containsKey(listener)) { return; } - mCallbackMap.remove(callback); + mListenerMap.remove(listener); - if (mCallbackMap.isEmpty() && mIsRegistered) { + if (mListenerMap.isEmpty() && mIsRegistered) { try { - mAdapter.unregisterControllerAlwaysOnStateCallback(this); + mAdapter.unregisterControllerAlwaysOnListener(this); } catch (RemoteException e) { - Log.w(TAG, "Failed to unregister ControllerAlwaysOnStateListener"); + Log.w(TAG, "Failed to unregister"); } mIsRegistered = false; } } } - private void sendCurrentState(@NonNull ControllerAlwaysOnStateCallback callback) { + private void sendCurrentState(@NonNull ControllerAlwaysOnListener listener) { synchronized (this) { - Executor executor = mCallbackMap.get(callback); + Executor executor = mListenerMap.get(listener); final long identity = Binder.clearCallingIdentity(); try { - executor.execute(() -> callback.onStateChanged( + executor.execute(() -> listener.onControllerAlwaysOnChanged( mCurrentState)); } finally { Binder.restoreCallingIdentity(identity); @@ -108,10 +108,10 @@ public class NfcControllerAlwaysOnStateListener extends INfcControllerAlwaysOnSt } @Override - public void onControllerAlwaysOnStateChanged(boolean isEnabled) { + public void onControllerAlwaysOnChanged(boolean isEnabled) { synchronized (this) { mCurrentState = isEnabled; - for (ControllerAlwaysOnStateCallback cb : mCallbackMap.keySet()) { + for (ControllerAlwaysOnListener cb : mListenerMap.keySet()) { sendCurrentState(cb); } } diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING new file mode 100644 index 000000000000..71ad687b7889 --- /dev/null +++ b/core/java/android/nfc/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "NfcManagerTests" + } + ] +} diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 16d041ac60f2..d026e959905c 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -34,6 +34,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * Java proxy for a native IBinder object. @@ -262,27 +265,45 @@ public final class BinderProxy implements IBinder { Log.e(Binder.TAG, "RemoteException while disabling app freezer"); } - for (WeakReference<BinderProxy> weakRef : proxiesToQuery) { - BinderProxy bp = weakRef.get(); - String key; - if (bp == null) { - key = "<cleared weak-ref>"; - } else { - try { - key = bp.getInterfaceDescriptor(); - if ((key == null || key.isEmpty()) && !bp.isBinderAlive()) { - key = "<proxy to dead node>"; + // We run the dump on a separate thread, because there are known cases where + // a process overrides getInterfaceDescriptor() and somehow blocks on it, causing + // the calling thread (usually AMS) to hit the watchdog. + // Do the dumping on a separate thread instead, and give up after a while. + ExecutorService executorService = Executors.newSingleThreadExecutor(); + executorService.submit(() -> { + for (WeakReference<BinderProxy> weakRef : proxiesToQuery) { + BinderProxy bp = weakRef.get(); + String key; + if (bp == null) { + key = "<cleared weak-ref>"; + } else { + try { + key = bp.getInterfaceDescriptor(); + if ((key == null || key.isEmpty()) && !bp.isBinderAlive()) { + key = "<proxy to dead node>"; + } + } catch (Throwable t) { + key = "<exception during getDescriptor>"; } - } catch (Throwable t) { - key = "<exception during getDescriptor>"; + } + Integer i = counts.get(key); + if (i == null) { + counts.put(key, 1); + } else { + counts.put(key, i + 1); } } - Integer i = counts.get(key); - if (i == null) { - counts.put(key, 1); - } else { - counts.put(key, i + 1); + }); + + try { + executorService.shutdown(); + boolean dumpDone = executorService.awaitTermination(20, TimeUnit.SECONDS); + if (!dumpDone) { + Log.e(Binder.TAG, "Failed to complete binder proxy dump," + + " dumping what we have so far."); } + } catch (InterruptedException e) { + // Ignore } try { ActivityManager.getService().enableAppFreezer(true); diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 1a40f0640a8d..17c90d64ce6a 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -886,6 +886,24 @@ public final class PermissionManager { } /** + * @param micMuted whether to consider the microphone muted when retrieving audio ops + * @return A list of permission groups currently or recently used by all apps by all users in + * the current profile group. + * + * @hide + */ + @TestApi + @NonNull + @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS) + public List<PermGroupUsage> getIndicatorAppOpUsageData(boolean micMuted) { + // Lazily initialize the usage helper + if (mUsageHelper == null) { + mUsageHelper = new PermissionUsageHelper(mContext); + } + return mUsageHelper.getOpUsageData(micMuted); + } + + /** * Determine if a package should be shown in indicators. Only a select few roles, and the * system app itself, are hidden. These values are updated at most every 15 seconds. * @hide diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index d89c29a5a360..87fb611d2a0b 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -37,6 +37,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.content.res.TypedArray; +import android.graphics.BLASTBufferQueue; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; @@ -208,8 +209,8 @@ public abstract class WallpaperService extends Service { int mCurHeight; float mZoom = 0f; int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - int mWindowPrivateFlags = - WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS; + int mWindowPrivateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS + | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; int mCurWindowFlags = mWindowFlags; int mCurWindowPrivateFlags = mWindowPrivateFlags; Rect mPreviewSurfacePosition; @@ -253,6 +254,7 @@ public abstract class WallpaperService extends Service { private int mDisplayState; SurfaceControl mSurfaceControl = new SurfaceControl(); + BLASTBufferQueue mBlastBufferQueue; final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { { @@ -974,7 +976,14 @@ public abstract class WallpaperService extends Service { View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl, mInsetsState, mTempControls, mSurfaceSize); if (mSurfaceControl.isValid()) { - mSurfaceHolder.mSurface.copyFrom(mSurfaceControl); + Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, + mSurfaceSize.y, mFormat); + // If blastSurface == null that means it hasn't changed since the last + // time we called. In this situation, avoid calling transferFrom as we + // would then inc the generation ID and cause EGL resources to be recreated. + if (blastSurface != null) { + mSurfaceHolder.mSurface.transferFrom(blastSurface); + } } if (!mLastSurfaceSize.equals(mSurfaceSize)) { mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y); @@ -1455,13 +1464,12 @@ public abstract class WallpaperService extends Service { return; } Surface surface = mSurfaceHolder.getSurface(); - boolean widthIsLarger = - mSurfaceControl.getWidth() > mSurfaceControl.getHeight(); - int smaller = widthIsLarger ? mSurfaceControl.getWidth() - : mSurfaceControl.getHeight(); + boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y; + int smaller = widthIsLarger ? mSurfaceSize.x + : mSurfaceSize.y; float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller; - int width = (int) (ratio * mSurfaceControl.getWidth()); - int height = (int) (ratio * mSurfaceControl.getHeight()); + int width = (int) (ratio * mSurfaceSize.x); + int height = (int) (ratio * mSurfaceSize.y); if (width <= 0 || height <= 0) { Log.e(TAG, "wrong width and height values of bitmap " + width + " " + height); return; @@ -1842,6 +1850,21 @@ public abstract class WallpaperService extends Service { public void onDisplayAdded(int displayId) { } }; + + private Surface getOrCreateBLASTSurface(int width, int height, int format) { + Surface ret = null; + if (mBlastBufferQueue == null) { + mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mSurfaceControl, width, + height, format); + // We only return the Surface the first time, as otherwise + // it hasn't changed and there is no need to update. + ret = mBlastBufferQueue.createSurface(); + } else { + mBlastBufferQueue.update(mSurfaceControl, width, height, format); + } + + return ret; + } } private boolean isValid(RectF area) { diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 340fa406bbb7..4b18c5aae614 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -796,13 +796,14 @@ public class TelephonyRegistryManager { /** * Notify {@link PhysicalChannelConfig} has changed for a specific subscription. * + * @param slotIndex for which physical channel configs changed. * @param subId the subId * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel. */ - public void notifyPhysicalChannelConfigForSubscriber( - int subId, List<PhysicalChannelConfig> configs) { + public void notifyPhysicalChannelConfigForSubscriber(int slotIndex, int subId, + List<PhysicalChannelConfig> configs) { try { - sRegistry.notifyPhysicalChannelConfigForSubscriber(subId, configs); + sRegistry.notifyPhysicalChannelConfigForSubscriber(slotIndex, subId, configs); } catch (RemoteException ex) { // system server crash } diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 0a3963d782b1..be172f748b55 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -693,71 +693,79 @@ public final class Choreographer { ThreadedRenderer.setFPSDivisor(divisor); } + private void traceMessage(String msg) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, msg); + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + void doFrame(long frameTimeNanos, int frame, DisplayEventReceiver.VsyncEventData vsyncEventData) { final long startNanos; final long frameIntervalNanos = vsyncEventData.frameInterval; - synchronized (mLock) { - if (!mFrameScheduled) { - return; // no work to do - } - - if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) { - mDebugPrintNextFrameTimeDelta = false; - Log.d(TAG, "Frame time delta: " - + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); + try { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, + "Choreographer#doFrame " + vsyncEventData.id); } - - long intendedFrameTimeNanos = frameTimeNanos; - startNanos = System.nanoTime(); - final long jitterNanos = startNanos - frameTimeNanos; - if (jitterNanos >= frameIntervalNanos) { - final long skippedFrames = jitterNanos / frameIntervalNanos; - if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { - Log.i(TAG, "Skipped " + skippedFrames + " frames! " - + "The application may be doing too much work on its main thread."); + synchronized (mLock) { + if (!mFrameScheduled) { + traceMessage("Frame not scheduled"); + return; // no work to do } - final long lastFrameOffset = jitterNanos % frameIntervalNanos; - if (DEBUG_JANK) { - Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " - + "which is more than the frame interval of " - + (frameIntervalNanos * 0.000001f) + " ms! " - + "Skipping " + skippedFrames + " frames and setting frame " - + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); + + if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) { + mDebugPrintNextFrameTimeDelta = false; + Log.d(TAG, "Frame time delta: " + + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); } - frameTimeNanos = startNanos - lastFrameOffset; - } - if (frameTimeNanos < mLastFrameTimeNanos) { - if (DEBUG_JANK) { - Log.d(TAG, "Frame time appears to be going backwards. May be due to a " - + "previously skipped frame. Waiting for next vsync."); + long intendedFrameTimeNanos = frameTimeNanos; + startNanos = System.nanoTime(); + final long jitterNanos = startNanos - frameTimeNanos; + if (jitterNanos >= frameIntervalNanos) { + final long skippedFrames = jitterNanos / frameIntervalNanos; + if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { + Log.i(TAG, "Skipped " + skippedFrames + " frames! " + + "The application may be doing too much work on its main thread."); + } + final long lastFrameOffset = jitterNanos % frameIntervalNanos; + if (DEBUG_JANK) { + Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + + "which is more than the frame interval of " + + (frameIntervalNanos * 0.000001f) + " ms! " + + "Skipping " + skippedFrames + " frames and setting frame " + + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); + } + frameTimeNanos = startNanos - lastFrameOffset; } - scheduleVsyncLocked(); - return; - } - if (mFPSDivisor > 1) { - long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; - if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) { + if (frameTimeNanos < mLastFrameTimeNanos) { + if (DEBUG_JANK) { + Log.d(TAG, "Frame time appears to be going backwards. May be due to a " + + "previously skipped frame. Waiting for next vsync."); + } + traceMessage("Frame time goes backward"); scheduleVsyncLocked(); return; } - } - mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id, - vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval); - mFrameScheduled = false; - mLastFrameTimeNanos = frameTimeNanos; - mLastFrameIntervalNanos = frameIntervalNanos; - mLastVsyncEventData = vsyncEventData; - } + if (mFPSDivisor > 1) { + long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; + if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) { + traceMessage("Frame skipped due to FPSDivisor"); + scheduleVsyncLocked(); + return; + } + } - try { - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "Choreographer#doFrame " + vsyncEventData.id); + mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id, + vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval); + mFrameScheduled = false; + mLastFrameTimeNanos = frameTimeNanos; + mLastFrameIntervalNanos = frameIntervalNanos; + mLastVsyncEventData = vsyncEventData; } + AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); @@ -870,7 +878,12 @@ public final class Choreographer { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void scheduleVsyncLocked() { - mDisplayEventReceiver.scheduleVsync(); + try { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked"); + mDisplayEventReceiver.scheduleVsync(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } private boolean isRunningOnLooperThreadLocked() { @@ -967,32 +980,40 @@ public final class Choreographer { @Override public void onVsync(long timestampNanos, long physicalDisplayId, int frame, VsyncEventData vsyncEventData) { - // Post the vsync event to the Handler. - // The idea is to prevent incoming vsync events from completely starving - // the message queue. If there are no messages in the queue with timestamps - // earlier than the frame time, then the vsync event will be processed immediately. - // Otherwise, messages that predate the vsync event will be handled first. - long now = System.nanoTime(); - if (timestampNanos > now) { - Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f) - + " ms in the future! Check that graphics HAL is generating vsync " - + "timestamps using the correct timebase."); - timestampNanos = now; - } + try { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, + "Choreographer#onVsync " + vsyncEventData.id); + } + // Post the vsync event to the Handler. + // The idea is to prevent incoming vsync events from completely starving + // the message queue. If there are no messages in the queue with timestamps + // earlier than the frame time, then the vsync event will be processed immediately. + // Otherwise, messages that predate the vsync event will be handled first. + long now = System.nanoTime(); + if (timestampNanos > now) { + Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f) + + " ms in the future! Check that graphics HAL is generating vsync " + + "timestamps using the correct timebase."); + timestampNanos = now; + } - if (mHavePendingVsync) { - Log.w(TAG, "Already have a pending vsync event. There should only be " - + "one at a time."); - } else { - mHavePendingVsync = true; - } + if (mHavePendingVsync) { + Log.w(TAG, "Already have a pending vsync event. There should only be " + + "one at a time."); + } else { + mHavePendingVsync = true; + } - mTimestampNanos = timestampNanos; - mFrame = frame; - mLastVsyncEventData = vsyncEventData; - Message msg = Message.obtain(mHandler, this); - msg.setAsynchronous(true); - mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); + mTimestampNanos = timestampNanos; + mFrame = frame; + mLastVsyncEventData = vsyncEventData; + Message msg = Message.obtain(mHandler, this); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } @Override diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java index a6d786e1db21..5fcb011c76f1 100644 --- a/core/java/android/view/ScrollCaptureConnection.java +++ b/core/java/android/view/ScrollCaptureConnection.java @@ -185,7 +185,8 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub { } Log.w(TAG, "close(): capture session still active! Ending now."); // -> UiThread - mUiThread.execute(() -> mLocal.onScrollCaptureEnd(() -> { /* ignore */ })); + final ScrollCaptureCallback callback = mLocal; + mUiThread.execute(() -> callback.onScrollCaptureEnd(() -> { /* ignore */ })); mActive = false; } mActive = false; diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 21f75d419a5e..2c81e8986ac6 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -35,6 +35,7 @@ import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; @@ -188,6 +189,10 @@ public final class SurfaceControl implements Parcelable { IBinder displayToken, int mode); private static native void nativeReparent(long transactionObj, long nativeObject, long newParentNativeObject); + private static native void nativeSetBuffer(long transactionObj, long nativeObject, + GraphicBuffer buffer); + private static native void nativeSetColorSpace(long transactionObj, long nativeObject, + int colorSpace); private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes); @@ -3362,6 +3367,31 @@ public final class SurfaceControl implements Parcelable { return this; } + /** + * Set a buffer for a SurfaceControl. This can only be used for SurfaceControls that were + * created as type {@link #FX_SURFACE_BLAST} + * + * @hide + */ + public Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) { + checkPreconditions(sc); + nativeSetBuffer(mNativeObject, sc.mNativeObject, buffer); + return this; + } + + /** + * Set the color space for the SurfaceControl. The supported color spaces are SRGB + * and Display P3, other color spaces will be treated as SRGB. This can only be used for + * SurfaceControls that were created as type {@link #FX_SURFACE_BLAST} + * + * @hide + */ + public Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) { + checkPreconditions(sc); + nativeSetColorSpace(mNativeObject, sc.mNativeObject, colorSpace.getId()); + return this; + } + /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 2b96a14b04d4..7bdf5cf879f3 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -30,6 +30,7 @@ import android.graphics.BLASTBufferQueue; import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.HardwareRenderer; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; @@ -221,8 +222,35 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private int mPendingReportDraws; - private SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction(); - private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); + /** + * Transaction that should be used from the render thread. This transaction is only thread safe + * with other calls directly from the render thread. + */ + private final SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction(); + + /** + * Transaction that should be used whe + * {@link HardwareRenderer.FrameDrawingCallback#onFrameDraw} is invoked. All + * frame callbacks can use the same transaction since they will be thread safe + */ + private final SurfaceControl.Transaction mFrameCallbackTransaction = + new SurfaceControl.Transaction(); + + /** + * Transaction that should be used for + * {@link RenderNode.PositionUpdateListener#positionChanged(long, int, int, int, int)} + * The callback is invoked from a thread pool so it's not thread safe with other render thread + * transactions. Keep the transactions for position changed callbacks on its own transaction. + */ + private final SurfaceControl.Transaction mPositionChangedTransaction = + new SurfaceControl.Transaction(); + + /** + * A temporary transaction holder that should only be used when applying right away. There + * should be no assumption about thread safety for this transaction. + */ + private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); + private int mParentSurfaceSequenceId; private RemoteAccessibilityController mRemoteAccessibilityController = @@ -432,7 +460,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * This gets called on a RenderThread worker thread, so members accessed here must * be protected by a lock. */ - final boolean useBLAST = useBLASTSync(viewRoot); viewRoot.registerRtFrameCallback(frame -> { try { synchronized (mSurfaceControlLock) { @@ -456,8 +483,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall Log.d(TAG, System.identityHashCode(this) + " updateSurfaceAlpha RT: set alpha=" + alpha); } - mRtTransaction.setAlpha(mSurfaceControl, alpha); - applyRtTransaction(frame); + + mFrameCallbackTransaction.setAlpha(mSurfaceControl, alpha); + applyOrMergeTransaction(mFrameCallbackTransaction, frame); } // It's possible that mSurfaceControl is released in the UI thread before // the transaction completes. If that happens, an exception is thrown, which @@ -806,7 +834,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * This gets called on a RenderThread worker thread, so members accessed here must * be protected by a lock. */ - final boolean useBLAST = useBLASTSync(viewRoot); viewRoot.registerRtFrameCallback(frame -> { try { synchronized (mSurfaceControlLock) { @@ -814,8 +841,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall return; } - updateRelativeZ(mRtTransaction); - applyRtTransaction(frame); + updateRelativeZ(mFrameCallbackTransaction); + applyOrMergeTransaction(mFrameCallbackTransaction, frame); } // It's possible that mSurfaceControl is released in the UI thread before // the transaction completes. If that happens, an exception is thrown, which @@ -1380,22 +1407,21 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall return mRTLastReportedPosition; } - private void setParentSpaceRectangle(Rect position, long frameNumber) { + private void setParentSpaceRectangle(Rect position, long frameNumber, Transaction t) { final ViewRootImpl viewRoot = getViewRootImpl(); - applySurfaceTransforms(mSurfaceControl, mRtTransaction, position); - applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface, frameNumber); - applyRtTransaction(frameNumber); + applySurfaceTransforms(mSurfaceControl, t, position); + applyChildSurfaceTransaction_renderWorker(t, viewRoot.mSurface, frameNumber); + applyOrMergeTransaction(t, frameNumber); } - private void applyRtTransaction(long frameNumber) { + private void applyOrMergeTransaction(Transaction t, long frameNumber) { final ViewRootImpl viewRoot = getViewRootImpl(); boolean useBLAST = viewRoot != null && useBLASTSync(viewRoot); if (useBLAST) { // If we are using BLAST, merge the transaction with the viewroot buffer transaction. - viewRoot.mergeWithNextTransaction(mRtTransaction, frameNumber); - return; + viewRoot.mergeWithNextTransaction(t, frameNumber); } else { - mRtTransaction.apply(); + t.apply(); } } @@ -1436,7 +1462,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall left, top, right, bottom)); } mRTLastReportedPosition.set(left, top, right, bottom); - setParentSpaceRectangle(mRTLastReportedPosition, frameNumber); + setParentSpaceRectangle(mRTLastReportedPosition, frameNumber, + mPositionChangedTransaction); // Now overwrite mRTLastReportedPosition with our values } catch (Exception ex) { Log.e(TAG, "Exception from repositionChild", ex); @@ -1448,7 +1475,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall float bottom, float vecX, float vecY, float maxStretch) { mRtTransaction.setStretchEffect(mSurfaceControl, left, top, right, bottom, vecX, vecY, maxStretch); - applyRtTransaction(frameNumber); + applyOrMergeTransaction(mRtTransaction, frameNumber); } @Override @@ -1468,14 +1495,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * need to hold the lock here. */ synchronized (mSurfaceControlLock) { - final ViewRootImpl viewRoot = getViewRootImpl(); - mRtTransaction.hide(mSurfaceControl); if (mRtReleaseSurfaces) { mRtReleaseSurfaces = false; releaseSurfaces(mRtTransaction); } - applyRtTransaction(frameNumber); + applyOrMergeTransaction(mRtTransaction, frameNumber); mRtHandlingPositionUpdates = false; } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 76eb882b6f53..be8e51980c3c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -9309,11 +9309,11 @@ public final class ViewRootImpl implements ViewParent, * Handles an inbound request for scroll capture from the system. A search will be * dispatched through the view tree to locate scrolling content. * <p> - * A call to {@link IScrollCaptureCallbacks#onScrollCaptureResponse(ScrollCaptureResponse)} - * will follow. + * A call to + * {@link IScrollCaptureResponseListener#onScrollCaptureResponse} will follow. * * @param listener to receive responses - * @see ScrollCaptureTargetSelector + * @see ScrollCaptureSearchResults */ public void handleScrollCaptureRequest(@NonNull IScrollCaptureResponseListener listener) { ScrollCaptureSearchResults results = diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 616910ab09ca..d6292caba344 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -581,7 +581,7 @@ public final class InputMethodManager { */ public void reportPerceptible(IBinder windowToken, boolean perceptible) { try { - mService.reportPerceptible(windowToken, perceptible); + mService.reportPerceptibleAsync(windowToken, perceptible); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index 20e520efc761..436596611f83 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -30,7 +30,7 @@ import com.android.internal.inputmethod.IVoidResultCallback; */ oneway interface IInputMethodPrivilegedOperations { void setImeWindowStatusAsync(int vis, int backDisposition); - void reportStartInput(in IBinder startInputToken, in IVoidResultCallback resultCallback); + void reportStartInputAsync(in IBinder startInputToken); void createInputContentUriToken(in Uri contentUri, in String packageName, in IIInputContentUriTokenResultCallback resultCallback); void reportFullscreenMode(boolean fullscreen, in IVoidResultCallback resultCallback); diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 10009140a7c0..555488d68866 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -123,21 +123,18 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#reportStartInput(IBinder, - * IVoidResultCallback)}. + * Calls {@link IInputMethodPrivilegedOperations#reportStartInputAsync(IBinder)}. * * @param startInputToken {@link IBinder} token to distinguish startInput session */ @AnyThread - public void reportStartInput(IBinder startInputToken) { + public void reportStartInputAsync(IBinder startInputToken) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { return; } try { - final Completable.Void value = Completable.createVoid(); - ops.reportStartInput(startInputToken, ResultCallbacks.of(value)); - Completable.getResult(value); + ops.reportStartInputAsync(startInputToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index e438d39e42f6..a0a0f3276b99 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -92,7 +92,7 @@ interface ITelephonyRegistry { void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity, String chosenPlmn, int domain, int causeCode, int additionalCauseCode); void notifyBarringInfoChanged(int slotIndex, int subId, in BarringInfo barringInfo); - void notifyPhysicalChannelConfigForSubscriber(in int subId, + void notifyPhysicalChannelConfigForSubscriber(in int phoneId, in int subId, in List<PhysicalChannelConfig> configs); void notifyDataEnabled(in int phoneId, int subId, boolean enabled, int reason); void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index fd13c26b05b2..93cd4e9046c6 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -85,7 +85,7 @@ interface IInputMethodManager { oneway void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, in float[] matrixValues, in IVoidResultCallback resultCallback); - oneway void reportPerceptible(in IBinder windowToken, boolean perceptible); + oneway void reportPerceptibleAsync(in IBinder windowToken, boolean perceptible); /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */ oneway void removeImeSurface(in IVoidResultCallback resultCallback); /** Remove the IME surface. Requires passing the currently focused window. */ diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java index a41511b74a7d..8aa2d57e8ea6 100644 --- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java +++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java @@ -17,6 +17,7 @@ package com.android.internal.view; import android.annotation.UiThread; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.HardwareRenderer; @@ -26,6 +27,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.RenderNode; import android.os.CancellationSignal; +import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display.ColorMode; @@ -53,11 +55,14 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa private static final String TAG = "ScrollCaptureViewSupport"; - private static final boolean WAIT_FOR_ANIMATION = true; + private static final String SETTING_CAPTURE_DELAY = "screenshot.scroll_capture_delay"; + private static final long SETTING_CAPTURE_DELAY_DEFAULT = 60L; // millis private final WeakReference<V> mWeakView; private final ScrollCaptureViewHelper<V> mViewHelper; private final ViewRenderer mRenderer; + private final long mPostScrollDelayMillis; + private boolean mStarted; private boolean mEnded; @@ -66,6 +71,10 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa mRenderer = new ViewRenderer(); // TODO(b/177649144): provide access to color space from android.media.Image mViewHelper = viewHelper; + Context context = containingView.getContext(); + ContentResolver contentResolver = context.getContentResolver(); + mPostScrollDelayMillis = Settings.Global.getLong(contentResolver, + SETTING_CAPTURE_DELAY, SETTING_CAPTURE_DELAY_DEFAULT); } /** Based on ViewRootImpl#updateColorModeIfNeeded */ @@ -120,37 +129,41 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa public final void onScrollCaptureImageRequest(ScrollCaptureSession session, CancellationSignal signal, Rect requestRect, Consumer<Rect> onComplete) { if (signal.isCanceled()) { + Log.w(TAG, "onScrollCaptureImageRequest: cancelled!"); return; } + V view = mWeakView.get(); if (view == null || !view.isVisibleToUser()) { // Signal to the controller that we have a problem and can't continue. onComplete.accept(new Rect()); return; } + // Ask the view to scroll as needed to bring this area into view. ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(), requestRect); + if (scrollResult.availableArea.isEmpty()) { onComplete.accept(scrollResult.availableArea); return; } - view.invalidate(); // don't wait for vsync // For image capture, shift back by scrollDelta to arrive at the location within the view // where the requested content will be drawn Rect viewCaptureArea = new Rect(scrollResult.availableArea); viewCaptureArea.offset(0, -scrollResult.scrollDelta); - if (WAIT_FOR_ANIMATION) { - view.postOnAnimation(() -> { + Runnable captureAction = () -> { + if (signal.isCanceled()) { + Log.w(TAG, "onScrollCaptureImageRequest: cancelled! skipping render."); + } else { mRenderer.renderView(view, viewCaptureArea); onComplete.accept(new Rect(scrollResult.availableArea)); - }); - } else { - mRenderer.renderView(view, viewCaptureArea); - onComplete.accept(new Rect(scrollResult.availableArea)); - } + } + }; + + view.postOnAnimationDelayed(captureAction, mPostScrollDelayMillis); } @Override diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ffba628f73ab..4194acbfe015 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -30,6 +30,7 @@ #include <android/hardware/display/IDeviceProductInfoConstants.h> #include <android/os/IInputConstants.h> #include <android_runtime/AndroidRuntime.h> +#include <android_runtime/android_graphics_GraphicBuffer.h> #include <android_runtime/android_hardware_HardwareBuffer.h> #include <android_runtime/android_view_Surface.h> #include <android_runtime/android_view_SurfaceSession.h> @@ -253,6 +254,15 @@ constexpr jint fromDataspaceToNamedColorSpaceValue(const ui::Dataspace dataspace } } +constexpr ui::Dataspace fromNamedColorSpaceValueToDataspace(const jint colorSpace) { + switch (colorSpace) { + case JNamedColorSpace::DISPLAY_P3: + return ui::Dataspace::DISPLAY_P3; + default: + return ui::Dataspace::V0_SRGB; + } +} + constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) { switch (colorMode) { case ui::ColorMode::DISPLAY_P3: @@ -553,6 +563,23 @@ static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, j transaction->setGeometry(ctrl, source, dst, orientation); } +static void nativeSetBuffer(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, + jobject bufferObject) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + sp<GraphicBuffer> buffer( + android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, bufferObject)); + transaction->setBuffer(ctrl, buffer); +} + +static void nativeSetColorSpace(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, + jint colorSpace) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + ui::Dataspace dataspace = fromNamedColorSpaceValueToDataspace(colorSpace); + transaction->setDataspace(ctrl, dataspace); +} + static void nativeSetBlurRegions(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jobjectArray regions, jint regionsLength) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -1877,6 +1904,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetDisplayedContentSample }, {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V", (void*)nativeSetGeometry }, + {"nativeSetBuffer", "(JJLandroid/graphics/GraphicBuffer;)V", + (void*)nativeSetBuffer }, + {"nativeSetColorSpace", "(JJI)V", + (void*)nativeSetColorSpace }, {"nativeSyncInputWindows", "(J)V", (void*)nativeSyncInputWindows }, {"nativeGetDisplayBrightnessSupport", "(Landroid/os/IBinder;)Z", diff --git a/core/proto/android/internal/OWNERS b/core/proto/android/internal/OWNERS new file mode 100644 index 000000000000..24e24c20a9cd --- /dev/null +++ b/core/proto/android/internal/OWNERS @@ -0,0 +1,2 @@ +# Binder +per-file binder_latency.proto = file:/core/java/com/android/internal/os/BINDER_OWNERS
\ No newline at end of file diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index a7127ad79401..b157146e70a3 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -308,6 +308,7 @@ message TaskProto { optional float minimize_amount = 27; optional bool created_by_organizer = 28; optional string affinity = 29; + optional bool has_child_pip_activity = 30; } /* represents ActivityRecordProto */ diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 419f142d0795..f24d663ced16 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4700,6 +4700,11 @@ --> <color name="config_letterboxBackgroundColor">#000</color> + <!-- Horizonal position of a center of the letterboxed app window. + 0 corresponds to the left side of the screen and 1 to the right side. If given value < 0 + or > 1, it is ignored and central positionis used (0.5). --> + <item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item> + <!-- If true, hide the display cutout with display area --> <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d69c7f8f9587..5715fabd3f24 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4186,6 +4186,7 @@ <java-symbol type="dimen" name="config_letterboxBackgroundWallaperDarkScrimAlpha" /> <java-symbol type="integer" name="config_letterboxBackgroundType" /> <java-symbol type="color" name="config_letterboxBackgroundColor" /> + <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" /> <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" /> diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java index fb0dd46a52f0..b2b9ab31282c 100644 --- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java +++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java @@ -93,8 +93,8 @@ public class PasswordMetricsTest { @Test public void testComputeForPassword_metrics() { - final PasswordMetrics metrics = - PasswordMetrics.computeForPassword("6B~0z1Z3*8A".getBytes()); + final PasswordMetrics metrics = PasswordMetrics.computeForPasswordOrPin( + "6B~0z1Z3*8A".getBytes(), /* isPin */ false); assertEquals(11, metrics.length); assertEquals(4, metrics.letters); assertEquals(3, metrics.upperCase); @@ -133,61 +133,71 @@ public class PasswordMetricsTest { @Test public void testDetermineComplexity_lowNumeric() { assertEquals(PASSWORD_COMPLEXITY_LOW, - PasswordMetrics.computeForPassword("1234".getBytes()).determineComplexity()); + PasswordMetrics.computeForPasswordOrPin("1234".getBytes(), + /* isPin */true).determineComplexity()); } @Test public void testDetermineComplexity_lowNumericComplex() { assertEquals(PASSWORD_COMPLEXITY_LOW, - PasswordMetrics.computeForPassword("124".getBytes()).determineComplexity()); + PasswordMetrics.computeForPasswordOrPin("124".getBytes(), + /* isPin */ true).determineComplexity()); } @Test public void testDetermineComplexity_lowAlphabetic() { assertEquals(PASSWORD_COMPLEXITY_LOW, - PasswordMetrics.computeForPassword("a!".getBytes()).determineComplexity()); + PasswordMetrics.computeForPasswordOrPin("a!".getBytes(), + /* isPin */ false).determineComplexity()); } @Test public void testDetermineComplexity_lowAlphanumeric() { assertEquals(PASSWORD_COMPLEXITY_LOW, - PasswordMetrics.computeForPassword("a!1".getBytes()).determineComplexity()); + PasswordMetrics.computeForPasswordOrPin("a!1".getBytes(), + /* isPin */ false).determineComplexity()); } @Test public void testDetermineComplexity_mediumNumericComplex() { assertEquals(PASSWORD_COMPLEXITY_MEDIUM, - PasswordMetrics.computeForPassword("1238".getBytes()).determineComplexity()); + PasswordMetrics.computeForPasswordOrPin("1238".getBytes(), + /* isPin */ true).determineComplexity()); } @Test public void testDetermineComplexity_mediumAlphabetic() { assertEquals(PASSWORD_COMPLEXITY_MEDIUM, - PasswordMetrics.computeForPassword("ab!c".getBytes()).determineComplexity()); + PasswordMetrics.computeForPasswordOrPin("ab!c".getBytes(), + /* isPin */ false).determineComplexity()); } @Test public void testDetermineComplexity_mediumAlphanumeric() { assertEquals(PASSWORD_COMPLEXITY_MEDIUM, - PasswordMetrics.computeForPassword("ab!1".getBytes()).determineComplexity()); + PasswordMetrics.computeForPasswordOrPin("ab!1".getBytes(), + /* isPin */ false).determineComplexity()); } @Test public void testDetermineComplexity_highNumericComplex() { assertEquals(PASSWORD_COMPLEXITY_HIGH, - PasswordMetrics.computeForPassword("12389647!".getBytes()).determineComplexity()); + PasswordMetrics.computeForPasswordOrPin("12389647!".getBytes(), + /* isPin */ true).determineComplexity()); } @Test public void testDetermineComplexity_highAlphabetic() { assertEquals(PASSWORD_COMPLEXITY_HIGH, - PasswordMetrics.computeForPassword("alphabetic!".getBytes()).determineComplexity()); + PasswordMetrics.computeForPasswordOrPin("alphabetic!".getBytes(), + /* isPin */ false).determineComplexity()); } @Test public void testDetermineComplexity_highAlphanumeric() { - assertEquals(PASSWORD_COMPLEXITY_HIGH, PasswordMetrics.computeForPassword( - "alphanumeric123!".getBytes()).determineComplexity()); + assertEquals(PASSWORD_COMPLEXITY_HIGH, + PasswordMetrics.computeForPasswordOrPin("alphanumeric123!".getBytes(), + /* isPin */ false).determineComplexity()); } @Test diff --git a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java index e951054e6558..f1be173b3677 100644 --- a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java +++ b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java @@ -28,6 +28,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; import static org.junit.Assert.assertEquals; @@ -80,7 +81,7 @@ public class PasswordPolicyTest { public void testGetMinMetrics_numeric() { PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC); PasswordMetrics minMetrics = policy.getMinMetrics(); - assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType); + assertEquals(CREDENTIAL_TYPE_PIN, minMetrics.credType); assertEquals(TEST_VALUE, minMetrics.length); assertEquals(0, minMetrics.numeric); // numeric can doesn't really require digits. assertEquals(0, minMetrics.letters); @@ -104,7 +105,7 @@ public class PasswordPolicyTest { public void testGetMinMetrics_numericComplex() { PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC_COMPLEX); PasswordMetrics minMetrics = policy.getMinMetrics(); - assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType); + assertEquals(CREDENTIAL_TYPE_PIN, minMetrics.credType); assertEquals(TEST_VALUE, minMetrics.length); assertEquals(0, minMetrics.numeric); assertEquals(0, minMetrics.letters); diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java index 56c685afa026..3d18337ab257 100644 --- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.expectThrows; +import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import androidx.test.core.app.ApplicationProvider; @@ -89,8 +90,12 @@ public class AppSearchSessionUnitTest { }); // Verify the NullPointException has been thrown. - ExecutionException executionException = expectThrows(ExecutionException.class, - putDocumentsFuture::get); - assertThat(executionException.getCause()).isInstanceOf(NullPointerException.class); + ExecutionException executionException = + expectThrows(ExecutionException.class, putDocumentsFuture::get); + assertThat(executionException.getCause()).isInstanceOf(AppSearchException.class); + AppSearchException appSearchException = (AppSearchException) executionException.getCause(); + assertThat(appSearchException.getResultCode()) + .isEqualTo(AppSearchResult.RESULT_INTERNAL_ERROR); + assertThat(appSearchException.getMessage()).startsWith("NullPointerException"); } } diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java index 22c71b527d48..e7b88c8df393 100644 --- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java +++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java @@ -32,6 +32,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.ICancellationSignal; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -189,4 +190,15 @@ public class ScrollCaptureConnectionTest { verifyNoMoreInteractions(mRemote); } + @Test + public void testClose_whileActive() throws RemoteException { + mConnection.startCapture(mSurface, mRemote); + + mCallback.completeStartRequest(); + assertTrue(mConnection.isActive()); + + mConnection.close(); + mCallback.completeEndRequest(); + assertFalse(mConnection.isActive()); + } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java index c393d68c3f94..8225afc5f510 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -244,7 +244,8 @@ public class TextClassificationTest { PendingIntent.getActivity( context, 0, new Intent("action1"), FLAG_IMMUTABLE))) .addAction(new RemoteAction(icon1, "title2", "desc2", - PendingIntent.getActivity(context, 0, new Intent("action2"), 0))) + PendingIntent.getActivity(context, 0, new Intent("action2"), + FLAG_IMMUTABLE))) .setEntityType(TextClassifier.TYPE_EMAIL, 0.5f) .setEntityType(TextClassifier.TYPE_PHONE, 0.4f) .build(); diff --git a/core/tests/nfctests/Android.bp b/core/tests/nfctests/Android.bp new file mode 100644 index 000000000000..335cea140df6 --- /dev/null +++ b/core/tests/nfctests/Android.bp @@ -0,0 +1,38 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "NfcManagerTests", + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.rules", + "mockito-target-minus-junit4", + ], + libs: [ + "android.test.runner", + ], + srcs: ["src/**/*.java"], + platform_apis: true, + certificate: "platform", + test_suites: ["device-tests"], +} diff --git a/core/tests/nfctests/AndroidManifest.xml b/core/tests/nfctests/AndroidManifest.xml new file mode 100644 index 000000000000..99e2c34c656b --- /dev/null +++ b/core/tests/nfctests/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.nfc"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <!-- This is a self-instrumenting test package. --> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.nfc" + android:label="NFC Manager Tests"> + </instrumentation> + +</manifest> + diff --git a/core/tests/nfctests/AndroidTest.xml b/core/tests/nfctests/AndroidTest.xml new file mode 100644 index 000000000000..490d6f5df197 --- /dev/null +++ b/core/tests/nfctests/AndroidTest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Config for NFC Manager test cases"> + <option name="test-suite-tag" value="apct"/> + <option name="test-suite-tag" value="apct-instrumentation"/> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="NfcManagerTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="NfcManagerTests"/> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.nfc" /> + <option name="hidden-api-checks" value="false"/> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/> + </test> +</configuration> diff --git a/core/tests/nfctests/OWNERS b/core/tests/nfctests/OWNERS new file mode 100644 index 000000000000..34b095c7fda0 --- /dev/null +++ b/core/tests/nfctests/OWNERS @@ -0,0 +1 @@ +include /core/java/android/nfc/OWNERS diff --git a/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java b/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java new file mode 100644 index 000000000000..43f9b6feea45 --- /dev/null +++ b/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java @@ -0,0 +1,166 @@ +/* + * 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 android.nfc; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.nfc.NfcAdapter.ControllerAlwaysOnListener; +import android.os.RemoteException; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Test of {@link NfcControllerAlwaysOnListener}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NfcControllerAlwaysOnListenerTest { + + private INfcAdapter mNfcAdapter = mock(INfcAdapter.class); + + private Throwable mThrowRemoteException = new RemoteException("RemoteException"); + + private static Executor getExecutor() { + return new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + } + }; + } + + private static void verifyListenerInvoked(ControllerAlwaysOnListener listener) { + verify(listener, times(1)).onControllerAlwaysOnChanged(anyBoolean()); + } + + @Test + public void testRegister_RegisterUnregister() throws RemoteException { + NfcControllerAlwaysOnListener mListener = + new NfcControllerAlwaysOnListener(mNfcAdapter); + ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class); + ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class); + + // Verify that the state listener registered with the NFC Adapter + mListener.register(getExecutor(), mockListener1); + verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any()); + + // Register a second client and no new call to NFC Adapter + mListener.register(getExecutor(), mockListener2); + verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any()); + + // Unregister first listener + mListener.unregister(mockListener1); + verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any()); + verify(mNfcAdapter, times(0)).unregisterControllerAlwaysOnListener(any()); + + // Unregister second listener and the state listener registered with the NFC Adapter + mListener.unregister(mockListener2); + verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any()); + verify(mNfcAdapter, times(1)).unregisterControllerAlwaysOnListener(any()); + } + + @Test + public void testRegister_FirstRegisterFails() throws RemoteException { + NfcControllerAlwaysOnListener mListener = + new NfcControllerAlwaysOnListener(mNfcAdapter); + ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class); + ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class); + + // Throw a remote exception whenever first registering + doThrow(mThrowRemoteException).when(mNfcAdapter).registerControllerAlwaysOnListener( + any()); + + mListener.register(getExecutor(), mockListener1); + verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any()); + + // No longer throw an exception, instead succeed + doNothing().when(mNfcAdapter).registerControllerAlwaysOnListener(any()); + + // Register a different listener + mListener.register(getExecutor(), mockListener2); + verify(mNfcAdapter, times(2)).registerControllerAlwaysOnListener(any()); + + // Ensure first and second listener were invoked + mListener.onControllerAlwaysOnChanged(true); + verifyListenerInvoked(mockListener1); + verifyListenerInvoked(mockListener2); + } + + @Test + public void testRegister_RegisterSameListenerTwice() throws RemoteException { + NfcControllerAlwaysOnListener mListener = + new NfcControllerAlwaysOnListener(mNfcAdapter); + ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class); + + // Register the same listener Twice + mListener.register(getExecutor(), mockListener); + mListener.register(getExecutor(), mockListener); + verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any()); + + // Invoke a state change and ensure the listener is only called once + mListener.onControllerAlwaysOnChanged(true); + verifyListenerInvoked(mockListener); + } + + @Test + public void testNotify_AllListenersNotified() throws RemoteException { + + NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter); + List<ControllerAlwaysOnListener> mockListeners = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class); + listener.register(getExecutor(), mockListener); + mockListeners.add(mockListener); + } + + // Invoke a state change and ensure all listeners are invoked + listener.onControllerAlwaysOnChanged(true); + for (ControllerAlwaysOnListener mListener : mockListeners) { + verifyListenerInvoked(mListener); + } + } + + @Test + public void testStateChange_CorrectValue() { + runStateChangeValue(true, true); + runStateChangeValue(false, false); + + } + + private void runStateChangeValue(boolean isEnabledIn, boolean isEnabledOut) { + NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter); + ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class); + listener.register(getExecutor(), mockListener); + listener.onControllerAlwaysOnChanged(isEnabledIn); + verify(mockListener, times(1)).onControllerAlwaysOnChanged(isEnabledOut); + verify(mockListener, times(0)).onControllerAlwaysOnChanged(!isEnabledOut); + } +} diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java index 6c03ddc4a12d..e9e58c6213bf 100644 --- a/graphics/java/android/graphics/RecordingCanvas.java +++ b/graphics/java/android/graphics/RecordingCanvas.java @@ -226,10 +226,12 @@ public final class RecordingCanvas extends BaseRecordingCanvas { */ public void drawRipple(CanvasProperty<Float> cx, CanvasProperty<Float> cy, CanvasProperty<Float> radius, CanvasProperty<Paint> paint, - CanvasProperty<Float> progress, RuntimeShader shader) { + CanvasProperty<Float> progress, CanvasProperty<Float> turbulencePhase, + RuntimeShader shader) { nDrawRipple(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(), radius.getNativeContainer(), paint.getNativeContainer(), - progress.getNativeContainer(), shader.getNativeShaderBuilder()); + progress.getNativeContainer(), turbulencePhase.getNativeContainer(), + shader.getNativeShaderBuilder()); } /** @@ -290,7 +292,7 @@ public final class RecordingCanvas extends BaseRecordingCanvas { long propCy, long propRadius, long propPaint); @CriticalNative private static native void nDrawRipple(long renderer, long propCx, long propCy, long propRadius, - long propPaint, long propProgress, long runtimeEffect); + long propPaint, long propProgress, long turbulencePhase, long runtimeEffect); @CriticalNative private static native void nDrawRoundRect(long renderer, long propLeft, long propTop, long propRight, long propBottom, long propRx, long propRy, long propPaint); diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java index c317831eb816..1cd4cf115180 100644 --- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java +++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java @@ -40,6 +40,8 @@ public final class RippleAnimationSession { private static final String TAG = "RippleAnimationSession"; private static final int ENTER_ANIM_DURATION = 450; private static final int EXIT_ANIM_DURATION = 300; + private static final long NOISE_ANIMATION_DURATION = 7000; + private static final long MAX_NOISE_PHASE = NOISE_ANIMATION_DURATION / 120; private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); @@ -49,7 +51,7 @@ public final class RippleAnimationSession { private Runnable mOnUpdate; private long mStartTime; private boolean mForceSoftware; - private boolean mAnimateSparkle; + private Animator mLoopAnimation; RippleAnimationSession(@NonNull AnimationProperties<Float, Paint> properties, boolean forceSoftware) { @@ -88,16 +90,6 @@ public final class RippleAnimationSession { return this; } - public boolean shouldAnimateSparkle() { - return mAnimateSparkle && ValueAnimator.getDurationScale() > 0; - } - - public float getSparklePhase() { - final long now = AnimationUtils.currentAnimationTimeMillis(); - final long elapsed = now - mStartTime; - return (float) elapsed / 800; - } - private boolean isHwAccelerated(Canvas canvas) { return canvas.isHardwareAccelerated() && !mForceSoftware; } @@ -114,7 +106,7 @@ public final class RippleAnimationSession { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - mAnimateSparkle = false; + if (mLoopAnimation != null) mLoopAnimation.cancel(); Consumer<RippleAnimationSession> onEnd = mOnSessionEnd; if (onEnd != null) onEnd.accept(RippleAnimationSession.this); } @@ -148,7 +140,7 @@ public final class RippleAnimationSession { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - mAnimateSparkle = false; + if (mLoopAnimation != null) mLoopAnimation.cancel(); Consumer<RippleAnimationSession> onEnd = mOnSessionEnd; if (onEnd != null) onEnd.accept(RippleAnimationSession.this); } @@ -167,24 +159,42 @@ public final class RippleAnimationSession { RenderNodeAnimator expand = new RenderNodeAnimator(props.getProgress(), .5f); expand.setTarget(canvas); - startAnimation(expand); + RenderNodeAnimator loop = new RenderNodeAnimator(props.getNoisePhase(), MAX_NOISE_PHASE); + loop.setTarget(canvas); + startAnimation(expand, loop); } - private void startAnimation(Animator expand) { + private void startAnimation(Animator expand, Animator loop) { expand.setDuration(ENTER_ANIM_DURATION); expand.addListener(new AnimatorListener(this)); expand.setInterpolator(FAST_OUT_SLOW_IN); expand.start(); - mAnimateSparkle = true; + loop.setDuration(NOISE_ANIMATION_DURATION); + loop.addListener(new AnimatorListener(this) { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mLoopAnimation = null; + } + }); + loop.setInterpolator(LINEAR_INTERPOLATOR); + loop.start(); + if (mLoopAnimation != null) mLoopAnimation.cancel(); + mLoopAnimation = loop; } private void enterSoftware() { ValueAnimator expand = ValueAnimator.ofFloat(0f, 0.5f); expand.addUpdateListener(updatedAnimation -> { notifyUpdate(); - mProperties.getShader().setProgress((Float) expand.getAnimatedValue()); + mProperties.getShader().setProgress((float) expand.getAnimatedValue()); }); - startAnimation(expand); + ValueAnimator loop = ValueAnimator.ofFloat(0f, MAX_NOISE_PHASE); + loop.addUpdateListener(updatedAnimation -> { + notifyUpdate(); + mProperties.getShader().setNoisePhase((float) loop.getAnimatedValue()); + }); + startAnimation(expand, loop); } @NonNull AnimationProperties<Float, Paint> getProperties() { @@ -198,6 +208,7 @@ public final class RippleAnimationSession { CanvasProperty.createFloat(mProperties.getX()), CanvasProperty.createFloat(mProperties.getY()), CanvasProperty.createFloat(mProperties.getMaxRadius()), + CanvasProperty.createFloat(mProperties.getNoisePhase()), CanvasProperty.createPaint(mProperties.getPaint()), CanvasProperty.createFloat(mProperties.getProgress()), mProperties.getShader()); @@ -236,16 +247,18 @@ public final class RippleAnimationSession { static class AnimationProperties<FloatType, PaintType> { private final FloatType mProgress; private final FloatType mMaxRadius; + private final FloatType mNoisePhase; private final PaintType mPaint; private final RippleShader mShader; private FloatType mX; private FloatType mY; - AnimationProperties(FloatType x, FloatType y, FloatType maxRadius, + AnimationProperties(FloatType x, FloatType y, FloatType maxRadius, FloatType noisePhase, PaintType paint, FloatType progress, RippleShader shader) { mY = y; mX = x; mMaxRadius = maxRadius; + mNoisePhase = noisePhase; mPaint = paint; mShader = shader; mProgress = progress; @@ -279,5 +292,9 @@ public final class RippleAnimationSession { RippleShader getShader() { return mShader; } + + FloatType getNoisePhase() { + return mNoisePhase; + } } } diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 086533263634..c972a24fb04b 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -858,15 +858,6 @@ public class RippleDrawable extends LayerDrawable { } for (int i = 0; i < mRunningAnimations.size(); i++) { RippleAnimationSession s = mRunningAnimations.get(i); - if (s.shouldAnimateSparkle()) { - final float phase = s.getSparklePhase(); - if (useCanvasProps) { - s.getCanvasProperties().getShader().setNoisePhase(phase); - } else { - s.getProperties().getShader().setNoisePhase(phase); - } - invalidateSelf(); - } if (useCanvasProps) { RippleAnimationSession.AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> @@ -883,7 +874,7 @@ public class RippleDrawable extends LayerDrawable { yProp = p.getY(); } can.drawRipple(xProp, yProp, p.getMaxRadius(), p.getPaint(), - p.getProgress(), p.getShader()); + p.getProgress(), p.getNoisePhase(), p.getShader()); } else { RippleAnimationSession.AnimationProperties<Float, Paint> p = s.getProperties(); @@ -953,7 +944,7 @@ public class RippleDrawable extends LayerDrawable { shader.setRadius(radius); shader.setProgress(.0f); properties = new RippleAnimationSession.AnimationProperties<>( - cx, cy, radius, p, 0f, shader); + cx, cy, radius, 0f, p, 0f, shader); if (mMaskShader == null) { shader.setShader(null); } else { diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java index 4608d0276b49..c1c6afceadd9 100644 --- a/graphics/java/android/graphics/drawable/RippleShader.java +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -167,6 +167,9 @@ final class RippleShader extends RuntimeShader { final float turbulencePhase = (float) ((mProgress + mNoisePhase * 0.333f) * 5f * Math.PI); setUniform("in_turbulencePhase", turbulencePhase); + // + // Keep in sync with: frameworks/base/libs/hwui/pipeline/skia/AnimatedDrawables.h + // final float scale = 1.5f; setUniform("in_tCircle1", new float[]{ (float) (scale * 0.5 + (turbulencePhase * 0.01 * Math.cos(scale * 0.55))), diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index f3baad7a02e4..602cd5d6daee 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -43,10 +43,10 @@ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Superior 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string> - <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utilizar el modo una mano"</string> + <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usar Modo una mano"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o toca cualquier zona que haya encima de la aplicación"</string> - <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo una mano"</string> - <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Salir del modo una mano"</string> + <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar Modo una mano"</string> + <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Salir del Modo una mano"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Ajustes de las burbujas de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú adicional"</string> <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Volver a añadir a la pila"</string> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index fed3ea9b53f9..a17f543278f7 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -62,10 +62,10 @@ <string name="bubbles_user_education_title" msgid="2112319053732691899">"گپ بااستفاده از حبابکها"</string> <string name="bubbles_user_education_description" msgid="4215862563054175407">"مکالمههای جدید بهصورت نمادهای شناور یا حبابکها نشان داده میشوند. برای باز کردن حبابکها ضربه بزنید. برای جابهجایی، آن را بکشید."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"کنترل حبابکها در هرزمانی"</string> - <string name="bubbles_user_education_manage" msgid="3460756219946517198">"برای خاموش کردن «حبابکها» از این برنامه، روی «مدیریت» ضربه بزنید"</string> + <string name="bubbles_user_education_manage" msgid="3460756219946517198">"برای خاموش کردن حبابکها از این برنامه، روی «مدیریت» ضربه بزنید"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"متوجهام"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"هیچ حبابک جدیدی وجود ندارد"</string> - <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"حبابکها اخیر و حبابکها ردشده اینجا ظاهر خواهند شد"</string> + <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"حبابکهای اخیر و حبابکهای ردشده اینجا ظاهر خواهند شد"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"حباب"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"مدیریت"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"حبابک رد شد."</string> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index a9693865e2c4..b2c005544c32 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -67,7 +67,7 @@ <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हाल ही के बबल्स मौजूद नहीं हैं"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हाल ही के बबल्स और हटाए गए बबल्स यहां दिखेंगे"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> - <string name="manage_bubbles_text" msgid="7730624269650594419">"प्रबंधित करें"</string> + <string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल खारिज किया गया."</string> <string name="restart_button_description" msgid="5887656107651190519">"इस ऐप्लिकेशन को रीस्टार्ट करने और फ़ुल स्क्रीन पर देखने के लिए टैप करें."</string> <string name="got_it" msgid="4428750913636945527">"ठीक है"</string> diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml index 8ca54e0a5473..ef98a9c41cf2 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml @@ -19,6 +19,6 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"תמונה בתוך תמונה"</string> <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(תוכנית ללא כותרת)"</string> - <string name="pip_close" msgid="9135220303720555525">"סגור PIP"</string> + <string name="pip_close" msgid="9135220303720555525">"סגירת PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"מסך מלא"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 530d40a79ca3..0c64c7639327 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -62,7 +62,7 @@ <string name="bubbles_user_education_title" msgid="2112319053732691899">"Калкып чыкма билдирмелер аркылуу маектешүү"</string> <string name="bubbles_user_education_description" msgid="4215862563054175407">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер түрүндө көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн таптап коюңуз. Жылдыруу үчүн сүйрөңүз."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Калкып чыкма билдирмелерди каалаган убакта көзөмөлдөңүз"</string> - <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Бул колдонмодогу калкып чыкма билдирмелерди өчүрүү үчүн, \"Башкарууну\" басыңыз"</string> + <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Бул колдонмодогу калкып чыкма билдирмелерди өчүрүү үчүн \"Башкарууну\" басыңыз"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Түшүндүм"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Азырынча эч нерсе жок"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Акыркы жана жабылган калкып чыкма билдирмелер ушул жерде көрүнөт"</string> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index 882ac374fde9..dfa364a763a8 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -30,7 +30,7 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"आकार बदल्नुहोस्"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"एप विभाजित स्क्रिनमा काम नगर्न सक्छ।"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अनुप्रयोगले विभाजित-स्क्रिनलाई समर्थन गर्दैन।"</string> - <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो अनुप्रयोगले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string> + <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"अनुप्रयोगले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"</string> <string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रिन छुट्याउने"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"बायाँ भाग फुल स्क्रिन"</string> diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 6698a01dc159..a138fee32550 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">300</integer> + <integer name="config_one_handed_translate_animation_duration">800</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/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java index 125e322974bf..25dd3ca57b92 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java @@ -22,8 +22,7 @@ import android.annotation.IntDef; import android.content.Context; import android.graphics.Rect; import android.view.SurfaceControl; -import android.view.animation.Interpolator; -import android.view.animation.OvershootInterpolator; +import android.view.animation.BaseInterpolator; import android.window.WindowContainerToken; import androidx.annotation.VisibleForTesting; @@ -54,7 +53,7 @@ public class OneHandedAnimationController { public @interface TransitionDirection { } - private final Interpolator mOvershootInterpolator; + private final OneHandedInterpolator mInterpolator; private final OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper; private final HashMap<WindowContainerToken, OneHandedTransitionAnimator> mAnimatorMap = new HashMap<>(); @@ -64,7 +63,7 @@ public class OneHandedAnimationController { */ public OneHandedAnimationController(Context context) { mSurfaceTransactionHelper = new OneHandedSurfaceTransactionHelper(context); - mOvershootInterpolator = new OvershootInterpolator(); + mInterpolator = new OneHandedInterpolator(); } @SuppressWarnings("unchecked") @@ -102,7 +101,7 @@ public class OneHandedAnimationController { OneHandedTransitionAnimator setupOneHandedTransitionAnimator( OneHandedTransitionAnimator animator) { animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); - animator.setInterpolator(mOvershootInterpolator); + animator.setInterpolator(mInterpolator); animator.setFloatValues(FRACTION_START, FRACTION_END); return animator; } @@ -112,6 +111,8 @@ public class OneHandedAnimationController { * * @param <T> Type of property to animate, either offset (float) */ + // TODO: Refactoring to use SpringAnimation and DynamicAnimation instead of using ValueAnimator + // to implement One-Handed transition animation. (b/185129031) public abstract static class OneHandedTransitionAnimator<T> extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { @@ -297,4 +298,15 @@ public class OneHandedAnimationController { }; } } + + /** + * An Interpolator for One-Handed transition animation. + */ + public class OneHandedInterpolator extends BaseInterpolator { + @Override + public float getInterpolation(float input) { + return (float) (Math.pow(2, -10 * input) * Math.sin(((input - 4.0f) / 4.0f) + * (2.0f * Math.PI) / 4.0f) + 1); + } + } } 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 19098fdd5220..0a86ad835d14 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 @@ -343,7 +343,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController> mOneHandedAccessibilityUtil.getOneHandedStartDescription()); mDisplayAreaOrganizer.scheduleOffset(0, yOffSet); mTimeoutHandler.resetTimer(); - mOneHandedUiEventLogger.writeEvent( OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_IN); } @@ -351,12 +350,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController> @VisibleForTesting void stopOneHanded() { - if (mDisplayAreaOrganizer.isInOneHanded()) { - mOneHandedAccessibilityUtil.announcementForScreenReader( - mOneHandedAccessibilityUtil.getOneHandedStopDescription()); - mDisplayAreaOrganizer.scheduleOffset(0, 0); - mTimeoutHandler.removeTimer(); - } + stopOneHanded(OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT); } private void stopOneHanded(int uiEvent) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java index d4f229cb3e09..3af0ff0dfb36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java @@ -216,7 +216,7 @@ public class PipMediaController { } ArrayList<RemoteAction> mediaActions = new ArrayList<>(); - boolean isPlaying = mMediaController.getPlaybackState().isActive(); + boolean isPlaying = mMediaController.getPlaybackState().isActiveState(); long actions = mMediaController.getPlaybackState().getActions(); // Prev action diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt index e50bde2eaeaa..6494f89997e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt @@ -23,7 +23,7 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.LAUNCHER_TITLE +import com.android.server.wm.flicker.HOME_WINDOW_TITLE import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible @@ -62,7 +62,7 @@ class EnterSplitScreenDockActivity( override val ignoredWindows: List<String> get() = listOf(LAUNCHER_PACKAGE_NAME, LIVE_WALLPAPER_PACKAGE_NAME, splitScreenApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, *LAUNCHER_TITLE) + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, *HOME_WINDOW_TITLE) @FlakyTest(bugId = 169271943) @Test diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 4c4a15295690..3056e977f841 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -820,10 +820,11 @@ void SkiaCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint, uirenderer::CanvasPropertyPrimitive* progress, + uirenderer::CanvasPropertyPrimitive* turbulencePhase, const SkRuntimeShaderBuilder& effectBuilder) { sk_sp<uirenderer::skiapipeline::AnimatedRipple> drawable( new uirenderer::skiapipeline::AnimatedRipple(x, y, radius, paint, progress, - effectBuilder)); + turbulencePhase, effectBuilder)); mCanvas->drawDrawable(drawable.get()); } diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index e0a0be54fb39..995f00c6aa8d 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -153,6 +153,7 @@ public: uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint, uirenderer::CanvasPropertyPrimitive* progress, + uirenderer::CanvasPropertyPrimitive* turbulencePhase, const SkRuntimeShaderBuilder& effectBuilder) override; virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override; diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h index 855cd0d6436b..173f3948dd0e 100644 --- a/libs/hwui/canvas/CanvasOps.h +++ b/libs/hwui/canvas/CanvasOps.h @@ -152,32 +152,66 @@ struct CanvasOp<CanvasOpType::DrawRippleProperty> { sp<uirenderer::CanvasPropertyPrimitive> radius; sp<uirenderer::CanvasPropertyPaint> paint; sp<uirenderer::CanvasPropertyPrimitive> progress; + sp<uirenderer::CanvasPropertyPrimitive> turbulencePhase; sk_sp<SkRuntimeEffect> effect; + const float PI = 3.1415926535897932384626; + const float PI_ROTATE_RIGHT = PI * 0.0078125; + const float PI_ROTATE_LEFT = PI * -0.0078125; + const float SCALE = 1.5; + const float CIRCLE_X_1 = 0.01 * cos(SCALE * 0.55); + const float CIRCLE_Y_1 = 0.01 * sin(SCALE * 0.55); + const float CIRCLE_X_2 = -0.0066 * cos(SCALE * 0.45); + const float CIRCLE_Y_2 = -0.0066 * sin(SCALE * 0.45); + const float CIRCLE_X_3 = -0.0066 * cos(SCALE * 0.35); + const float CIRCLE_Y_3 = -0.0066 * sin(SCALE * 0.35); + void draw(SkCanvas* canvas) const { SkRuntimeShaderBuilder runtimeEffectBuilder(effect); - SkRuntimeShaderBuilder::BuilderUniform center = runtimeEffectBuilder.uniform("in_origin"); - if (center.fVar != nullptr) { - center = SkV2{x->value, y->value}; - } - - SkRuntimeShaderBuilder::BuilderUniform radiusU = - runtimeEffectBuilder.uniform("in_radius"); - if (radiusU.fVar != nullptr) { - radiusU = radius->value; - } - - SkRuntimeShaderBuilder::BuilderUniform progressU = - runtimeEffectBuilder.uniform("in_progress"); - if (progressU.fVar != nullptr) { - progressU = progress->value; - } + setUniform2f(runtimeEffectBuilder, "in_origin", x->value, y->value); + setUniform(runtimeEffectBuilder, "in_radius", radius); + setUniform(runtimeEffectBuilder, "in_progress", progress); + setUniform(runtimeEffectBuilder, "in_turbulencePhase", turbulencePhase); + + // + // Keep in sync with: + // frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java + // + const float turbulence = turbulencePhase->value; + setUniform2f(runtimeEffectBuilder, "in_tCircle1", SCALE * 0.5 + (turbulence * CIRCLE_X_1), + SCALE * 0.5 + (turbulence * CIRCLE_Y_1)); + setUniform2f(runtimeEffectBuilder, "in_tCircle2", SCALE * 0.2 + (turbulence * CIRCLE_X_2), + SCALE * 0.2 + (turbulence * CIRCLE_Y_2)); + setUniform2f(runtimeEffectBuilder, "in_tCircle3", SCALE + (turbulence * CIRCLE_X_3), + SCALE + (turbulence * CIRCLE_Y_3)); + const float rotation1 = turbulence * PI_ROTATE_RIGHT + 1.7 * PI; + setUniform2f(runtimeEffectBuilder, "in_tRotation1", cos(rotation1), sin(rotation1)); + const float rotation2 = turbulence * PI_ROTATE_LEFT + 2 * PI; + setUniform2f(runtimeEffectBuilder, "in_tRotation2", cos(rotation2), sin(rotation2)); + const float rotation3 = turbulence * PI_ROTATE_RIGHT + 2.75 * PI; + setUniform2f(runtimeEffectBuilder, "in_tRotation3", cos(rotation3), sin(rotation3)); SkPaint paintMod = paint->value; paintMod.setShader(runtimeEffectBuilder.makeShader(nullptr, false)); canvas->drawCircle(x->value, y->value, radius->value, paintMod); } + + void setUniform(SkRuntimeShaderBuilder& effect, std::string name, + sp<uirenderer::CanvasPropertyPrimitive> property) const { + SkRuntimeShaderBuilder::BuilderUniform uniform = effect.uniform(name.c_str()); + if (uniform.fVar != nullptr) { + uniform = property->value; + } + } + + void setUniform2f(SkRuntimeShaderBuilder effect, std::string name, float a, float b) const { + SkRuntimeShaderBuilder::BuilderUniform uniform = effect.uniform(name.c_str()); + if (uniform.fVar != nullptr) { + uniform = SkV2{a, b}; + } + } + ASSERT_DRAWABLE() }; diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index c1feb767b459..837b055a8f42 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -146,6 +146,7 @@ public: uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint, uirenderer::CanvasPropertyPrimitive* progress, + uirenderer::CanvasPropertyPrimitive* turbulencePhase, const SkRuntimeShaderBuilder& effectBuilder) = 0; virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) = 0; diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp index 855d56ee2e55..eb5a88ad7ae8 100644 --- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp +++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp @@ -141,20 +141,22 @@ static void android_view_DisplayListCanvas_drawCircleProps(CRITICAL_JNI_PARAMS_C canvas->drawCircle(xProp, yProp, radiusProp, paintProp); } -static void android_view_DisplayListCanvas_drawRippleProps(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, - jlong xPropPtr, jlong yPropPtr, - jlong radiusPropPtr, jlong paintPropPtr, - jlong progressPropPtr, - jlong builderPtr) { +static void android_view_DisplayListCanvas_drawRippleProps( + CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong xPropPtr, jlong yPropPtr, + jlong radiusPropPtr, jlong paintPropPtr, jlong progressPropPtr, jlong turbulencePhasePtr, + jlong builderPtr) { Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr); CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr); CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr); + CanvasPropertyPrimitive* turbulencePhaseProp = + reinterpret_cast<CanvasPropertyPrimitive*>(turbulencePhasePtr); CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr); CanvasPropertyPrimitive* progressProp = reinterpret_cast<CanvasPropertyPrimitive*>(progressPropPtr); SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(builderPtr); - canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, *builder); + canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, turbulencePhaseProp, + *builder); } static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jint functor) { @@ -169,19 +171,22 @@ static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAM const char* const kClassPathName = "android/graphics/RecordingCanvas"; static JNINativeMethod gMethods[] = { - // ------------ @CriticalNative -------------- - { "nCreateDisplayListCanvas", "(JII)J", (void*) android_view_DisplayListCanvas_createDisplayListCanvas }, - { "nResetDisplayListCanvas", "(JJII)V", (void*) android_view_DisplayListCanvas_resetDisplayListCanvas }, - { "nGetMaximumTextureWidth", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureSize }, - { "nGetMaximumTextureHeight", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureSize }, - { "nEnableZ", "(JZ)V", (void*) android_view_DisplayListCanvas_enableZ }, - { "nFinishRecording", "(JJ)V", (void*) android_view_DisplayListCanvas_finishRecording }, - { "nDrawRenderNode", "(JJ)V", (void*) android_view_DisplayListCanvas_drawRenderNode }, - { "nDrawTextureLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawTextureLayer }, - { "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps }, - { "nDrawRoundRect", "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps }, - { "nDrawWebViewFunctor", "(JI)V", (void*) android_view_DisplayListCanvas_drawWebViewFunctor }, - { "nDrawRipple", "(JJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRippleProps }, + // ------------ @CriticalNative -------------- + {"nCreateDisplayListCanvas", "(JII)J", + (void*)android_view_DisplayListCanvas_createDisplayListCanvas}, + {"nResetDisplayListCanvas", "(JJII)V", + (void*)android_view_DisplayListCanvas_resetDisplayListCanvas}, + {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize}, + {"nGetMaximumTextureHeight", "()I", + (void*)android_view_DisplayListCanvas_getMaxTextureSize}, + {"nEnableZ", "(JZ)V", (void*)android_view_DisplayListCanvas_enableZ}, + {"nFinishRecording", "(JJ)V", (void*)android_view_DisplayListCanvas_finishRecording}, + {"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode}, + {"nDrawTextureLayer", "(JJ)V", (void*)android_view_DisplayListCanvas_drawTextureLayer}, + {"nDrawCircle", "(JJJJJ)V", (void*)android_view_DisplayListCanvas_drawCircleProps}, + {"nDrawRoundRect", "(JJJJJJJJ)V", (void*)android_view_DisplayListCanvas_drawRoundRectProps}, + {"nDrawWebViewFunctor", "(JI)V", (void*)android_view_DisplayListCanvas_drawWebViewFunctor}, + {"nDrawRipple", "(JJJJJJJJ)V", (void*)android_view_DisplayListCanvas_drawRippleProps}, }; int register_android_view_DisplayListCanvas(JNIEnv* env) { diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h index 78591450f10a..7d65be1a76e6 100644 --- a/libs/hwui/pipeline/skia/AnimatedDrawables.h +++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h @@ -19,6 +19,7 @@ #include <SkCanvas.h> #include <SkDrawable.h> #include <SkRuntimeEffect.h> +#include <math.h> #include <utils/RefBase.h> #include "CanvasProperty.h" @@ -61,12 +62,14 @@ public: uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint, uirenderer::CanvasPropertyPrimitive* progress, + uirenderer::CanvasPropertyPrimitive* turbulencePhase, const SkRuntimeShaderBuilder& effectBuilder) : mX(x) , mY(y) , mRadius(radius) , mPaint(paint) , mProgress(progress) + , mTurbulencePhase(turbulencePhase) , mRuntimeEffectBuilder(effectBuilder) {} protected: @@ -77,22 +80,28 @@ protected: return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius); } virtual void onDraw(SkCanvas* canvas) override { - SkRuntimeShaderBuilder::BuilderUniform center = mRuntimeEffectBuilder.uniform("in_origin"); - if (center.fVar != nullptr) { - center = SkV2{mX->value, mY->value}; - } - - SkRuntimeShaderBuilder::BuilderUniform radiusU = - mRuntimeEffectBuilder.uniform("in_radius"); - if (radiusU.fVar != nullptr) { - radiusU = mRadius->value; - } - - SkRuntimeShaderBuilder::BuilderUniform progressU = - mRuntimeEffectBuilder.uniform("in_progress"); - if (progressU.fVar != nullptr) { - progressU = mProgress->value; - } + setUniform2f("in_origin", mX->value, mY->value); + setUniform("in_radius", mRadius); + setUniform("in_progress", mProgress); + setUniform("in_turbulencePhase", mTurbulencePhase); + + // + // Keep in sync with: + // frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java + // + const float turbulencePhase = mTurbulencePhase->value; + setUniform2f("in_tCircle1", SCALE * 0.5 + (turbulencePhase * CIRCLE_X_1), + SCALE * 0.5 + (turbulencePhase * CIRCLE_Y_1)); + setUniform2f("in_tCircle2", SCALE * 0.2 + (turbulencePhase * CIRCLE_X_2), + SCALE * 0.2 + (turbulencePhase * CIRCLE_Y_2)); + setUniform2f("in_tCircle3", SCALE + (turbulencePhase * CIRCLE_X_3), + SCALE + (turbulencePhase * CIRCLE_Y_3)); + const float rotation1 = turbulencePhase * PI_ROTATE_RIGHT + 1.7 * PI; + setUniform2f("in_tRotation1", cos(rotation1), sin(rotation1)); + const float rotation2 = turbulencePhase * PI_ROTATE_LEFT + 2 * PI; + setUniform2f("in_tRotation2", cos(rotation2), sin(rotation2)); + const float rotation3 = turbulencePhase * PI_ROTATE_RIGHT + 2.75 * PI; + setUniform2f("in_tRotation3", cos(rotation3), sin(rotation3)); SkPaint paint = mPaint->value; paint.setShader(mRuntimeEffectBuilder.makeShader(nullptr, false)); @@ -105,7 +114,35 @@ private: sp<uirenderer::CanvasPropertyPrimitive> mRadius; sp<uirenderer::CanvasPropertyPaint> mPaint; sp<uirenderer::CanvasPropertyPrimitive> mProgress; + sp<uirenderer::CanvasPropertyPrimitive> mTurbulencePhase; SkRuntimeShaderBuilder mRuntimeEffectBuilder; + + const float PI = 3.1415926535897932384626; + const float PI_ROTATE_RIGHT = PI * 0.0078125; + const float PI_ROTATE_LEFT = PI * -0.0078125; + const float SCALE = 1.5; + const float CIRCLE_X_1 = 0.01 * cos(SCALE * 0.55); + const float CIRCLE_Y_1 = 0.01 * sin(SCALE * 0.55); + const float CIRCLE_X_2 = -0.0066 * cos(SCALE * 0.45); + const float CIRCLE_Y_2 = -0.0066 * sin(SCALE * 0.45); + const float CIRCLE_X_3 = -0.0066 * cos(SCALE * 0.35); + const float CIRCLE_Y_3 = -0.0066 * sin(SCALE * 0.35); + + virtual void setUniform(std::string name, sp<uirenderer::CanvasPropertyPrimitive> property) { + SkRuntimeShaderBuilder::BuilderUniform uniform = + mRuntimeEffectBuilder.uniform(name.c_str()); + if (uniform.fVar != nullptr) { + uniform = property->value; + } + } + + virtual void setUniform2f(std::string name, float a, float b) { + SkRuntimeShaderBuilder::BuilderUniform uniform = + mRuntimeEffectBuilder.uniform(name.c_str()); + if (uniform.fVar != nullptr) { + uniform = SkV2{a, b}; + } + } }; class AnimatedCircle : public SkDrawable { diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index 71f533c3fc4f..ab00dd5a487c 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -24,6 +24,7 @@ #include "SkClipStack.h" #include "SkRect.h" #include "SkM44.h" +#include "utils/GLUtils.h" namespace android { namespace uirenderer { @@ -170,6 +171,8 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { setScissor(info.height, clipRegion.getBounds()); } + // WebView may swallow GL errors, so catch them here + GL_CHECKPOINT(LOW); mWebViewHandle->drawGl(info); if (clearStencilAfterFunctor) { diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 82814def6962..9e73f04c32ab 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -60,12 +60,11 @@ void SkiaRecordingCanvas::punchHole(const SkRRect& rect) { // Add the marker annotation to allow HWUI to determine where the current // clip/transformation should be applied SkVector vector = rect.getSimpleRadii(); - const int dataSize = 2; - float data[dataSize]; + float data[2]; data[0] = vector.x(); data[1] = vector.y(); mRecorder.drawAnnotation(rect.rect(), HOLE_PUNCH_ANNOTATION.c_str(), - SkData::MakeWithCopy(data, dataSize)); + SkData::MakeWithCopy(data, 2 * sizeof(float))); // Clear the current rect within the layer itself SkPaint paint = SkPaint(); @@ -115,9 +114,10 @@ void SkiaRecordingCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint, uirenderer::CanvasPropertyPrimitive* progress, + uirenderer::CanvasPropertyPrimitive* turbulencePhase, const SkRuntimeShaderBuilder& effectBuilder) { drawDrawable(mDisplayList->allocateDrawable<AnimatedRipple>(x, y, radius, paint, progress, - effectBuilder)); + turbulencePhase, effectBuilder)); } void SkiaRecordingCanvas::enableZ(bool enableZ) { diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 06f2a27d22a5..4deb3b9c47c8 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -75,6 +75,7 @@ public: uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint, uirenderer::CanvasPropertyPrimitive* progress, + uirenderer::CanvasPropertyPrimitive* turbulencePhase, const SkRuntimeShaderBuilder& effectBuilder) override; virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp index 6bfbb0d270b7..a6e4c4cf9ca7 100644 --- a/libs/hwui/pipeline/skia/TransformCanvas.cpp +++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp @@ -22,7 +22,7 @@ using namespace android::uirenderer::skiapipeline; void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkData* value) { if (HOLE_PUNCH_ANNOTATION == key) { - auto* rectParams = static_cast<const float*>(value->data()); + auto* rectParams = reinterpret_cast<const float*>(value->data()); float radiusX = rectParams[0]; float radiusY = rectParams[1]; SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY); diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 9eacc74843f9..e7d30ebba4b1 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -19,6 +19,7 @@ import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -492,26 +493,15 @@ public final class PlaybackState implements Parcelable { /** * Returns whether this is considered as an active playback state. - * <p> - * The playback state is considered as an active if the state is one of the following: - * <ul> - * <li>{@link #STATE_BUFFERING}</li> - * <li>{@link #STATE_CONNECTING}</li> - * <li>{@link #STATE_FAST_FORWARDING}</li> - * <li>{@link #STATE_PLAYING}</li> - * <li>{@link #STATE_REWINDING}</li> - * <li>{@link #STATE_SKIPPING_TO_NEXT}</li> - * <li>{@link #STATE_SKIPPING_TO_PREVIOUS}</li> - * <li>{@link #STATE_SKIPPING_TO_QUEUE_ITEM}</li> - * </ul> + * @hide */ - public boolean isActive() { + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public boolean isActiveState() { switch (mState) { case PlaybackState.STATE_FAST_FORWARDING: case PlaybackState.STATE_REWINDING: case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: case PlaybackState.STATE_SKIPPING_TO_NEXT: - case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM: case PlaybackState.STATE_BUFFERING: case PlaybackState.STATE_CONNECTING: case PlaybackState.STATE_PLAYING: diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml index 83cc263abafd..07d04aa1ecc9 100644 --- a/packages/CompanionDeviceManager/res/values-fa/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml @@ -22,6 +22,6 @@ <string name="profile_name_watch" msgid="576290739483672360">"ساعت"</string> <string name="confirmation_title" msgid="8455544820286920304">"مجاز کردن <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> برای مدیریت کردن <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_summary" msgid="2059360676631420073">"این برنامه برای مدیریت <xliff:g id="PROFILE_NAME">%1$s</xliff:g> شما لازم است. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> - <string name="consent_yes" msgid="8344487259618762872">"مجاز"</string> - <string name="consent_no" msgid="2640796915611404382">"مجاز نیست"</string> + <string name="consent_yes" msgid="8344487259618762872">"مجاز بودن"</string> + <string name="consent_no" msgid="2640796915611404382">"مجاز نبودن"</string> </resources> diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp index c62402d7d4c9..bb93af90a9be 100644 --- a/packages/Connectivity/framework/Android.bp +++ b/packages/Connectivity/framework/Android.bp @@ -100,6 +100,7 @@ java_sdk_library { "//frameworks/base", // Tests using hidden APIs + "//cts/tests/netlegacy22.api", "//external/sl4a:__subpackages__", "//frameworks/base/tests/net:__subpackages__", "//frameworks/libs/net/common/testutils", diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt index 4719772075ce..9e2cd3e8a7fd 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -13,7 +13,7 @@ package android.net { method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean); diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index 2194575f20ba..3ca74756df64 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -277,7 +277,7 @@ package android.net { method @NonNull public int[] getAdministratorUids(); method @Nullable public static String getCapabilityCarrierName(int); method @Nullable public String getSsid(); - method @NonNull public java.util.Set<java.lang.Integer> getSubIds(); + method @NonNull public java.util.Set<java.lang.Integer> getSubscriptionIds(); method @NonNull public int[] getTransportTypes(); method public boolean isPrivateDnsBroken(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); @@ -308,7 +308,7 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); - method @NonNull public android.net.NetworkCapabilities.Builder setSubIds(@NonNull java.util.Set<java.lang.Integer>); + method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>); method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); } @@ -338,7 +338,7 @@ package android.net { public static class NetworkRequest.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); - method @NonNull public android.net.NetworkRequest.Builder setSubIds(@NonNull java.util.Set<java.lang.Integer>); + method @NonNull public android.net.NetworkRequest.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>); } public final class NetworkScore implements android.os.Parcelable { diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java index 92a792b78410..a2e218dcbb4b 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java @@ -68,5 +68,11 @@ public final class ConnectivityFrameworkInitializer { return cm.startOrGetTestNetworkManager(); } ); + + SystemServiceRegistry.registerContextAwareService( + DnsResolverServiceManager.DNS_RESOLVER_SERVICE, + DnsResolverServiceManager.class, + (context, serviceBinder) -> new DnsResolverServiceManager(serviceBinder) + ); } } diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index c4a0d69d3a8a..96f2de6f76bd 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -5362,10 +5362,10 @@ public class ConnectivityManager { * {@link #unregisterNetworkCallback(NetworkCallback)}. * * @param request {@link NetworkRequest} describing this request. - * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. - * If null, the callback is invoked on the default internal Handler. * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * If null, the callback is invoked on the default internal Handler. * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. * @throws SecurityException if missing the appropriate permissions. * @throws RuntimeException if the app already has too many callbacks registered. @@ -5380,7 +5380,8 @@ public class ConnectivityManager { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK }) public void requestBackgroundNetwork(@NonNull NetworkRequest request, - @NonNull Handler handler, @NonNull NetworkCallback networkCallback) { + @NonNull NetworkCallback networkCallback, + @SuppressLint("ListenerLast") @NonNull Handler handler) { final NetworkCapabilities nc = request.networkCapabilities; sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST, TYPE_NONE, new CallbackHandler(handler)); diff --git a/packages/Connectivity/framework/src/android/net/DnsResolverServiceManager.java b/packages/Connectivity/framework/src/android/net/DnsResolverServiceManager.java new file mode 100644 index 000000000000..79009e8d629e --- /dev/null +++ b/packages/Connectivity/framework/src/android/net/DnsResolverServiceManager.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net; + +import android.annotation.NonNull; +import android.os.IBinder; + +/** + * Provides a way to obtain the DnsResolver binder objects. + * + * @hide + */ +public class DnsResolverServiceManager { + /** Service name for the DNS resolver. Keep in sync with DnsResolverService.h */ + public static final String DNS_RESOLVER_SERVICE = "dnsresolver"; + + private final IBinder mResolver; + + DnsResolverServiceManager(IBinder resolver) { + mResolver = resolver; + } + + /** + * Get an {@link IBinder} representing the DnsResolver stable AIDL interface + * + * @return {@link android.net.IDnsResolver} IBinder. + */ + @NonNull + public IBinder getService() { + return mResolver; + } +} diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java index c4277c3c4f97..775c88f20d56 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -1749,7 +1749,7 @@ public final class NetworkCapabilities implements Parcelable { combineSSIDs(nc); combineRequestor(nc); combineAdministratorUids(nc); - combineSubIds(nc); + combineSubscriptionIds(nc); } /** @@ -1771,7 +1771,7 @@ public final class NetworkCapabilities implements Parcelable { && (onlyImmutable || satisfiedByUids(nc)) && (onlyImmutable || satisfiedBySSID(nc)) && (onlyImmutable || satisfiedByRequestor(nc)) - && (onlyImmutable || satisfiedBySubIds(nc))); + && (onlyImmutable || satisfiedBySubscriptionIds(nc))); } /** @@ -1868,7 +1868,7 @@ public final class NetworkCapabilities implements Parcelable { && equalsPrivateDnsBroken(that) && equalsRequestor(that) && equalsAdministratorUids(that) - && equalsSubIds(that); + && equalsSubscriptionIds(that); } @Override @@ -2346,7 +2346,7 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ @NonNull - public NetworkCapabilities setSubIds(@NonNull Set<Integer> subIds) { + public NetworkCapabilities setSubscriptionIds(@NonNull Set<Integer> subIds) { mSubIds = new ArraySet(Objects.requireNonNull(subIds)); return this; } @@ -2362,14 +2362,14 @@ public final class NetworkCapabilities implements Parcelable { */ @NonNull @SystemApi - public Set<Integer> getSubIds() { + public Set<Integer> getSubscriptionIds() { return new ArraySet<>(mSubIds); } /** * Tests if the subscription ID set of this network is the same as that of the passed one. */ - private boolean equalsSubIds(@NonNull NetworkCapabilities nc) { + private boolean equalsSubscriptionIds(@NonNull NetworkCapabilities nc) { return Objects.equals(mSubIds, nc.mSubIds); } @@ -2378,7 +2378,7 @@ public final class NetworkCapabilities implements Parcelable { * If specified in the request, the passed one need to have at least one subId and at least * one of them needs to be in the request set. */ - private boolean satisfiedBySubIds(@NonNull NetworkCapabilities nc) { + private boolean satisfiedBySubscriptionIds(@NonNull NetworkCapabilities nc) { if (mSubIds.isEmpty()) return true; if (nc.mSubIds.isEmpty()) return false; for (final Integer subId : nc.mSubIds) { @@ -2395,7 +2395,7 @@ public final class NetworkCapabilities implements Parcelable { * <p>If both subscription IDs are not equal, they belong to different subscription * (or no subscription). In this case, it would not make sense to add them together. */ - private void combineSubIds(@NonNull NetworkCapabilities nc) { + private void combineSubscriptionIds(@NonNull NetworkCapabilities nc) { if (!Objects.equals(mSubIds, nc.mSubIds)) { throw new IllegalStateException("Can't combine two subscription ID sets"); } @@ -2737,8 +2737,8 @@ public final class NetworkCapabilities implements Parcelable { */ @NonNull @SystemApi - public Builder setSubIds(@NonNull final Set<Integer> subIds) { - mCaps.setSubIds(subIds); + public Builder setSubscriptionIds(@NonNull final Set<Integer> subIds) { + mCaps.setSubscriptionIds(subIds); return this; } diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java index 78e101192467..90aac0ec92d1 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java +++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java @@ -508,8 +508,8 @@ public class NetworkRequest implements Parcelable { */ @NonNull @SystemApi - public Builder setSubIds(@NonNull Set<Integer> subIds) { - mNetworkCapabilities.setSubIds(subIds); + public Builder setSubscriptionIds(@NonNull Set<Integer> subIds) { + mNetworkCapabilities.setSubscriptionIds(subIds); return this; } } diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml index a24456ea02cf..f57061a30c14 100644 --- a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml +++ b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml @@ -18,5 +18,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="enabled_by_admin" msgid="6630472777476410137">"વ્યવસ્થાપકે ચાલુ કરેલ"</string> - <string name="disabled_by_admin" msgid="4023569940620832713">"વ્યવસ્થાપકે બંધ કરેલ"</string> + <string name="disabled_by_admin" msgid="4023569940620832713">"વ્યવસ્થાપકે બંધ કરેલું"</string> </resources> diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml index 199a2d6c7079..bddf43ce6917 100644 --- a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml +++ b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml @@ -18,5 +18,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="enabled_by_admin" msgid="6630472777476410137">"Attivata dall\'amministratore"</string> - <string name="disabled_by_admin" msgid="4023569940620832713">"Disattivata dall\'amministratore"</string> + <string name="disabled_by_admin" msgid="4023569940620832713">"Opzione disattivata dall\'amministratore"</string> </resources> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index a83478469637..173e95954014 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -762,7 +762,7 @@ android:showForAllUsers="true" android:finishOnTaskLaunch="true" android:launchMode="singleInstance" - android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" + android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> 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 3363f8ea808c..03d844a34fc1 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -31,14 +31,16 @@ class ActivityLaunchAnimator(context: Context) { companion object { const val ANIMATION_DURATION = 500L const val ANIMATION_DURATION_FADE_OUT_CONTENT = 183L - const val ANIMATION_DURATION_FADE_IN_WINDOW = 216L - const val ANIMATION_DELAY_FADE_IN_WINDOW = 166L + const val ANIMATION_DURATION_FADE_IN_WINDOW = 217L + const val ANIMATION_DELAY_FADE_IN_WINDOW = 167L private const val ANIMATION_DURATION_NAV_FADE_IN = 266L private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L private const val ANIMATION_DELAY_NAV_FADE_IN = ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN private const val LAUNCH_TIMEOUT = 1000L + private val CONTENT_FADE_OUT_INTERPOLATOR = PathInterpolator(0f, 0f, 0.2f, 1f) + private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f) private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f) private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f) @@ -418,12 +420,12 @@ class ActivityLaunchAnimator(context: Context) { val contentAlphaProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) state.contentAlpha = - 1 - Interpolators.ALPHA_OUT.getInterpolation(contentAlphaProgress) + 1 - CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(contentAlphaProgress) val backgroundAlphaProgress = getProgress(linearProgress, ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW) state.backgroundAlpha = - 1 - Interpolators.ALPHA_IN.getInterpolation(backgroundAlphaProgress) + 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(backgroundAlphaProgress) applyStateToWindow(window, state) navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } 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 01ec447e5685..3da45210e8c2 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -9,6 +9,7 @@ import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.InsetDrawable import android.graphics.drawable.LayerDrawable import android.view.GhostView import android.view.View @@ -186,6 +187,10 @@ open class GhostedViewLaunchAnimatorController( return drawable } + if (drawable is InsetDrawable) { + return drawable.drawable?.let { findGradientDrawable(it) } + } + if (drawable is LayerDrawable) { for (i in 0 until drawable.numberOfLayers) { val maybeGradient = drawable.getDrawable(i) @@ -255,6 +260,11 @@ open class GhostedViewLaunchAnimatorController( } private fun setXfermode(background: Drawable, mode: PorterDuffXfermode?) { + if (background is InsetDrawable) { + background.drawable?.let { setXfermode(it, mode) } + return + } + if (background !is LayerDrawable) { background.setXfermode(mode) return @@ -323,6 +333,11 @@ open class GhostedViewLaunchAnimatorController( return } + if (drawable is InsetDrawable) { + drawable.drawable?.let { applyBackgroundRadii(it, radii) } + return + } + if (drawable !is LayerDrawable) { return } 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 00bea8da2efc..47a373ebf429 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -60,6 +60,8 @@ public interface ActivityStarter { */ void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags); void startActivity(Intent intent, boolean dismissShade); + void startActivity(Intent intent, boolean dismissShade, + @Nullable ActivityLaunchAnimator.Controller animationController); void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade); void startActivity(Intent intent, boolean dismissShade, Callback callback); void postStartActivityDismissingKeyguard(Intent intent, int delay); diff --git a/packages/SystemUI/res/drawable/ic_brightness.xml b/packages/SystemUI/res/drawable/ic_brightness.xml index f44333236a12..842af26ddfd3 100644 --- a/packages/SystemUI/res/drawable/ic_brightness.xml +++ b/packages/SystemUI/res/drawable/ic_brightness.xml @@ -1,29 +1,23 @@ <!-- -Copyright (C) 2020 The Android Open Source Project + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ 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. + --> - 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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - - <path - android:pathData="M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z" - /> - - <path - android:pathData=" M20,8.69 V4h-4.69L12,0.69L8.69,4H4v4.69L0.69,12L4,15.31V20h4.69L12,23.31L15.31,20H20v-4.69L23.31,12L20,8.69z M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S14.76,7 12,7z" - android:fillColor="#FFFFFF" /> -</vector> +<level-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Levels in drawables go from 0 to 10000 --> + <!-- "One third" of the range per icon --> + <item android:maxLevel="3333" android:drawable="@drawable/ic_brightness_low" /> + <item android:maxLevel="6666" android:drawable="@drawable/ic_brightness_medium" /> + <item android:maxLevel="10000" android:drawable="@drawable/ic_brightness_full" /> +</level-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_brightness_thumb.xml b/packages/SystemUI/res/drawable/ic_brightness_full.xml index d72198874b73..f44333236a12 100644 --- a/packages/SystemUI/res/drawable/ic_brightness_thumb.xml +++ b/packages/SystemUI/res/drawable/ic_brightness_full.xml @@ -1,5 +1,5 @@ <!-- -Copyright (C) 2017 The Android Open Source Project +Copyright (C) 2020 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,9 +21,9 @@ Copyright (C) 2017 The Android Open Source Project <path android:pathData="M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z" - android:fillColor="?android:attr/colorBackgroundFloating" /> + /> <path android:pathData=" M20,8.69 V4h-4.69L12,0.69L8.69,4H4v4.69L0.69,12L4,15.31V20h4.69L12,23.31L15.31,20H20v-4.69L23.31,12L20,8.69z M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S14.76,7 12,7z" - android:fillColor="?android:attr/colorControlActivated" /> + android:fillColor="#FFFFFF" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_brightness_low.xml b/packages/SystemUI/res/drawable/ic_brightness_low.xml new file mode 100644 index 000000000000..b463556e20d0 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_brightness_low.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM18,14.48L18,18h-3.52L12,20.48 9.52,18L6,18v-3.52L3.52,12 6,9.52L6,6h3.52L12,3.52 14.48,6L18,6v3.52L20.48,12 18,14.48zM12,9c1.65,0 3,1.35 3,3s-1.35,3 -3,3 -3,-1.35 -3,-3 1.35,-3 3,-3m0,-2c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_brightness_medium.xml b/packages/SystemUI/res/drawable/ic_brightness_medium.xml new file mode 100644 index 000000000000..80acc4d565fa --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_brightness_medium.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM18,14.48L18,18h-3.52L12,20.48 9.52,18L6,18v-3.52L3.52,12 6,9.52L6,6h3.52L12,3.52 14.48,6L18,6v3.52L20.48,12 18,14.48zM12,17c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5v10z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml b/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml index 6022206c208c..77b98711a304 100644 --- a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml +++ b/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml @@ -23,13 +23,18 @@ <item android:id="@android:id/mask"> <shape android:shape="rectangle"> <solid android:color="@android:color/white"/> + <corners android:radius="@dimen/screenshot_button_corner_radius"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <solid android:color="?attr/underSurfaceColor"/> <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> <item> <shape android:shape="rectangle"> - <stroke android:width="1dp" android:color="@color/qs_footer_action_border"/> - <solid android:color="@android:color/transparent"/> + <stroke android:width="1dp" android:color="@color/qs_footer_action_border"/> <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml new file mode 100644 index 000000000000..4e9d380af319 --- /dev/null +++ b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- dot drawable for ongoing system privacy events --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid + android:color="@color/privacy_circle"/> + <size + android:width="6dp" + android:height="6dp" + /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/people_tile_medium_empty.xml b/packages/SystemUI/res/layout/people_tile_medium_empty.xml index 7d9cbb907506..4236493dce91 100644 --- a/packages/SystemUI/res/layout/people_tile_medium_empty.xml +++ b/packages/SystemUI/res/layout/people_tile_medium_empty.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:layout_width="match_parent" android:layout_height="match_parent" @@ -22,57 +22,49 @@ <LinearLayout android:background="@drawable/people_space_tile_view_card" android:id="@+id/item" - android:orientation="vertical" + android:gravity="center" + android:paddingHorizontal="16dp" + android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> - + <ImageView + android:id="@+id/person_icon" + android:layout_marginTop="-2dp" + android:layout_marginStart="-2dp" + android:layout_width="64dp" + android:layout_height="64dp" /> + <ImageView + android:id="@+id/availability" + android:layout_marginStart="-2dp" + android:layout_width="10dp" + android:layout_height="10dp" + android:background="@drawable/circle_green_10dp" /> <LinearLayout - android:orientation="horizontal" - android:gravity="center" - android:layout_gravity="center" - android:paddingVertical="2dp" - android:paddingHorizontal="8dp" + android:orientation="vertical" + android:paddingStart="6dp" + android:gravity="top" android:layout_width="match_parent" - android:layout_height="match_parent"> - <ImageView - android:id="@+id/person_icon" - android:layout_width="64dp" - android:layout_height="64dp"/> - <ImageView - android:id="@+id/availability" - android:layout_marginStart="-2dp" - android:layout_width="10dp" - android:layout_height="10dp" - android:background="@drawable/circle_green_10dp"/> - <LinearLayout - android:orientation="vertical" - android:paddingStart="6dp" - android:gravity="top" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <TextView - android:id="@+id/name" - android:text="@string/empty_user_name" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="14sp" - android:maxLines="1" - android:ellipsize="end" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> - - <TextView - android:id="@+id/last_interaction" - android:text="@string/empty_status" - android:textColor="?android:attr/textColorSecondary" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textSize="12sp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:maxLines="3" - android:ellipsize="end"/> - </LinearLayout> + android:layout_height="wrap_content"> + <TextView + android:id="@+id/name" + android:text="@string/empty_user_name" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" + android:textColor="?android:attr/textColorPrimary" + android:textSize="14sp" + android:maxLines="1" + android:ellipsize="end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + <TextView + android:id="@+id/last_interaction" + android:text="@string/empty_status" + android:textColor="?android:attr/textColorSecondary" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" + android:textSize="12sp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="3" + android:ellipsize="end" /> </LinearLayout> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml index c9e4945a7be2..70706600e6da 100644 --- a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml +++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml @@ -97,7 +97,7 @@ android:gravity="bottom" android:layout_gravity="center_vertical" android:orientation="horizontal" - android:paddingTop="4dp" + android:paddingTop="2dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:clipToOutline="true"> diff --git a/packages/SystemUI/res/layout/people_tile_small.xml b/packages/SystemUI/res/layout/people_tile_small.xml index 34aa8e498231..7c28fc10c184 100644 --- a/packages/SystemUI/res/layout/people_tile_small.xml +++ b/packages/SystemUI/res/layout/people_tile_small.xml @@ -21,7 +21,7 @@ <LinearLayout android:id="@+id/item" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:layout_gravity="center" android:background="@drawable/people_space_tile_view_card" android:orientation="vertical" @@ -42,12 +42,12 @@ android:tint="?android:attr/colorAccent" android:layout_gravity="center" android:layout_width="18dp" - android:layout_height="22dp" - android:layout_weight="1" /> + android:layout_height="22dp" /> <TextView android:id="@+id/messages_count" android:layout_gravity="center" + android:gravity="center" android:paddingStart="8dp" android:paddingEnd="8dp" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" @@ -59,7 +59,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" - android:layout_weight="1" /> <TextView @@ -67,7 +66,6 @@ android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_weight="1" android:ellipsize="end" android:maxLines="1" android:paddingHorizontal="4dp" diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml index ae3adb82fe6f..4fcce243f58f 100644 --- a/packages/SystemUI/res/layout/remote_input.xml +++ b/packages/SystemUI/res/layout/remote_input.xml @@ -31,8 +31,7 @@ android:paddingTop="2dp" android:paddingStart="16dp" android:paddingEnd="12dp" - android:layout_marginRight="20dp" - android:layout_marginLeft="20dp" + android:layout_marginLeft="16dp" android:layout_marginTop="5dp" android:layout_marginBottom="16dp" android:layout_gravity="start|center_vertical" @@ -52,12 +51,10 @@ android:layout_gravity="center_vertical"> <ImageButton - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_gravity="center" - android:paddingLeft="10dp" - android:layout_marginBottom="12dp" - android:layout_marginEnd="12dp" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_gravity="center_horizontal|bottom" + android:layout_marginBottom="22dp" android:id="@+id/remote_input_send" android:src="@drawable/ic_send" android:contentDescription="@*android:string/ime_action_send" @@ -69,8 +66,8 @@ android:id="@+id/remote_input_progress" android:layout_width="24dp" android:layout_height="24dp" - android:layout_marginBottom="12dp" - android:layout_gravity="center_vertical" + android:layout_marginBottom="34dp" + android:layout_gravity="center_horizontal|bottom" android:visibility="invisible" android:indeterminate="true" style="?android:attr/progressBarStyleSmall" /> diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml index db892d78c556..04fe9184cf60 100644 --- a/packages/SystemUI/res/layout/rounded_corners.xml +++ b/packages/SystemUI/res/layout/rounded_corners.xml @@ -14,6 +14,8 @@ ** See the License for the specific language governing permissions and ** limitations under the License. --> + +<!-- TODO: remove this in favor of requiring top and bottom layouts --> <com.android.systemui.RegionInterceptingFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rounded_corners_default" @@ -26,6 +28,25 @@ android:layout_gravity="left|top" android:tint="#ff000000" android:src="@drawable/rounded"/> + + <FrameLayout + android:id="@+id/privacy_dot_left_container" + android:layout_height="@dimen/status_bar_height" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginLeft="8dp" + android:layout_gravity="left|top" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot_left" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + </FrameLayout> + + <ImageView android:id="@+id/right" android:layout_width="12dp" @@ -33,4 +54,22 @@ android:tint="#ff000000" android:layout_gravity="right|bottom" android:src="@drawable/rounded"/> + <FrameLayout + android:id="@+id/privacy_dot_right_container" + android:layout_height="@dimen/status_bar_height" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginRight="8dp" + android:layout_gravity="right|top" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot_right" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + + </FrameLayout> + </com.android.systemui.RegionInterceptingFrameLayout> diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml index dde1248356e0..720e47b1908c 100644 --- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml +++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml @@ -26,6 +26,24 @@ android:layout_gravity="left|bottom" android:tint="#ff000000" android:src="@drawable/rounded_corner_bottom"/> + + <FrameLayout + android:id="@+id/privacy_dot_left_container" + android:layout_height="@dimen/status_bar_height" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginLeft="0dp" + android:layout_gravity="left|bottom" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|right" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + </FrameLayout> + <ImageView android:id="@+id/right" android:layout_width="12dp" @@ -33,4 +51,21 @@ android:tint="#ff000000" android:layout_gravity="right|bottom" android:src="@drawable/rounded_corner_bottom"/> + <FrameLayout + android:id="@+id/privacy_dot_right_container" + android:layout_height="@dimen/status_bar_height" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginRight="0dp" + android:layout_gravity="right|bottom" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|left" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + </FrameLayout> + </com.android.systemui.RegionInterceptingFrameLayout> diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml index 813c97d06f57..6abe406e0ea6 100644 --- a/packages/SystemUI/res/layout/rounded_corners_top.xml +++ b/packages/SystemUI/res/layout/rounded_corners_top.xml @@ -26,6 +26,24 @@ android:layout_gravity="left|top" android:tint="#ff000000" android:src="@drawable/rounded_corner_top"/> + + <FrameLayout + android:id="@+id/privacy_dot_left_container" + android:layout_height="@*android:dimen/status_bar_height_portrait" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginLeft="0dp" + android:layout_gravity="left|top" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|right" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + </FrameLayout> + <ImageView android:id="@+id/right" android:layout_width="12dp" @@ -33,4 +51,24 @@ android:tint="#ff000000" android:layout_gravity="right|top" android:src="@drawable/rounded_corner_top"/> + + <FrameLayout + android:id="@+id/privacy_dot_right_container" + android:layout_height="@*android:dimen/status_bar_height_portrait" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginRight="0dp" + android:layout_gravity="right|top" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|left" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + + </FrameLayout> + + </com.android.systemui.RegionInterceptingFrameLayout> diff --git a/packages/SystemUI/res/layout/system_event_animation_window.xml b/packages/SystemUI/res/layout/system_event_animation_window.xml new file mode 100644 index 000000000000..c92dec9dd643 --- /dev/null +++ b/packages/SystemUI/res/layout/system_event_animation_window.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center_vertical|end" + android:paddingTop="@dimen/status_bar_padding_top" + android:paddingEnd="8dp" + > + + <ImageView + android:id="@+id/dot_view" + android:layout_width="10dp" + android:layout_height="10dp" + android:layout_gravity="center_vertical|end" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="invisible" + /> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 62ac75e33ca5..5d9c909ef4c7 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1397,6 +1397,7 @@ <dimen name="max_people_avatar_size_for_large_content">64dp</dimen> <dimen name="max_people_avatar_size">108dp</dimen> <dimen name="name_text_size_for_small">14sp</dimen> + <dimen name="name_text_size_for_medium">14sp</dimen> <dimen name="name_text_size_for_large">24sp</dimen> <dimen name="content_text_size_for_medium">12sp</dimen> <dimen name="content_text_size_for_large">14sp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java index 983b1bb63913..62d5a458d51d 100644 --- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java @@ -91,6 +91,13 @@ public class ActivityStarterDelegate implements ActivityStarter { } @Override + public void startActivity(Intent intent, boolean dismissShade, + @Nullable ActivityLaunchAnimator.Controller animationController) { + mActualStarter.ifPresent( + starter -> starter.get().startActivity(intent, dismissShade, animationController)); + } + + @Override public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) { mActualStarter.ifPresent( starter -> starter.get().startActivity(intent, onlyProvisioned, dismissShade)); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index a686fc086b40..cbfdce5d0c69 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -79,6 +79,8 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.notification.NotificationFilter; @@ -352,6 +354,8 @@ public class Dependency { @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy; @Inject Lazy<NavigationBarOverlayController> mNavbarButtonsControllerLazy; @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager; + @Inject Lazy<SystemStatusAnimationScheduler> mSystemStatusAnimationSchedulerLazy; + @Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy; @Inject public Dependency() { @@ -561,6 +565,10 @@ public class Dependency { mProviders.put(NavigationBarOverlayController.class, mNavbarButtonsControllerLazy::get); + mProviders.put(SystemStatusAnimationScheduler.class, + mSystemStatusAnimationSchedulerLazy::get); + mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get); + Dependency.setInstance(this); } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 5fa98bccfc69..d07723e475fb 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -88,6 +88,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.SecureSetting; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.settings.SecureSettings; @@ -126,6 +127,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { private DisplayManager.DisplayListener mDisplayListener; private CameraAvailabilityListener mCameraListener; private final UserTracker mUserTracker; + private final PrivacyDotViewController mDotViewController; //TODO: These are piecemeal being updated to Points for now to support non-square rounded // corners. for now it is only supposed when reading the intrinsic size from the drawables with @@ -140,6 +142,11 @@ public class ScreenDecorations extends SystemUI implements Tunable { protected View[] mOverlays; @Nullable private DisplayCutoutView[] mCutoutViews; + //TODO: + View mTopLeftDot; + View mTopRightDot; + View mBottomLeftDot; + View mBottomRightDot; private float mDensity; private WindowManager mWindowManager; private int mRotation; @@ -147,6 +154,8 @@ public class ScreenDecorations extends SystemUI implements Tunable { private Handler mHandler; private boolean mPendingRotationChange; private boolean mIsRoundedCornerMultipleRadius; + private int mStatusBarHeightPortrait; + private int mStatusBarHeightLandscape; private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback = new CameraAvailabilityListener.CameraTransitionCallback() { @@ -205,13 +214,15 @@ public class ScreenDecorations extends SystemUI implements Tunable { SecureSettings secureSettings, BroadcastDispatcher broadcastDispatcher, TunerService tunerService, - UserTracker userTracker) { + UserTracker userTracker, + PrivacyDotViewController dotViewController) { super(context); mMainHandler = handler; mSecureSettings = secureSettings; mBroadcastDispatcher = broadcastDispatcher; mTunerService = tunerService; mUserTracker = userTracker; + mDotViewController = dotViewController; } @Override @@ -222,6 +233,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { } mHandler = startHandlerThread(); mHandler.post(this::startOnScreenDecorationsThread); + mDotViewController.setUiExecutor(mHandler::post); } @VisibleForTesting @@ -286,6 +298,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { private void setupDecorations() { if (hasRoundedCorners() || shouldDrawCutout()) { + updateStatusBarHeight(); final DisplayCutout cutout = getCutout(); final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll(); int rotatedPos; @@ -298,6 +311,10 @@ public class ScreenDecorations extends SystemUI implements Tunable { removeOverlay(i); } } + // Overlays have been created, send the dots to the controller + //TODO: need a better way to do this + mDotViewController.initialize( + mTopLeftDot, mTopRightDot, mBottomLeftDot, mBottomRightDot); } else { removeAllOverlays(); } @@ -431,14 +448,21 @@ public class ScreenDecorations extends SystemUI implements Tunable { private View overlayForPosition(@BoundsPosition int pos) { switch (pos) { case BOUNDS_POSITION_TOP: - return LayoutInflater.from(mContext) + case BOUNDS_POSITION_LEFT: + View top = LayoutInflater.from(mContext) .inflate(R.layout.rounded_corners_top, null); + mTopLeftDot = top.findViewById(R.id.privacy_dot_left_container); + mTopRightDot = top.findViewById(R.id.privacy_dot_right_container); + return top; case BOUNDS_POSITION_BOTTOM: - return LayoutInflater.from(mContext) + case BOUNDS_POSITION_RIGHT: + View bottom = LayoutInflater.from(mContext) .inflate(R.layout.rounded_corners_bottom, null); + mBottomLeftDot = bottom.findViewById(R.id.privacy_dot_left_container); + mBottomRightDot = bottom.findViewById(R.id.privacy_dot_right_container); + return bottom; default: - return LayoutInflater.from(mContext) - .inflate(R.layout.rounded_corners, null); + throw new IllegalArgumentException("Unknown bounds position"); } } @@ -575,6 +599,11 @@ public class ScreenDecorations extends SystemUI implements Tunable { View child; for (int j = 0; j < size; j++) { child = ((ViewGroup) mOverlays[i]).getChildAt(j); + if (child.getId() == R.id.privacy_dot_left_container + || child.getId() == R.id.privacy_dot_right_container) { + // Exclude privacy dot from color inversion (for now?) + continue; + } if (child instanceof ImageView) { ((ImageView) child).setImageTintList(tintList); } else if (child instanceof DisplayCutoutView) { @@ -611,10 +640,15 @@ public class ScreenDecorations extends SystemUI implements Tunable { Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(), "must call on " + mHandler.getLooper().getThread() + ", but was " + Thread.currentThread()); + + int newRotation = mContext.getDisplay().getRotation(); + if (mRotation != newRotation) { + mDotViewController.updateRotation(newRotation); + } + if (mPendingRotationChange) { return; } - int newRotation = mContext.getDisplay().getRotation(); if (newRotation != mRotation) { mRotation = newRotation; @@ -630,6 +664,14 @@ public class ScreenDecorations extends SystemUI implements Tunable { } } + private void updateStatusBarHeight() { + mStatusBarHeightLandscape = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height_landscape); + mStatusBarHeightPortrait = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height_portrait); + mDotViewController.setStatusBarHeights(mStatusBarHeightPortrait, mStatusBarHeightLandscape); + } + private void updateRoundedCornerRadii() { // We should eventually move to just using the intrinsic size of the drawables since // they should be sized to the exact pixels they want to cover. Therefore I'm purposely not diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java index 2661d8913a3f..b544599d0979 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java @@ -60,17 +60,31 @@ public interface AppOpsController { /** * Returns a copy of the list containing all the active AppOps that the controller tracks. * - * @return List of active AppOps information + * @return List of active AppOps information, without paused elements. */ List<AppOpItem> getActiveAppOps(); /** + * Returns a copy of the list containing all the active AppOps that the controller tracks. + * + * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise. + * @return List of active AppOps information + */ + List<AppOpItem> getActiveAppOps(boolean showPaused); + + /** * Returns a copy of the list containing all the active AppOps that the controller tracks, for * a given user id. * * @param userId User id to track + * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise. * * @return List of active AppOps information for that user id */ - List<AppOpItem> getActiveAppOpsForUser(int userId); + List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused); + + /** + * @return whether this controller is considering the microphone as muted. + */ + boolean isMicMuted(); } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 994401db4380..534f93ec0e47 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -241,9 +241,9 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName); if (item == null && active) { item = new AppOpItem(code, uid, packageName, mClock.elapsedRealtime()); - if (code == AppOpsManager.OP_RECORD_AUDIO) { + if (isOpMicrophone(code)) { item.setDisabled(isAnyRecordingPausedLocked(uid)); - } else if (code == AppOpsManager.OP_CAMERA) { + } else if (isOpCamera(code)) { item.setDisabled(mCameraDisabled); } mActiveItems.add(item); @@ -298,6 +298,11 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); } + @WorkerThread + public List<AppOpItem> getActiveAppOps() { + return getActiveAppOps(false); + } + /** * Returns a copy of the list containing all the active AppOps that the controller tracks. * @@ -306,8 +311,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon * @return List of active AppOps information */ @WorkerThread - public List<AppOpItem> getActiveAppOps() { - return getActiveAppOpsForUser(UserHandle.USER_ALL); + public List<AppOpItem> getActiveAppOps(boolean showPaused) { + return getActiveAppOpsForUser(UserHandle.USER_ALL, showPaused); } /** @@ -321,7 +326,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon * @return List of active AppOps information for that user id */ @WorkerThread - public List<AppOpItem> getActiveAppOpsForUser(int userId) { + public List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused) { Assert.isNotMainThread(); List<AppOpItem> list = new ArrayList<>(); synchronized (mActiveItems) { @@ -330,7 +335,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) - && isUserVisible(item.getPackageName()) && !item.isDisabled()) { + && isUserVisible(item.getPackageName()) + && (showPaused || !item.isDisabled())) { list.add(item); } } @@ -441,9 +447,9 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); boolean paused = false; - if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) { + if (isOpMicrophone(item.getCode())) { paused = isAnyRecordingPausedLocked(item.getUid()); - } else if (item.getCode() == AppOpsManager.OP_CAMERA) { + } else if (isOpCamera(item.getCode())) { paused = mCameraDisabled; } @@ -502,6 +508,19 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon }); } + @Override + public boolean isMicMuted() { + return mMicMuted; + } + + private boolean isOpCamera(int op) { + return op == AppOpsManager.OP_CAMERA || op == AppOpsManager.OP_PHONE_CALL_CAMERA; + } + + private boolean isOpMicrophone(int op) { + return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE; + } + protected class H extends Handler { H(Looper looper) { super(looper); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java index de00d50b6e36..1a94473f18d4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java @@ -17,10 +17,20 @@ package com.android.systemui.keyguard; import android.annotation.IntDef; +import android.app.IWallpaperManager; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Point; +import android.os.Bundle; import android.os.PowerManager; +import android.os.RemoteException; import android.os.Trace; +import android.util.DisplayMetrics; + +import androidx.annotation.Nullable; import com.android.systemui.Dumpable; +import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import java.io.FileDescriptor; @@ -31,7 +41,7 @@ import java.lang.annotation.RetentionPolicy; import javax.inject.Inject; /** - * Tracks the wakefulness lifecycle. + * Tracks the wakefulness lifecycle, including why we're waking or sleeping. */ @SysUISingleton public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observer> implements @@ -51,13 +61,30 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe public static final int WAKEFULNESS_AWAKE = 2; public static final int WAKEFULNESS_GOING_TO_SLEEP = 3; + private final Context mContext; + private final DisplayMetrics mDisplayMetrics; + private final IWallpaperManager mWallpaperManagerService; + private int mWakefulness = WAKEFULNESS_ASLEEP; + private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN; + + @Nullable + private Point mLastWakeOriginLocation = null; + private @PowerManager.GoToSleepReason int mLastSleepReason = PowerManager.GO_TO_SLEEP_REASON_MIN; + @Nullable + private Point mLastSleepOriginLocation = null; + @Inject - public WakefulnessLifecycle() { + public WakefulnessLifecycle( + Context context, + @Nullable IWallpaperManager wallpaperManagerService) { + mContext = context; + mDisplayMetrics = context.getResources().getDisplayMetrics(); + mWallpaperManagerService = wallpaperManagerService; } public @Wakefulness int getWakefulness() { @@ -85,6 +112,15 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe } setWakefulness(WAKEFULNESS_WAKING); mLastWakeReason = pmWakeReason; + updateLastWakeOriginLocation(); + + try { + mWallpaperManagerService.notifyWakingUp( + mLastWakeOriginLocation.x, mLastWakeOriginLocation.y, new Bundle()); + } catch (RemoteException e) { + e.printStackTrace(); + } + dispatch(Observer::onStartedWakingUp); } @@ -102,6 +138,15 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe } setWakefulness(WAKEFULNESS_GOING_TO_SLEEP); mLastSleepReason = pmSleepReason; + updateLastSleepOriginLocation(); + + try { + mWallpaperManagerService.notifyGoingToSleep( + mLastSleepOriginLocation.x, mLastSleepOriginLocation.y, new Bundle()); + } catch (RemoteException e) { + e.printStackTrace(); + } + dispatch(Observer::onStartedGoingToSleep); } @@ -124,6 +169,60 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe Trace.traceCounter(Trace.TRACE_TAG_APP, "wakefulness", wakefulness); } + private void updateLastWakeOriginLocation() { + mLastWakeOriginLocation = null; + + switch (mLastWakeReason) { + case PowerManager.WAKE_REASON_POWER_BUTTON: + mLastWakeOriginLocation = getPowerButtonOrigin(); + break; + default: + mLastWakeOriginLocation = getDefaultWakeSleepOrigin(); + break; + } + } + + private void updateLastSleepOriginLocation() { + mLastSleepOriginLocation = null; + + switch (mLastSleepReason) { + case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON: + mLastSleepOriginLocation = getPowerButtonOrigin(); + break; + default: + mLastSleepOriginLocation = getDefaultWakeSleepOrigin(); + break; + } + } + + /** + * Returns the point on the screen closest to the physical power button. + */ + private Point getPowerButtonOrigin() { + final boolean isPortrait = mContext.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_PORTRAIT; + + if (isPortrait) { + return new Point( + mDisplayMetrics.widthPixels, + mContext.getResources().getDimensionPixelSize( + R.dimen.physical_power_button_center_screen_location_y)); + } else { + return new Point( + mContext.getResources().getDimensionPixelSize( + R.dimen.physical_power_button_center_screen_location_y), + mDisplayMetrics.heightPixels); + } + } + + /** + * Returns the point on the screen used as the default origin for wake/sleep events. This is the + * middle-bottom of the screen. + */ + private Point getDefaultWakeSleepOrigin() { + return new Point(mDisplayMetrics.widthPixels / 2, mDisplayMetrics.heightPixels); + } + public interface Observer { default void onStartedWakingUp() {} default void onFinishedWakingUp() {} diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index a0b55218c0fc..38a6186ee7ea 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -23,18 +23,14 @@ import static com.android.systemui.people.PeopleTileViewHelper.getPersonIconBitm import static com.android.systemui.people.PeopleTileViewHelper.getSizeInDp; import android.app.Activity; -import android.app.INotificationManager; -import android.app.people.IPeopleManager; import android.app.people.PeopleSpaceTile; import android.content.Context; import android.content.Intent; -import android.content.pm.LauncherApps; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Outline; import android.graphics.drawable.GradientDrawable; import android.os.Bundle; -import android.os.ServiceManager; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -43,7 +39,6 @@ import android.widget.LinearLayout; import com.android.systemui.R; import com.android.systemui.people.widget.PeopleSpaceWidgetManager; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import java.util.ArrayList; import java.util.List; @@ -56,19 +51,13 @@ public class PeopleSpaceActivity extends Activity { private static final String TAG = "PeopleSpaceActivity"; private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; - private IPeopleManager mPeopleManager; private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; - private INotificationManager mNotificationManager; - private LauncherApps mLauncherApps; private Context mContext; - private NotificationEntryManager mNotificationEntryManager; private int mAppWidgetId; @Inject - public PeopleSpaceActivity(NotificationEntryManager notificationEntryManager, - PeopleSpaceWidgetManager peopleSpaceWidgetManager) { + public PeopleSpaceActivity(PeopleSpaceWidgetManager peopleSpaceWidgetManager) { super(); - mNotificationEntryManager = notificationEntryManager; mPeopleSpaceWidgetManager = peopleSpaceWidgetManager; } @@ -77,11 +66,6 @@ public class PeopleSpaceActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = getApplicationContext(); - mNotificationManager = INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)); - mPeopleManager = IPeopleManager.Stub.asInterface( - ServiceManager.getService(Context.PEOPLE_SERVICE)); - mLauncherApps = mContext.getSystemService(LauncherApps.class); mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID); setResult(RESULT_CANCELED); @@ -92,10 +76,8 @@ public class PeopleSpaceActivity extends Activity { List<PeopleSpaceTile> priorityTiles = new ArrayList<>(); List<PeopleSpaceTile> recentTiles = new ArrayList<>(); try { - priorityTiles = PeopleSpaceUtils.getPriorityTiles(mContext, mNotificationManager, - mPeopleManager, mLauncherApps, mNotificationEntryManager); - recentTiles = PeopleSpaceUtils.getRecentTiles(mContext, mNotificationManager, - mPeopleManager, mLauncherApps, mNotificationEntryManager); + priorityTiles = mPeopleSpaceWidgetManager.getPriorityTiles(); + recentTiles = mPeopleSpaceWidgetManager.getRecentTiles(); } catch (Exception e) { Log.e(TAG, "Couldn't retrieve conversations", e); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 0fbe1f71a7d7..a16037981370 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -19,8 +19,6 @@ package com.android.systemui.people; import static android.app.Notification.CATEGORY_MISSED_CALL; import static android.app.Notification.EXTRA_MESSAGES; -import android.annotation.NonNull; -import android.app.INotificationManager; import android.app.Notification; import android.app.people.ConversationChannel; import android.app.people.IPeopleManager; @@ -36,16 +34,13 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.icu.text.MeasureFormat; -import android.icu.util.Measure; -import android.icu.util.MeasureUnit; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.os.ServiceManager; import android.os.UserHandle; +import android.os.UserManager; import android.provider.ContactsContract; -import android.service.notification.ConversationChannelWrapper; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; @@ -65,7 +60,6 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.text.SimpleDateFormat; -import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -73,7 +67,6 @@ import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -85,9 +78,6 @@ public class PeopleSpaceUtils { /** Turns on debugging information about People Space. */ public static final boolean DEBUG = true; private static final String TAG = "PeopleSpaceUtils"; - private static final int DAYS_IN_A_WEEK = 7; - private static final int MIN_HOUR = 1; - private static final int ONE_DAY = 1; public static final String PACKAGE_NAME = "package_name"; public static final String USER_ID = "user_id"; public static final String SHORTCUT_ID = "shortcut_id"; @@ -127,58 +117,6 @@ public class PeopleSpaceUtils { } } - /** Returns a list of map entries corresponding to user's priority conversations. */ - @NonNull - public static List<PeopleSpaceTile> getPriorityTiles( - Context context, INotificationManager notificationManager, IPeopleManager peopleManager, - LauncherApps launcherApps, NotificationEntryManager notificationEntryManager) - throws Exception { - List<ConversationChannelWrapper> conversations = - notificationManager.getConversations( - false).getList(); - // Add priority conversations to tiles list. - Stream<ShortcutInfo> priorityConversations = conversations.stream() - .filter(c -> c.getNotificationChannel() != null - && c.getNotificationChannel().isImportantConversation()) - .map(c -> c.getShortcutInfo()); - List<PeopleSpaceTile> priorityTiles = getSortedTiles(peopleManager, launcherApps, - priorityConversations); - priorityTiles = augmentTilesFromVisibleNotifications( - context, priorityTiles, notificationEntryManager); - return priorityTiles; - } - - /** Returns a list of map entries corresponding to user's recent conversations. */ - @NonNull - public static List<PeopleSpaceTile> getRecentTiles( - Context context, INotificationManager notificationManager, IPeopleManager peopleManager, - LauncherApps launcherApps, NotificationEntryManager notificationEntryManager) - throws Exception { - if (DEBUG) Log.d(TAG, "Add recent conversations"); - List<ConversationChannelWrapper> conversations = - notificationManager.getConversations( - false).getList(); - Stream<ShortcutInfo> nonPriorityConversations = conversations.stream() - .filter(c -> c.getNotificationChannel() == null - || !c.getNotificationChannel().isImportantConversation()) - .map(c -> c.getShortcutInfo()); - - List<ConversationChannel> recentConversationsList = - peopleManager.getRecentConversations().getList(); - Stream<ShortcutInfo> recentConversations = recentConversationsList - .stream() - .map(c -> c.getShortcutInfo()); - - Stream<ShortcutInfo> mergedStream = Stream.concat(nonPriorityConversations, - recentConversations); - List<PeopleSpaceTile> recentTiles = - getSortedTiles(peopleManager, launcherApps, mergedStream); - - recentTiles = augmentTilesFromVisibleNotifications( - context, recentTiles, notificationEntryManager); - return recentTiles; - } - /** Returns stored widgets for the conversation specified. */ public static Set<String> getStoredWidgetIds(SharedPreferences sp, PeopleTileKey key) { if (!key.isValid()) { @@ -255,7 +193,8 @@ public class PeopleSpaceUtils { return augmentedTile.get(0); } - static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(Context context, + /** Adds to {@code tiles} any visible notifications. */ + public static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(Context context, List<PeopleSpaceTile> tiles, NotificationEntryManager notificationEntryManager) { if (notificationEntryManager == null) { Log.w(TAG, "NotificationEntryManager is null"); @@ -356,11 +295,12 @@ public class PeopleSpaceUtils { } /** Returns a list sorted by ascending last interaction time from {@code stream}. */ - private static List<PeopleSpaceTile> getSortedTiles(IPeopleManager peopleManager, - LauncherApps launcherApps, + public static List<PeopleSpaceTile> getSortedTiles(IPeopleManager peopleManager, + LauncherApps launcherApps, UserManager userManager, Stream<ShortcutInfo> stream) { return stream .filter(Objects::nonNull) + .filter(c -> !userManager.isQuietModeEnabled(c.getUserHandle())) .map(c -> new PeopleSpaceTile.Builder(c, launcherApps).build()) .filter(c -> shouldKeepConversation(c)) .map(c -> c.toBuilder().setLastInteractionTimestamp( @@ -426,36 +366,6 @@ public class PeopleSpaceUtils { return bitmap; } - /** Returns a readable status describing the {@code lastInteraction}. */ - public static String getLastInteractionString(Context context, long lastInteraction) { - if (lastInteraction == 0L) { - Log.e(TAG, "Could not get valid last interaction"); - return context.getString(R.string.basic_status); - } - long now = System.currentTimeMillis(); - Duration durationSinceLastInteraction = Duration.ofMillis(now - lastInteraction); - MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), - MeasureFormat.FormatWidth.WIDE); - if (durationSinceLastInteraction.toHours() < MIN_HOUR) { - return context.getString(R.string.timestamp, formatter.formatMeasures( - new Measure(durationSinceLastInteraction.toMinutes(), MeasureUnit.MINUTE))); - } else if (durationSinceLastInteraction.toDays() < ONE_DAY) { - return context.getString(R.string.timestamp, formatter.formatMeasures( - new Measure(durationSinceLastInteraction.toHours(), - MeasureUnit.HOUR))); - } else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) { - return context.getString(R.string.timestamp, formatter.formatMeasures( - new Measure(durationSinceLastInteraction.toHours(), - MeasureUnit.DAY))); - } else { - return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK - ? R.string.timestamp : R.string.over_timestamp, - formatter.formatMeasures( - new Measure(durationSinceLastInteraction.toDays() / DAYS_IN_A_WEEK, - MeasureUnit.WEEK))); - } - } - /** * Returns whether the {@code conversation} should be kept for display in the People Space. * diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 51af47d92d7b..6b917c576fd8 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -33,7 +33,6 @@ import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH; import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap; import static com.android.systemui.people.PeopleSpaceUtils.getUserId; -import android.annotation.ColorInt; import android.annotation.Nullable; import android.app.PendingIntent; import android.app.people.ConversationStatus; @@ -41,27 +40,28 @@ import android.app.people.PeopleSpaceTile; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; -import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.icu.text.MeasureFormat; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; import android.net.Uri; import android.os.Bundle; import android.text.TextUtils; import android.util.IconDrawableFactory; import android.util.Log; -import android.view.ContextThemeWrapper; import android.view.View; import android.widget.RemoteViews; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; -import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import java.text.NumberFormat; +import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -76,6 +76,10 @@ class PeopleTileViewHelper { public static final boolean DEBUG = true; private static final String TAG = "PeopleTileView"; + private static final int DAYS_IN_A_WEEK = 7; + private static final int ONE_DAY = 1; + private static final int MAX_WEEKS = 2; + public static final int LAYOUT_SMALL = 0; public static final int LAYOUT_MEDIUM = 1; public static final int LAYOUT_LARGE = 2; @@ -83,7 +87,9 @@ class PeopleTileViewHelper { private static final int MIN_CONTENT_MAX_LINES = 2; private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT = 14 + 12 + 4 + 16; - private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT = 8 + 4 + 4 + 8; + private static final int MIN_MEDIUM_VERTICAL_PADDING = 4; + private static final int MAX_MEDIUM_PADDING = 16; + private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING = 4 + 4; private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL = 6 + 4 + 8; private static final int FIXED_WIDTH_DIMENS_FOR_SMALL = 4 + 4; @@ -96,6 +102,8 @@ class PeopleTileViewHelper { public static final String EMPTY_STRING = ""; + private int mMediumVerticalPadding; + private Context mContext; private PeopleSpaceTile mTile; private float mDensity; @@ -205,7 +213,8 @@ class PeopleTileViewHelper { private int getContentHeightForLayout(int lineHeight) { switch (mLayoutSize) { case LAYOUT_MEDIUM: - return mHeight - (lineHeight + FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT); + return mHeight - (lineHeight + FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING + + mMediumVerticalPadding * 2); case LAYOUT_LARGE: return mHeight - (getSizeInDp( R.dimen.max_people_avatar_size_for_large_content) + lineHeight @@ -224,7 +233,16 @@ class PeopleTileViewHelper { } // Small layout used below a certain minimum mWidth with any mHeight. if (mWidth >= getSizeInDp(R.dimen.required_width_for_medium)) { - if (DEBUG) Log.d(TAG, "Medium view for mWidth: " + mWidth + " mHeight: " + mHeight); + int spaceAvailableForPadding = + mHeight - (getSizeInDp(R.dimen.avatar_size_for_medium) + 4 + getLineHeight( + getSizeInDp(R.dimen.name_text_size_for_medium))); + if (DEBUG) { + Log.d(TAG, "Medium view for mWidth: " + mWidth + " mHeight: " + mHeight + + " with padding space: " + spaceAvailableForPadding); + } + int maxVerticalPadding = Math.min(Math.floorDiv(spaceAvailableForPadding, 2), + MAX_MEDIUM_PADDING); + mMediumVerticalPadding = Math.max(MIN_MEDIUM_VERTICAL_PADDING, maxVerticalPadding); return LAYOUT_MEDIUM; } // Small layout can always handle our minimum mWidth and mHeight for our widget. @@ -347,11 +365,7 @@ class PeopleTileViewHelper { setMaxLines(views); CharSequence content = mTile.getNotificationContent(); views = setPunctuationRemoteViewsFields(views, content); - // TODO(b/184931139): Update to RemoteViews wrapper to set via attribute once available - @ColorInt int color = Utils.getColorAttr(mContext, - android.R.attr.textColorPrimary).getDefaultColor(); - views.setInt(R.id.text_content, "setTextColor", color); - + views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorPrimary); views.setTextViewText(R.id.text_content, mTile.getNotificationContent()); views.setViewVisibility(R.id.image, View.GONE); views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message); @@ -398,9 +412,7 @@ class PeopleTileViewHelper { views.setViewVisibility(R.id.messages_count, View.GONE); setMaxLines(views); // Secondary text color for statuses. - @ColorInt int secondaryColor = Utils.getColorAttr(mContext, - android.R.attr.textColorSecondary).getDefaultColor(); - views.setInt(R.id.text_content, "setTextColor", secondaryColor); + views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorSecondary); views.setTextViewText(R.id.text_content, statusText); Icon statusIcon = status.getIcon(); @@ -541,6 +553,14 @@ class PeopleTileViewHelper { views.setViewVisibility(R.id.text_content, View.VISIBLE); views.setViewVisibility(R.id.subtext, View.GONE); } + + if (mLayoutSize == LAYOUT_MEDIUM) { + if (DEBUG) Log.d(TAG, "Set vertical padding: " + mMediumVerticalPadding); + int horizontalPadding = (int) Math.floor(MAX_MEDIUM_PADDING * mDensity); + int verticalPadding = (int) Math.floor(mMediumVerticalPadding * mDensity); + views.setViewPadding(R.id.item, horizontalPadding, verticalPadding, horizontalPadding, + verticalPadding); + } return views; } @@ -551,9 +571,16 @@ class PeopleTileViewHelper { views.setViewVisibility(R.id.predefined_icon, View.GONE); views.setViewVisibility(R.id.messages_count, View.GONE); } - String status = PeopleSpaceUtils.getLastInteractionString(mContext, + String status = getLastInteractionString(mContext, mTile.getLastInteractionTimestamp()); - views.setTextViewText(R.id.last_interaction, status); + if (status != null) { + if (DEBUG) Log.d(TAG, "Show last interaction"); + views.setViewVisibility(R.id.last_interaction, View.VISIBLE); + views.setTextViewText(R.id.last_interaction, status); + } else { + if (DEBUG) Log.d(TAG, "Hide last interaction"); + views.setViewVisibility(R.id.last_interaction, View.GONE); + } return views; } @@ -599,4 +626,34 @@ class PeopleTileViewHelper { hasNewStory); return convertDrawableToBitmap(personDrawable); } + + /** Returns a readable status describing the {@code lastInteraction}. */ + @Nullable + public static String getLastInteractionString(Context context, long lastInteraction) { + if (lastInteraction == 0L) { + Log.e(TAG, "Could not get valid last interaction"); + return null; + } + long now = System.currentTimeMillis(); + Duration durationSinceLastInteraction = Duration.ofMillis(now - lastInteraction); + MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), + MeasureFormat.FormatWidth.WIDE); + if (durationSinceLastInteraction.toDays() <= ONE_DAY) { + return null; + } else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) { + return context.getString(R.string.timestamp, formatter.formatMeasures( + new Measure(durationSinceLastInteraction.toHours(), + MeasureUnit.DAY))); + } else if (durationSinceLastInteraction.toDays() <= DAYS_IN_A_WEEK * 2) { + return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK + ? R.string.timestamp : R.string.over_timestamp, + formatter.formatMeasures( + new Measure(durationSinceLastInteraction.toDays() / DAYS_IN_A_WEEK, + MeasureUnit.WEEK))); + } else { + // Over 2 weeks ago + return context.getString(R.string.over_timestamp, + formatter.formatMeasures(new Measure(MAX_WEEKS, MeasureUnit.WEEK))); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java index c01a52dd0a8e..c416b5edb264 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java @@ -23,11 +23,13 @@ import android.content.pm.LauncherApps; import android.os.Bundle; import android.os.ServiceManager; import android.os.UserHandle; +import android.os.UserManager; import android.service.notification.NotificationStats; import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.UnlaunchableAppActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.statusbar.IStatusBarService; @@ -48,15 +50,17 @@ public class LaunchConversationActivity extends Activity { private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); private NotificationEntryManager mNotificationEntryManager; private final Optional<BubblesManager> mBubblesManagerOptional; + private final UserManager mUserManager; private boolean mIsForTesting; private IStatusBarService mIStatusBarService; @Inject public LaunchConversationActivity(NotificationEntryManager notificationEntryManager, - Optional<BubblesManager> bubblesManagerOptional) { + Optional<BubblesManager> bubblesManagerOptional, UserManager userManager) { super(); mNotificationEntryManager = notificationEntryManager; mBubblesManagerOptional = bubblesManagerOptional; + mUserManager = userManager; } @Override @@ -80,12 +84,24 @@ public class LaunchConversationActivity extends Activity { } mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_CLICKED); try { + + if (mUserManager.isQuietModeEnabled(userHandle)) { + if (DEBUG) Log.d(TAG, "Cannot launch app when quieted"); + final Intent dialogIntent = + UnlaunchableAppActivity.createInQuietModeDialogIntent( + userHandle.getIdentifier()); + this.getApplicationContext().startActivity(dialogIntent); + finish(); + return; + } + NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif( notificationKey); if (entry != null && entry.canBubble() && mBubblesManagerOptional.isPresent()) { if (DEBUG) Log.d(TAG, "Open bubble for conversation"); mBubblesManagerOptional.get().expandStackAndSelectBubble(entry); // Just opt-out and don't cancel the notification for bubbles. + finish(); return; } diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java index d63dc4ae6616..6cb7c315737e 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java @@ -31,7 +31,9 @@ import static com.android.systemui.people.PeopleSpaceUtils.getStoredWidgetIds; import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetOptionsAndView; import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetViews; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.PendingIntent; @@ -51,7 +53,9 @@ import android.net.Uri; import android.os.Bundle; import android.os.ServiceManager; import android.os.UserHandle; +import android.os.UserManager; import android.preference.PreferenceManager; +import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log; @@ -76,6 +80,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Singleton; @@ -96,6 +101,8 @@ public class PeopleSpaceWidgetManager { private NotificationEntryManager mNotificationEntryManager; private PackageManager mPackageManager; private PeopleSpaceWidgetProvider mPeopleSpaceWidgetProvider; + private INotificationManager mINotificationManager; + private UserManager mUserManager; public UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); @GuardedBy("mLock") public static Map<PeopleTileKey, PeopleSpaceWidgetProvider.TileConversationListener> @@ -121,17 +128,21 @@ public class PeopleSpaceWidgetManager { mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); mPackageManager = mContext.getPackageManager(); mPeopleSpaceWidgetProvider = new PeopleSpaceWidgetProvider(); + mINotificationManager = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + mUserManager = context.getSystemService(UserManager.class); } /** * AppWidgetManager setter used for testing. */ @VisibleForTesting - protected void setAppWidgetManager( + public void setAppWidgetManager( AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager, PeopleManager peopleManager, LauncherApps launcherApps, NotificationEntryManager notificationEntryManager, PackageManager packageManager, - boolean isForTesting, PeopleSpaceWidgetProvider peopleSpaceWidgetProvider) { + boolean isForTesting, PeopleSpaceWidgetProvider peopleSpaceWidgetProvider, + UserManager userManager, INotificationManager notificationManager) { mAppWidgetManager = appWidgetManager; mIPeopleManager = iPeopleManager; mPeopleManager = peopleManager; @@ -140,6 +151,8 @@ public class PeopleSpaceWidgetManager { mPackageManager = packageManager; mIsForTesting = isForTesting; mPeopleSpaceWidgetProvider = peopleSpaceWidgetProvider; + mUserManager = userManager; + mINotificationManager = notificationManager; } /** @@ -783,4 +796,52 @@ public class PeopleSpaceWidgetManager { ComponentName componentName = new ComponentName(mContext, PeopleSpaceWidgetProvider.class); return mAppWidgetManager.requestPinAppWidget(componentName, extras, successCallback); } + + /** Returns a list of map entries corresponding to user's priority conversations. */ + @NonNull + public List<PeopleSpaceTile> getPriorityTiles() + throws Exception { + List<ConversationChannelWrapper> conversations = + mINotificationManager.getConversations(true).getList(); + // Add priority conversations to tiles list. + Stream<ShortcutInfo> priorityConversations = conversations.stream() + .filter(c -> c.getNotificationChannel() != null + && c.getNotificationChannel().isImportantConversation()) + .map(c -> c.getShortcutInfo()); + List<PeopleSpaceTile> priorityTiles = PeopleSpaceUtils.getSortedTiles(mIPeopleManager, + mLauncherApps, mUserManager, + priorityConversations); + priorityTiles = PeopleSpaceUtils.augmentTilesFromVisibleNotifications( + mContext, priorityTiles, mNotificationEntryManager); + return priorityTiles; + } + + /** Returns a list of map entries corresponding to user's recent conversations. */ + @NonNull + public List<PeopleSpaceTile> getRecentTiles() + throws Exception { + if (DEBUG) Log.d(TAG, "Add recent conversations"); + List<ConversationChannelWrapper> conversations = + mINotificationManager.getConversations(false).getList(); + Stream<ShortcutInfo> nonPriorityConversations = conversations.stream() + .filter(c -> c.getNotificationChannel() == null + || !c.getNotificationChannel().isImportantConversation()) + .map(c -> c.getShortcutInfo()); + + List<ConversationChannel> recentConversationsList = + mIPeopleManager.getRecentConversations().getList(); + Stream<ShortcutInfo> recentConversations = recentConversationsList + .stream() + .map(c -> c.getShortcutInfo()); + + Stream<ShortcutInfo> mergedStream = Stream.concat(nonPriorityConversations, + recentConversations); + List<PeopleSpaceTile> recentTiles = + PeopleSpaceUtils.getSortedTiles(mIPeopleManager, mLauncherApps, mUserManager, + mergedStream); + + recentTiles = PeopleSpaceUtils.augmentTilesFromVisibleNotifications( + mContext, recentTiles, mNotificationEntryManager); + return recentTiles; + } } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt index f87ea7c61ca8..feb27d804e5e 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt @@ -29,6 +29,7 @@ import android.util.Log import androidx.annotation.MainThread import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread +import com.android.systemui.appops.AppOpsController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -48,7 +49,6 @@ private val defaultDialogProvider = object : PrivacyDialogController.DialogProvi return PrivacyDialog(context, list, starter) } } - /** * Controller for [PrivacyDialog]. * @@ -66,6 +66,7 @@ class PrivacyDialogController( private val uiExecutor: Executor, private val privacyLogger: PrivacyLogger, private val keyguardStateController: KeyguardStateController, + private val appOpsController: AppOpsController, @VisibleForTesting private val dialogProvider: DialogProvider ) { @@ -79,7 +80,8 @@ class PrivacyDialogController( @Background backgroundExecutor: Executor, @Main uiExecutor: Executor, privacyLogger: PrivacyLogger, - keyguardStateController: KeyguardStateController + keyguardStateController: KeyguardStateController, + appOpsController: AppOpsController ) : this( permissionManager, packageManager, @@ -90,6 +92,7 @@ class PrivacyDialogController( uiExecutor, privacyLogger, keyguardStateController, + appOpsController, defaultDialogProvider ) @@ -127,7 +130,9 @@ class PrivacyDialogController( } @WorkerThread - private fun permGroupUsage(): List<PermGroupUsage> = permissionManager.indicatorAppOpUsageData + private fun permGroupUsage(): List<PermGroupUsage> { + return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted) + } /** * Show the [PrivacyDialog] @@ -261,4 +266,4 @@ class PrivacyDialogController( starter: (String, Int) -> Unit ): PrivacyDialog } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt index 4c617edd8542..63ec6e5db017 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt @@ -54,7 +54,8 @@ private const val UNKNOWN_TIMESTAMP = -1L data class PrivacyItem( val privacyType: PrivacyType, val application: PrivacyApplication, - val timeStampElapsed: Long = UNKNOWN_TIMESTAMP + val timeStampElapsed: Long = UNKNOWN_TIMESTAMP, + val paused: Boolean = false ) { val log = "(${privacyType.logName}, ${application.packageName}(${application.uid}), " + "$timeStampElapsed)" diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index f7e2a31ce727..8b27b6ecbfd1 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -257,7 +257,7 @@ class PrivacyItemController @Inject constructor( privacyList = emptyList() return } - val list = appOpsController.getActiveAppOpsForUser(UserHandle.USER_ALL).filter { + val list = appOpsController.getActiveAppOps(true).filter { UserHandle.getUserId(it.uid) in currentUserIds || it.code == AppOpsManager.OP_PHONE_CALL_MICROPHONE || it.code == AppOpsManager.OP_PHONE_CALL_CAMERA @@ -279,7 +279,9 @@ class PrivacyItemController @Inject constructor( // Anything earlier than this timestamp can be removed val removeBeforeTime = systemClock.elapsedRealtime() - TIME_TO_HOLD_INDICATORS - val mustKeep = privacyList.filter { it.timeStampElapsed > removeBeforeTime && it !in list } + val mustKeep = privacyList.filter { + it.timeStampElapsed > removeBeforeTime && !(it isIn list) + } // There are items we must keep because they haven't been around for enough time. if (mustKeep.isNotEmpty()) { @@ -291,7 +293,18 @@ class PrivacyItemController @Inject constructor( logger.logPrivacyItemsUpdateScheduled(delay) holdingRunnableCanceler = bgExecutor.executeDelayed(updateListAndNotifyChanges, delay) } - return list + mustKeep + return list.filter { !it.paused } + mustKeep + } + + /** + * Ignores the paused status to determine if the element is in the list + */ + private infix fun PrivacyItem.isIn(list: List<PrivacyItem>): Boolean { + return list.any { + it.privacyType == privacyType && + it.application == application && + it.timeStampElapsed == timeStampElapsed + } } private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? { @@ -308,7 +321,7 @@ class PrivacyItemController @Inject constructor( return null } val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid) - return PrivacyItem(type, app, appOpItem.timeStartedElapsed) + return PrivacyItem(type, app, appOpItem.timeStartedElapsed, appOpItem.isDisabled) } interface Callback { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index 3467838bd02a..74ae3a698998 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -32,6 +32,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.globalactions.GlobalActionsDialogLite; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; @@ -65,6 +66,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final MetricsLogger mMetricsLogger; private final FalsingManager mFalsingManager; private final SettingsButton mSettingsButton; + private final View mSettingsButtonContainer; private final TextView mBuildText; private final View mEdit; private final MultiUserSwitch mMultiUserSwitch; @@ -152,6 +154,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mFalsingManager = falsingManager; mSettingsButton = mView.findViewById(R.id.settings_button); + mSettingsButtonContainer = mView.findViewById(R.id.settings_button_container); mBuildText = mView.findViewById(R.id.build); mEdit = mView.findViewById(android.R.id.edit); mMultiUserSwitch = mView.findViewById(R.id.multi_user_switch); @@ -258,10 +261,12 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mView.disable(state2, isTunerEnabled()); } - private void startSettingsActivity() { + ActivityLaunchAnimator.Controller animationController = + mSettingsButtonContainer != null ? ActivityLaunchAnimator.Controller.fromView( + mSettingsButtonContainer) : null; mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), - true /* dismissShade */); + true /* dismissShade */, animationController); } private boolean isTunerEnabled() { 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 b728b4360db0..6f19276b8d00 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -184,8 +184,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { @Override public CharSequence getTileLabel() { - CharSequence qawLabel = mQuickAccessWalletClient.getServiceLabel(); - return qawLabel == null ? mLabel : qawLabel; + return mLabel; } private void queryWalletCards() { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java index dbd6758b090d..b60fd1326cd3 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java @@ -48,7 +48,7 @@ public class BrightnessSliderView extends FrameLayout { super(context, attrs); } - // Inflated from quick_settings_brightness_dialog or quick_settings_brightness_dialog_thick + // Inflated from quick_settings_brightness_dialog @Override protected void onFinishInflate() { super.onFinishInflate(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index f57fd21526b9..f4266a27c6b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.statusbar.phone.NotificationIconContainer; @@ -82,6 +83,9 @@ public class NotificationShelf extends ActivatableNotificationView implements private Rect mClipRect = new Rect(); private int mCutoutHeight; private int mGapHeight; + private int mIndexOfFirstViewInShelf = -1; + private int mIndexOfFirstViewInOverflowingSection = -1; + private NotificationShelfController mController; public NotificationShelf(Context context, AttributeSet attrs) { @@ -159,30 +163,49 @@ public class NotificationShelf extends ActivatableNotificationView implements } /** Update the state of the shelf. */ - public void updateState(AmbientState ambientState) { + public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState, + AmbientState ambientState) { ExpandableView lastView = ambientState.getLastVisibleBackgroundChild(); ShelfState viewState = (ShelfState) getViewState(); if (mShowNotificationShelf && lastView != null) { - float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding() - + ambientState.getStackTranslation(); ExpandableViewState lastViewState = lastView.getViewState(); - float viewEnd = lastViewState.yTranslation + lastViewState.height; viewState.copyFrom(lastViewState); + viewState.height = getIntrinsicHeight(); - viewState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height, - getFullyClosedTranslation()); viewState.zTranslation = ambientState.getBaseZHeight(); viewState.clipTopAmount = 0; viewState.alpha = 1f - ambientState.getHideAmount(); viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0; viewState.hideSensitive = false; viewState.xTranslation = getTranslationX(); + viewState.hasItemsInStableShelf = lastViewState.inShelf; + viewState.firstViewInShelf = algorithmState.firstViewInShelf; + viewState.firstViewInOverflowSection = algorithmState.firstViewInOverflowSection; if (mNotGoneIndex != -1) { viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex); } - viewState.hasItemsInStableShelf = lastViewState.inShelf; + viewState.hidden = !mAmbientState.isShadeExpanded() - || mAmbientState.isQsCustomizerShowing(); + || mAmbientState.isQsCustomizerShowing() + || algorithmState.firstViewInShelf == null; + + final int indexOfFirstViewInShelf = algorithmState.visibleChildren.indexOf( + algorithmState.firstViewInShelf); + + if (mAmbientState.isExpansionChanging() + && algorithmState.firstViewInShelf != null + && indexOfFirstViewInShelf > 0) { + + // Show shelf if section before it is showing. + final ExpandableView viewBeforeShelf = algorithmState.visibleChildren.get( + indexOfFirstViewInShelf - 1); + if (viewBeforeShelf.getViewState().hidden) { + viewState.hidden = true; + } + } + + final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight(); + viewState.yTranslation = stackEnd - viewState.height; } else { viewState.hidden = true; viewState.location = ExpandableViewState.LOCATION_GONE; @@ -199,13 +222,11 @@ public class NotificationShelf extends ActivatableNotificationView implements if (!mShowNotificationShelf) { return; } - mShelfIcons.resetViewStates(); float shelfStart = getTranslationY(); float numViewsInShelf = 0.0f; View lastChild = mAmbientState.getLastVisibleBackgroundChild(); mNotGoneIndex = -1; - float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2; // find the first view that doesn't overlap with the shelf int notGoneIndex = 0; int colorOfViewBeforeLast = NO_COLOR; @@ -219,7 +240,7 @@ public class NotificationShelf extends ActivatableNotificationView implements float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity(); boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold || (mAmbientState.isExpansionChanging() - && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold); + && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold); boolean expandingAnimated = mAmbientState.isExpansionChanging() && !mAmbientState.isPanelTracking(); int baseZHeight = mAmbientState.getBaseZHeight(); @@ -233,22 +254,37 @@ public class NotificationShelf extends ActivatableNotificationView implements if (!child.needsClippingToShelf() || child.getVisibility() == GONE) { continue; } - float notificationClipEnd; boolean aboveShelf = ViewState.getFinalTranslationZ(child) > baseZHeight || child.isPinned(); boolean isLastChild = child == lastChild; float rowTranslationY = child.getTranslationY(); + + final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast, + expandingAnimated, isLastChild); + + final float stackEnd = mAmbientState.getStackY() + + mAmbientState.getStackHeight(); + // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) { - notificationClipEnd = shelfStart + getIntrinsicHeight(); + notificationClipEnd = stackEnd; + } else if (mAmbientState.isExpansionChanging()) { + if (mIndexOfFirstViewInOverflowingSection != -1 + && i >= mIndexOfFirstViewInOverflowingSection) { + // Clip notifications in (section overflowing into shelf) to shelf start. + notificationClipEnd = shelfStart - mPaddingBetweenElements; + } else { + // Clip notifications before the section overflowing into shelf + // to stackEnd because we do not show the shelf if the section right before the + // shelf is still hidden. + notificationClipEnd = stackEnd; + } } else { notificationClipEnd = shelfStart - mPaddingBetweenElements; } int clipTop = updateNotificationClipHeight(child, notificationClipEnd, notGoneIndex); clipTopAmount = Math.max(clipTop, clipTopAmount); - final float inShelfAmount = updateShelfTransformation(child, scrollingFast, - expandingAnimated, isLastChild); // If the current row is an ExpandableNotificationRow, update its color, roundedness, // and icon state. if (child instanceof ExpandableNotificationRow) { @@ -314,19 +350,23 @@ public class NotificationShelf extends ActivatableNotificationView implements distanceToGapTop / mGapHeight); previousAnv.setBottomRoundness(firstElementRoundness, false /* don't animate */); - backgroundTop = (int) distanceToGapBottom; } } previousAnv = anv; } } + clipTransientViews(); setClipTopAmount(clipTopAmount); - boolean isHidden = getViewState().hidden || clipTopAmount >= getIntrinsicHeight(); - if (mShowNotificationShelf) { - setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); - } + + boolean isHidden = getViewState().hidden + || clipTopAmount >= getIntrinsicHeight() + || !mShowNotificationShelf + || numViewsInShelf < 1f; + + // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa. + setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); setBackgroundTop(backgroundTop); setFirstElementRoundness(firstElementRoundness); mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex()); @@ -339,11 +379,10 @@ public class NotificationShelf extends ActivatableNotificationView implements continue; } ExpandableNotificationRow row = (ExpandableNotificationRow) child; - updateIconClipAmount(row); updateContinuousClipping(row); } - boolean hideBackground = numViewsInShelf < 1.0f; - setHideBackground(hideBackground || backgroundForceHidden); + boolean hideBackground = isHidden; + setHideBackground(hideBackground); if (mNotGoneIndex == -1) { mNotGoneIndex = notGoneIndex; } @@ -476,10 +515,10 @@ public class NotificationShelf extends ActivatableNotificationView implements /** * @return the amount how much this notification is in the shelf */ - private float updateShelfTransformation(ExpandableView view, boolean scrollingFast, + private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) { - // Let calculate how much the view is in the shelf + // Let's calculate how much the view is in the shelf float viewStart = view.getTranslationY(); int fullHeight = view.getActualHeight() + mPaddingBetweenElements; float iconTransformStart = calculateIconTransformationStart(view); @@ -496,15 +535,21 @@ public class NotificationShelf extends ActivatableNotificationView implements transformDistance, view.getMinHeight() - getIntrinsicHeight()); } + float viewEnd = viewStart + fullHeight; float fullTransitionAmount = 0.0f; float iconTransitionAmount = 0.0f; float shelfStart = getTranslationY(); - - if (viewEnd >= shelfStart + if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) { + // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf + if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) { + fullTransitionAmount = 1f; + iconTransitionAmount = 1f; + } + } else if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || view.isInShelf()) && (mAmbientState.isShadeExpanded() - || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) { + || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) { if (viewStart < shelfStart) { float fullAmount = (shelfStart - viewStart) / fullHeight; @@ -572,7 +617,7 @@ public class NotificationShelf extends ActivatableNotificationView implements && !mNoAnimationsInThisFrame; } iconState.clampedAppearAmount = clampedAmount; - setIconTransformationAmount(view, transitionAmount, isLastChild); + setIconTransformationAmount(view, transitionAmount); } private boolean isTargetClipped(ExpandableView view) { @@ -585,12 +630,10 @@ public class NotificationShelf extends ActivatableNotificationView implements + view.getContentTranslation() + view.getRelativeTopPadding(target) + target.getHeight(); - return endOfTarget >= getTranslationY() - mPaddingBetweenElements; } - private void setIconTransformationAmount(ExpandableView view, float transitionAmount, - boolean isLastChild) { + private void setIconTransformationAmount(ExpandableView view, float transitionAmount) { if (!(view instanceof ExpandableNotificationRow)) { return; } @@ -601,7 +644,6 @@ public class NotificationShelf extends ActivatableNotificationView implements return; } iconState.alpha = transitionAmount; - boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf(); iconState.hidden = isAppearing || (view instanceof ExpandableNotificationRow @@ -610,8 +652,8 @@ public class NotificationShelf extends ActivatableNotificationView implements || (transitionAmount == 0.0f && !iconState.isAnimating(icon)) || row.isAboveShelf() || row.showingPulsing() - || (!row.isInShelf() && isLastChild) || row.getTranslationZ() > mAmbientState.getBaseZHeight(); + iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount; // Fade in icons at shelf start @@ -790,8 +832,19 @@ public class NotificationShelf extends ActivatableNotificationView implements mController = notificationShelfController; } + public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) { + mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf); + } + + public void setFirstViewInOverflowingSection(ExpandableView firstViewInOverflowingSection) { + mIndexOfFirstViewInOverflowingSection = + mHostLayoutController.indexOfChild(firstViewInOverflowingSection); + } + private class ShelfState extends ExpandableViewState { private boolean hasItemsInStableShelf; + private ExpandableView firstViewInShelf; + private ExpandableView firstViewInOverflowSection; @Override public void applyToView(View view) { @@ -800,6 +853,8 @@ public class NotificationShelf extends ActivatableNotificationView implements } super.applyToView(view); + setIndexOfFirstViewInShelf(firstViewInShelf); + setFirstViewInOverflowingSection(firstViewInOverflowSection); updateAppearance(); setHasItemsInStableShelf(hasItemsInStableShelf); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); @@ -812,6 +867,8 @@ public class NotificationShelf extends ActivatableNotificationView implements } super.animateTo(child, properties); + setIndexOfFirstViewInShelf(firstViewInShelf); + setFirstViewInOverflowingSection(firstViewInOverflowSection); updateAppearance(); setHasItemsInStableShelf(hasItemsInStableShelf); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java index 1e935c19b710..4f70fdb0d978 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java @@ -22,6 +22,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter; @@ -103,9 +104,10 @@ public class NotificationShelfController { return mView.getHeight(); } - public void updateState(AmbientState ambientState) { + public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState, + AmbientState ambientState) { mAmbientState = ambientState; - mView.updateState(ambientState); + mView.updateState(algorithmState, ambientState); } public int getIntrinsicHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt new file mode 100644 index 000000000000..5ab71bc62fe6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -0,0 +1,352 @@ +/* + * 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.statusbar.events + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ObjectAnimator +import android.annotation.UiThread +import android.util.Log +import android.view.Gravity +import android.view.View +import android.widget.FrameLayout + +import com.android.systemui.animation.Interpolators +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main + +import java.lang.IllegalStateException +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * Understands how to keep the persistent privacy dot in the corner of the screen in + * ScreenDecorations, which does not rotate with the device. + * + * The basic principle here is that each dot will sit in a box that is equal to the margins of the + * status bar (specifically the status_bar_contents view in PhoneStatusBarView). Each dot container + * will have its gravity set towards the corner (i.e., top-right corner gets top|right gravity), and + * the contained ImageView will be set to center_vertical and away from the corner horizontally. The + * Views will match the status bar top padding and status bar height so that the dot can appear to + * reside directly after the status bar system contents (basically to the right of the battery). + * + * NOTE: any operation that modifies views directly must run on the provided executor, because + * these views are owned by ScreenDecorations and it runs in its own thread + */ + +@SysUISingleton +class PrivacyDotViewController @Inject constructor( + @Main val mainExecutor: Executor, + val animationScheduler: SystemStatusAnimationScheduler +) { + private var rotation = 0 + private var leftSize = 0 + private var rightSize = 0 + + private var sbHeightPortrait = 0 + private var sbHeightLandscape = 0 + + private var hasMultipleHeights = false + private var needsHeightUpdate = false + private var needsRotationUpdate = false + private var needsMarginUpdate = false + + private lateinit var tl: View + private lateinit var tr: View + private lateinit var bl: View + private lateinit var br: View + + // Track which corner is active (based on orientation + RTL) + private var designatedCorner: View? = null + + // Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread + private var uiExecutor: Executor? = null + + private val views: Sequence<View> + get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl) + + fun setUiExecutor(e: Executor) { + uiExecutor = e + } + + @UiThread + fun updateRotation(rot: Int) { + if (rot == rotation) { + return + } + + // A rotation has started, hide the views to avoid flicker + setCornerVisibilities(View.INVISIBLE) + + if (hasMultipleHeights && (rotation % 2) != (rot % 2)) { + // we've changed from vertical to horizontal; update status bar height + needsHeightUpdate = true + } + + rotation = rot + needsRotationUpdate = true + } + + @UiThread + private fun updateHeights(rot: Int) { + val height = when (rot) { + 0, 2 -> sbHeightPortrait + 1, 3 -> sbHeightLandscape + else -> 0 + } + + views.forEach { it.layoutParams.height = height } + } + + // Update the gravity and margins of the privacy views + @UiThread + private fun updateRotations() { + // To keep a view in the corner, its gravity is always the description of its current corner + // Therefore, just figure out which view is in which corner. This turns out to be something + // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and + // rotating the device counter-clockwise increments rotation by 1 + + views.forEach { corner -> + val rotatedCorner = rotatedCorner(cornerForView(corner)) + (corner.layoutParams as FrameLayout.LayoutParams).apply { + gravity = rotatedCorner.toGravity() + } + + // Set the dot's view gravity to hug the status bar + (corner.findViewById<View>(R.id.privacy_dot) + .layoutParams as FrameLayout.LayoutParams) + .gravity = rotatedCorner.innerGravity() + } + } + + @UiThread + private fun updateCornerSizes() { + views.forEach { corner -> + val rotatedCorner = rotatedCorner(cornerForView(corner)) + val w = widthForCorner(rotatedCorner) + Log.d(TAG, "updateCornerSizes: setting (${cornerForView(corner)}) to $w") + (corner.layoutParams as FrameLayout.LayoutParams).width = w + corner.requestLayout() + } + } + + // Designated view will be the one at statusbar's view.END + @UiThread + private fun selectDesignatedCorner(): View? { + if (!this::tl.isInitialized) { + return null + } + + val isRtl = tl.isLayoutRtl + + return when (rotation) { + 0 -> if (isRtl) tl else tr + 1 -> if (isRtl) tr else br + 2 -> if (isRtl) br else bl + 3 -> if (isRtl) bl else tl + else -> throw IllegalStateException("unknown rotation") + } + } + + // Track the current designated corner and maybe animate to a new rotation + @UiThread + private fun updateDesignatedCorner(newCorner: View) { + designatedCorner = newCorner + + if (animationScheduler.hasPersistentDot) { + designatedCorner!!.visibility = View.VISIBLE + designatedCorner!!.alpha = 0f + designatedCorner!!.animate() + .alpha(1.0f) + .setDuration(300) + .start() + } + } + + @UiThread + private fun setCornerVisibilities(vis: Int) { + views.forEach { corner -> + corner.visibility = vis + } + } + + private fun cornerForView(v: View): Int { + return when (v) { + tl -> TOP_LEFT + tr -> TOP_RIGHT + bl -> BOTTOM_LEFT + br -> BOTTOM_RIGHT + else -> throw IllegalArgumentException("not a corner view") + } + } + + private fun rotatedCorner(corner: Int): Int { + var modded = corner - rotation + if (modded < 0) { + modded += 4 + } + + return modded + } + + private fun widthForCorner(corner: Int): Int { + return when (corner) { + TOP_LEFT, BOTTOM_LEFT -> leftSize + TOP_RIGHT, BOTTOM_RIGHT -> rightSize + else -> throw IllegalArgumentException("Unknown corner") + } + } + + fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) { + if (this::tl.isInitialized && this::tr.isInitialized && + this::bl.isInitialized && this::br.isInitialized) { + if (tl == topLeft && tr == topRight && bl == bottomLeft && br == bottomRight) { + return + } + } + + tl = topLeft + tr = topRight + bl = bottomLeft + br = bottomRight + + designatedCorner = selectDesignatedCorner() + mainExecutor.execute { + animationScheduler.addCallback(systemStatusAnimationCallback) + } + } + + /** + * Set the status bar height in portrait and landscape, in pixels. If they are the same you can + * pass the same value twice + */ + fun setStatusBarHeights(portrait: Int, landscape: Int) { + sbHeightPortrait = portrait + sbHeightLandscape = landscape + + hasMultipleHeights = portrait != landscape + } + + /** + * The dot view containers will fill the margin in order to position the dots correctly + * + * @param left the space between the status bar contents and the left side of the screen + * @param right space between the status bar contents and the right side of the screen + */ + fun setStatusBarMargins(left: Int, right: Int) { + leftSize = left + rightSize = right + + needsMarginUpdate = true + + // Margins come after PhoneStatusBarView does a layout pass, and so will always happen + // after rotation changes. It is safe to execute the updates from here + uiExecutor?.execute { + doUpdates(needsRotationUpdate, needsHeightUpdate, needsMarginUpdate) + } + } + + private fun doUpdates(rot: Boolean, height: Boolean, width: Boolean) { + var newDesignatedCorner: View? = null + + if (rot) { + needsRotationUpdate = false + updateRotations() + newDesignatedCorner = selectDesignatedCorner() + } + + if (height) { + needsHeightUpdate = false + updateHeights(rotation) + } + + if (width) { + needsMarginUpdate = false + updateCornerSizes() + } + + if (newDesignatedCorner != null && newDesignatedCorner != designatedCorner) { + updateDesignatedCorner(newDesignatedCorner) + } + } + + private val systemStatusAnimationCallback: SystemStatusAnimationCallback = + object : SystemStatusAnimationCallback { + override fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { + if (designatedCorner == null) { + return null + } + + val alpha = ObjectAnimator.ofFloat( + designatedCorner, "alpha", 0f, 1f) + alpha.duration = DURATION + alpha.interpolator = Interpolators.ALPHA_OUT + alpha.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animator: Animator) { + uiExecutor?.execute { designatedCorner?.visibility = View.VISIBLE } + } + }) + return alpha + } + + override fun onHidePersistentDot(): Animator? { + if (designatedCorner == null) { + return null + } + + val alpha = ObjectAnimator.ofFloat( + designatedCorner, "alpha", 1f, 0f) + alpha.duration = DURATION + alpha.interpolator = Interpolators.ALPHA_OUT + alpha.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animator: Animator) { + uiExecutor?.execute { designatedCorner?.visibility = View.INVISIBLE } + } + }) + alpha.start() + return null + } + } +} + +const val TOP_LEFT = 0 +const val TOP_RIGHT = 1 +const val BOTTOM_RIGHT = 2 +const val BOTTOM_LEFT = 3 +private const val DURATION = 160L +private const val TAG = "PrivacyDotViewController" + +private fun Int.toGravity(): Int { + return when (this) { + TOP_LEFT -> Gravity.TOP or Gravity.LEFT + TOP_RIGHT -> Gravity.TOP or Gravity.RIGHT + BOTTOM_LEFT -> Gravity.BOTTOM or Gravity.LEFT + BOTTOM_RIGHT -> Gravity.BOTTOM or Gravity.RIGHT + else -> throw IllegalArgumentException("Not a corner") + } +} + +private fun Int.innerGravity(): Int { + return when (this) { + TOP_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT + TOP_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT + BOTTOM_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT + BOTTOM_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT + else -> throw IllegalArgumentException("Not a corner") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt new file mode 100644 index 000000000000..6380dc0b9b46 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt @@ -0,0 +1,67 @@ +/* + * 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.statusbar.events + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import com.android.settingslib.graph.ThemedBatteryDrawable +import com.android.systemui.R +import com.android.systemui.privacy.OngoingPrivacyChip +import com.android.systemui.privacy.PrivacyItem + +interface StatusEvent { + val priority: Int + // Whether or not to force the status bar open and show a dot + val forceVisible: Boolean + val viewCreator: (context: Context) -> View +} + +class BatteryEvent : StatusEvent { + override val priority = 50 + override val forceVisible = false + + override val viewCreator: (context: Context) -> View = { context -> + val iv = ImageView(context) + iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE)) + iv.setBackgroundDrawable(ColorDrawable(Color.GREEN)) + iv + } + + override fun toString(): String { + return javaClass.simpleName + } +} +class PrivacyEvent : StatusEvent { + override val priority = 100 + override val forceVisible = true + var privacyItems: List<PrivacyItem> = listOf() + + override val viewCreator: (context: Context) -> View = { context -> + val v = LayoutInflater.from(context) + .inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip + v.privacyList = privacyItems + v + } + + override fun toString(): String { + return javaClass.simpleName + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt new file mode 100644 index 000000000000..620963060b49 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt @@ -0,0 +1,137 @@ +/* + * 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.statusbar.events + +import android.animation.ValueAnimator +import android.content.Context +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.FrameLayout + +import com.android.systemui.R +import com.android.systemui.statusbar.SuperStatusBarViewFactory +import com.android.systemui.statusbar.phone.StatusBarWindowController +import com.android.systemui.statusbar.phone.StatusBarWindowView + +import javax.inject.Inject + +/** + * //TODO: this _probably_ doesn't control a window anymore + * Controls the window for system event animations. + */ +class SystemEventChipAnimationController @Inject constructor( + private val context: Context, + private val statusBarViewFactory: SuperStatusBarViewFactory, + private val statusBarWindowController: StatusBarWindowController +) : SystemStatusChipAnimationCallback { + var showPersistentDot = false + set(value) { + field = value + statusBarWindowController.setForceStatusBarVisible(value) + maybeUpdateShowDot() + } + + private lateinit var animationWindowView: FrameLayout + private lateinit var animationDotView: View + private lateinit var statusBarWindowView: StatusBarWindowView + private var currentAnimatedView: View? = null + + // TODO: move to dagger + private var initialized = false + + override fun onChipAnimationStart( + viewCreator: (context: Context) -> View, + @SystemAnimationState state: Int + ) { + if (!initialized) init() + + if (state == ANIMATING_IN) { + currentAnimatedView = viewCreator(context) + animationWindowView.addView(currentAnimatedView, layoutParamsDefault) + + // We are animating IN; chip comes in from View.END + currentAnimatedView?.apply { + translationX = width.toFloat() + alpha = 0f + visibility = View.VISIBLE + } + } else { + // We are animating away + currentAnimatedView?.apply { + translationX = 0f + alpha = 1f + } + } + } + + override fun onChipAnimationEnd(@SystemAnimationState state: Int) { + if (state == ANIMATING_IN) { + // Finished animating in + currentAnimatedView?.apply { + translationX = 0f + alpha = 1f + } + } else { + // Finished animating away + currentAnimatedView?.apply { + visibility = View.INVISIBLE + } + animationWindowView.removeView(currentAnimatedView) + } + } + + override fun onChipAnimationUpdate( + animator: ValueAnimator, + @SystemAnimationState state: Int + ) { + // Alpha is parameterized 0,1, and translation from (width, 0) + currentAnimatedView?.apply { + val amt = animator.animatedValue as Float + + alpha = amt + + val w = width + val translation = (1 - amt) * w + translationX = translation + } + } + + private fun maybeUpdateShowDot() { + if (!initialized) return + if (!showPersistentDot && currentAnimatedView == null) { + animationDotView.visibility = View.INVISIBLE + } + } + + private fun init() { + initialized = true + statusBarWindowView = statusBarViewFactory.statusBarWindowView + animationWindowView = LayoutInflater.from(context) + .inflate(R.layout.system_event_animation_window, null) as FrameLayout + animationDotView = animationWindowView.findViewById(R.id.dot_view) + val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL + statusBarWindowView.addView(animationWindowView, lp) + } + + private val layoutParamsDefault = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also { + it.gravity = Gravity.END or Gravity.CENTER_VERTICAL + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt new file mode 100644 index 000000000000..b4818233aea3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt @@ -0,0 +1,112 @@ +/* + * 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.statusbar.events + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.privacy.PrivacyItem +import com.android.systemui.privacy.PrivacyItemController +import com.android.systemui.statusbar.policy.BatteryController +import javax.inject.Inject + +/** + * Listens for system events (battery, privacy, connectivity) and allows listeners + * to show status bar animations when they happen + */ +@SysUISingleton +class SystemEventCoordinator @Inject constructor( + private val batteryController: BatteryController, + private val privacyController: PrivacyItemController +) { + private lateinit var scheduler: SystemStatusAnimationScheduler + + fun startObserving() { + /* currently unused + batteryController.addCallback(batteryStateListener) + */ + privacyController.addCallback(privacyStateListener) + } + + fun stopObserving() { + /* currently unused + batteryController.removeCallback(batteryStateListener) + */ + privacyController.removeCallback(privacyStateListener) + } + + fun attachScheduler(s: SystemStatusAnimationScheduler) { + this.scheduler = s + } + + fun notifyPluggedIn() { + scheduler.onStatusEvent(BatteryEvent()) + } + + fun notifyPrivacyItemsEmpty() { + scheduler.setShouldShowPersistentPrivacyIndicator(false) + } + + fun notifyPrivacyItemsChanged() { + val event = PrivacyEvent() + event.privacyItems = privacyStateListener.currentPrivacyItems + scheduler.onStatusEvent(event) + } + + private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback { + var plugged = false + var stateKnown = false + override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { + if (!stateKnown) { + stateKnown = true + plugged = pluggedIn + notifyListeners() + return + } + + if (plugged != pluggedIn) { + plugged = pluggedIn + notifyListeners() + } + } + + private fun notifyListeners() { + // We only care about the plugged in status + if (plugged) notifyPluggedIn() + } + } + + private val privacyStateListener = object : PrivacyItemController.Callback { + var currentPrivacyItems = listOf<PrivacyItem>() + + override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) { + if (privacyItems.isNotEmpty() && currentPrivacyItems.containsAll(privacyItems)) { + return + } + currentPrivacyItems = privacyItems + notifyListeners() + } + + private fun notifyListeners() { + if (currentPrivacyItems.isEmpty()) { + notifyPrivacyItemsEmpty() + } else { + notifyPrivacyItemsChanged() + } + } + } +} + +private const val TAG = "SystemEventCoordinator"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt new file mode 100644 index 000000000000..b85d0310092c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt @@ -0,0 +1,333 @@ +/* + * 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.statusbar.events + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.animation.ValueAnimator +import android.annotation.IntDef +import android.content.Context +import android.os.Process +import android.util.Log +import android.view.View + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.phone.StatusBarWindowController +import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.util.Assert +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.time.SystemClock + +import javax.inject.Inject + +/** + * Dead-simple scheduler for system status events. Obeys the following principles (all values TBD): + * - Avoiding log spam by only allowing 12 events per minute (1event/5s) + * - Waits 100ms to schedule any event for debouncing/prioritization + * - Simple prioritization: Privacy > Battery > connectivity (encoded in StatusEvent) + * - Only schedules a single event, and throws away lowest priority events + * + * There are 4 basic stages of animation at play here: + * 1. System chrome animation OUT + * 2. Chip animation IN + * 3. Chip animation OUT; potentially into a dot + * 4. System chrome animation IN + * + * Thus we can keep all animations synchronized with two separate ValueAnimators, one for system + * chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize + * their respective views based on the progress of the animator. Interpolation differences TBD + */ +@SysUISingleton +class SystemStatusAnimationScheduler @Inject constructor( + private val coordinator: SystemEventCoordinator, + private val chipAnimationController: SystemEventChipAnimationController, + private val statusBarWindowController: StatusBarWindowController, + private val systemClock: SystemClock, + @Main private val executor: DelayableExecutor +) : CallbackController<SystemStatusAnimationCallback> { + + /** True from the time a scheduled event starts until it's animation finishes */ + var isActive = false + private set + + @SystemAnimationState var animationState: Int = IDLE + private set + + /** True if the persistent privacy dot should be active */ + var hasPersistentDot = false + private set + + private var scheduledEvent: StatusEvent? = null + private var cancelExecutionRunnable: Runnable? = null + private val listeners = mutableSetOf<SystemStatusAnimationCallback>() + + init { + coordinator.attachScheduler(this) + } + + fun onStatusEvent(event: StatusEvent) { + // Ignore any updates until the system is up and running + if (isTooEarly()) { + return + } + + // Don't deal with threading for now (no need let's be honest) + Assert.isMainThread() + if (event.priority > scheduledEvent?.priority ?: -1) { + if (DEBUG) { + Log.d(TAG, "scheduling event $event") + } + scheduleEvent(event) + } else { + if (DEBUG) { + Log.d(TAG, "ignoring event $event") + } + } + } + + private fun clearDotIfVisible() { + notifyHidePersistentDot() + } + + fun setShouldShowPersistentPrivacyIndicator(should: Boolean) { + if (hasPersistentDot == should) { + return + } + + hasPersistentDot = should + + if (!hasPersistentDot) { + clearDotIfVisible() + } + } + + private fun isTooEarly(): Boolean { + Log.d(TAG, "time=> ${systemClock.uptimeMillis() - Process.getStartUptimeMillis()}") + return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME + } + + /** + * Clear the scheduled event (if any) and schedule a new one + */ + private fun scheduleEvent(event: StatusEvent) { + scheduledEvent = event + if (scheduledEvent!!.forceVisible) { + hasPersistentDot = true + } + + // Schedule the animation to start after a debounce period + cancelExecutionRunnable = executor.executeDelayed({ + cancelExecutionRunnable = null + animationState = ANIMATING_IN + statusBarWindowController.setForceStatusBarVisible(true) + + val entranceAnimator = ValueAnimator.ofFloat(1f, 0f) + entranceAnimator.duration = ENTRANCE_ANIM_LENGTH + entranceAnimator.addListener(systemAnimatorAdapter) + entranceAnimator.addUpdateListener(systemUpdateListener) + + val chipAnimator = ValueAnimator.ofFloat(0f, 1f) + chipAnimator.duration = CHIP_ANIM_LENGTH + chipAnimator.addListener( + ChipAnimatorAdapter(RUNNING_CHIP_ANIM, scheduledEvent!!.viewCreator)) + chipAnimator.addUpdateListener(chipUpdateListener) + + val aSet2 = AnimatorSet() + aSet2.playSequentially(entranceAnimator, chipAnimator) + aSet2.start() + + executor.executeDelayed({ + animationState = ANIMATING_OUT + + val systemAnimator = ValueAnimator.ofFloat(0f, 1f) + systemAnimator.duration = ENTRANCE_ANIM_LENGTH + systemAnimator.addListener(systemAnimatorAdapter) + systemAnimator.addUpdateListener(systemUpdateListener) + + val chipAnimator = ValueAnimator.ofFloat(1f, 0f) + chipAnimator.duration = CHIP_ANIM_LENGTH + chipAnimator.addListener(ChipAnimatorAdapter(IDLE, scheduledEvent!!.viewCreator)) + chipAnimator.addUpdateListener(chipUpdateListener) + + val aSet2 = AnimatorSet() + + aSet2.play(chipAnimator).before(systemAnimator) + if (hasPersistentDot) { + val dotAnim = notifyTransitionToPersistentDot() + if (dotAnim != null) aSet2.playTogether(systemAnimator, dotAnim) + } + + aSet2.start() + + statusBarWindowController.setForceStatusBarVisible(false) + scheduledEvent = null + }, DISPLAY_LENGTH) + }, DELAY) + } + + private fun notifyTransitionToPersistentDot(): Animator? { + val anims: List<Animator> = listeners.mapNotNull { + it.onSystemStatusAnimationTransitionToPersistentDot() + } + if (anims.isNotEmpty()) { + val aSet = AnimatorSet() + aSet.playTogether(anims) + return aSet + } + + return null + } + + private fun notifyHidePersistentDot(): Animator? { + val anims: List<Animator> = listeners.mapNotNull { + it.onHidePersistentDot() + } + + if (anims.isNotEmpty()) { + val aSet = AnimatorSet() + aSet.playTogether(anims) + return aSet + } + + return null + } + + private fun notifySystemStart() { + listeners.forEach { it.onSystemChromeAnimationStart() } + } + + private fun notifySystemFinish() { + listeners.forEach { it.onSystemChromeAnimationEnd() } + } + + private fun notifySystemAnimationUpdate(anim: ValueAnimator) { + listeners.forEach { it.onSystemChromeAnimationUpdate(anim) } + } + + override fun addCallback(listener: SystemStatusAnimationCallback) { + Assert.isMainThread() + + if (listeners.isEmpty()) { + coordinator.startObserving() + } + listeners.add(listener) + } + + override fun removeCallback(listener: SystemStatusAnimationCallback) { + Assert.isMainThread() + + listeners.remove(listener) + if (listeners.isEmpty()) { + coordinator.stopObserving() + } + } + + private val systemUpdateListener = ValueAnimator.AnimatorUpdateListener { + anim -> notifySystemAnimationUpdate(anim) + } + + private val systemAnimatorAdapter = object : AnimatorListenerAdapter() { + override fun onAnimationEnd(p0: Animator?) { + notifySystemFinish() + } + + override fun onAnimationStart(p0: Animator?) { + notifySystemStart() + } + } + + private val chipUpdateListener = ValueAnimator.AnimatorUpdateListener { + anim -> chipAnimationController.onChipAnimationUpdate(anim, animationState) + } + + inner class ChipAnimatorAdapter( + @SystemAnimationState val endState: Int, + val viewCreator: (context: Context) -> View + ) : AnimatorListenerAdapter() { + override fun onAnimationEnd(p0: Animator?) { + chipAnimationController.onChipAnimationEnd(animationState) + animationState = endState + } + + override fun onAnimationStart(p0: Animator?) { + chipAnimationController.onChipAnimationStart(viewCreator, animationState) + } + } +} + +/** + * The general idea here is that this scheduler will run two value animators, and provide + * animator-like callbacks for each kind of animation. The SystemChrome animation is expected to + * create space for the chip animation to display. This means hiding the system elements in the + * status bar and keyguard. + * + * TODO: the chip animation really only has one client, we can probably remove it from this + * interface + * + * The value animators themselves are simple animators from 0.0 to 1.0. Listeners can apply any + * interpolation they choose but realistically these are most likely to be simple alpha transitions + */ +interface SystemStatusAnimationCallback { + @JvmDefault fun onSystemChromeAnimationUpdate(animator: ValueAnimator) {} + @JvmDefault fun onSystemChromeAnimationStart() {} + @JvmDefault fun onSystemChromeAnimationEnd() {} + + // Best method name, change my mind + @JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { return null } + @JvmDefault fun onHidePersistentDot(): Animator? { return null } +} + +interface SystemStatusChipAnimationCallback { + fun onChipAnimationUpdate(animator: ValueAnimator, @SystemAnimationState state: Int) {} + + fun onChipAnimationStart( + viewCreator: (context: Context) -> View, + @SystemAnimationState state: Int + ) {} + + fun onChipAnimationEnd(@SystemAnimationState state: Int) {} +} + +/** + */ +@Retention(AnnotationRetention.SOURCE) +@IntDef( + value = [ + IDLE, ANIMATING_IN, RUNNING_CHIP_ANIM, ANIMATING_OUT + ] +) +annotation class SystemAnimationState + +/** No animation is in progress */ +const val IDLE = 0 +/** System is animating out, and chip is animating in */ +const val ANIMATING_IN = 1 +/** Chip has animated in and is awaiting exit animation, and optionally playing its own animation */ +const val RUNNING_CHIP_ANIM = 2 +/** Chip is animating away and system is animating back */ +const val ANIMATING_OUT = 3 + +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 MIN_UPTIME: Long = 5 * 1000 + +private const val DEBUG = false
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java index 5748c4aa0b13..2537b19513d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification; import android.content.Intent; import android.service.notification.StatusBarNotification; +import android.view.View; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -33,7 +34,8 @@ public interface NotificationActivityStarter { void startNotificationGutsIntent(Intent intent, int appUid, ExpandableNotificationRow row); - void startHistoryIntent(boolean showHistory); + /** Called when the user clicks "Manage" or "History" in the Shade. */ + void startHistoryIntent(View view, boolean showHistory); default boolean isCollapsingToShowActivityOverLockscreen() { return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index 4ed5056866f2..3bf0ddb1985b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -106,10 +106,6 @@ public class FooterView extends StackScrollerDecorView { showHistory(mShowHistory); } - public boolean isButtonVisible() { - return mManageButton.getAlpha() != 0.0f; - } - @Override public ExpandableViewState createExpandableViewState() { return new FooterViewState(); 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 8446b4e6a3f0..caf47207de07 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 @@ -60,7 +60,7 @@ public class AmbientState { private NotificationShelf mShelf; private int mZDistanceBetweenElements; private int mBaseZHeight; - private int mMaxLayoutHeight; + private int mContentHeight; private ExpandableView mLastVisibleBackgroundChild; private float mCurrentScrollVelocity; private int mStatusBarState; @@ -84,6 +84,75 @@ public class AmbientState { private boolean mIsShadeOpening; private float mSectionPadding; + /** Distance of top of notifications panel from top of screen. */ + private float mStackY = 0; + + /** Height of notifications panel. */ + private float mStackHeight = 0; + + /** Fraction of shade expansion. */ + private float mExpansionFraction; + + /** Height of the notifications panel without top padding when expansion completes. */ + private float mStackEndHeight; + + /** + * @return Height of the notifications panel without top padding when expansion completes. + */ + public float getStackEndHeight() { + return mStackEndHeight; + } + + /** + * @param stackEndHeight Height of the notifications panel without top padding + * when expansion completes. + */ + public void setStackEndHeight(float stackEndHeight) { + mStackEndHeight = stackEndHeight; + } + + /** + * @param stackY Distance of top of notifications panel from top of screen. + */ + public void setStackY(float stackY) { + mStackY = stackY; + } + + /** + * @return Distance of top of notifications panel from top of screen. + */ + public float getStackY() { + return mStackY; + } + + /** + * @param expansionFraction Fraction of shade expansion. + */ + public void setExpansionFraction(float expansionFraction) { + mExpansionFraction = expansionFraction; + } + + /** + * @return Fraction of shade expansion. + */ + public float getExpansionFraction() { + return mExpansionFraction; + } + + /** + * @param stackHeight Height of notifications panel. + */ + public void setStackHeight(float stackHeight) { + mStackHeight = stackHeight; + } + + /** + * @return Height of notifications panel. + */ + public float getStackHeight() { + return mStackHeight; + } + /** Tracks the state from AlertingNotificationManager#hasNotifications() */ private boolean mHasAlertEntries; @@ -263,8 +332,8 @@ public class AmbientState { if (mDozeAmount == 1.0f && !isPulseExpanding()) { return mShelf.getHeight(); } - int height = Math.max(mLayoutMinHeight, - Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding); + int height = (int) Math.max(mLayoutMinHeight, + Math.min(mLayoutHeight, mContentHeight) - mTopPadding); if (ignorePulseHeight) { return height; } @@ -313,8 +382,12 @@ public class AmbientState { return mShelf; } - public void setLayoutMaxHeight(int maxLayoutHeight) { - mMaxLayoutHeight = maxLayoutHeight; + public void setContentHeight(int contentHeight) { + mContentHeight = contentHeight; + } + + public float getContentHeight() { + return mContentHeight; } /** 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 fb72ac3c1411..751573ac2cca 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 @@ -658,6 +658,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable y = getHeight() - getEmptyBottomMargin(); mDebugPaint.setColor(Color.GREEN); canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + + y = (int) (mAmbientState.getStackY()); + mDebugPaint.setColor(Color.CYAN); + canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + + y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight()); + mDebugPaint.setColor(Color.BLUE); + canvas.drawLine(0, y, getWidth(), y, mDebugPaint); } } @@ -1123,18 +1131,37 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mTopPaddingNeedsAnimation = true; mNeedsAnimation = true; } + updateStackPosition(); requestChildrenUpdate(); notifyHeightChangeListener(null, animate); } } /** + * Apply expansion fraction to the y position and height of the notifications panel. + */ + private void updateStackPosition() { + // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD + mAmbientState.setStackY( + MathUtils.lerp(0, mTopPadding, mAmbientState.getExpansionFraction())); + final float shadeBottom = getHeight() - getEmptyBottomMargin(); + mAmbientState.setStackEndHeight(shadeBottom - mTopPadding); + mAmbientState.setStackHeight( + MathUtils.lerp(0, shadeBottom - mTopPadding, mAmbientState.getExpansionFraction())); + } + + /** * Update the height of the panel. * * @param height the expanded height of the panel */ @ShadeViewRefactor(RefactorComponent.COORDINATOR) public void setExpandedHeight(float height) { + final float shadeBottom = getHeight() - getEmptyBottomMargin(); + final float expansionFraction = MathUtils.constrain(height / shadeBottom, 0f, 1f); + mAmbientState.setExpansionFraction(expansionFraction); + updateStackPosition(); + mExpandedHeight = height; setIsExpanded(height > 0); int minExpansionHeight = getMinExpansionHeight(); @@ -2067,7 +2094,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin; updateScrollability(); clampScrollPosition(); - mAmbientState.setLayoutMaxHeight(mContentHeight); + updateStackPosition(); + mAmbientState.setContentHeight(mContentHeight); } /** @@ -4849,7 +4877,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable clearNotifications(ROWS_ALL, true /* closeShade */); }); footerView.setManageButtonClickListener(v -> { - mNotificationActivityStarter.startHistoryIntent(mFooterView.isHistoryShown()); + mNotificationActivityStarter.startHistoryIntent(v, mFooterView.isHistoryShown()); }); setFooterView(footerView); } 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 7776e69d9bed..4fc49ed26f64 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 @@ -788,6 +788,10 @@ public class NotificationStackScrollLayoutController { return mView.getTranslationX(); } + public int indexOfChild(View view) { + return mView.indexOfChild(view); + } + public void setOnHeightChangedListener( ExpandableView.OnHeightChangedListener listener) { mView.setOnHeightChangedListener(listener); 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 bbdbe809a8d8..3e1a7816c2b2 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 @@ -20,21 +20,22 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; -import android.util.Log; import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; -import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; +import com.android.systemui.statusbar.notification.dagger.SilentHeader; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.FooterView; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * The Algorithm of the {@link com.android.systemui.statusbar.notification.stack @@ -92,20 +93,16 @@ public class StackScrollAlgorithm { // First we reset the view states to their default values. resetChildViewStates(); - initAlgorithmState(mHostView, algorithmState, ambientState); - updatePositionsForState(algorithmState, ambientState); - updateZValuesForState(algorithmState, ambientState); - updateHeadsUpStates(algorithmState, ambientState); updatePulsingStates(algorithmState, ambientState); updateDimmedActivatedHideSensitive(ambientState, algorithmState); updateClipping(algorithmState, ambientState); updateSpeedBumpState(algorithmState, speedBumpIndex); - updateShelfState(ambientState); + updateShelfState(algorithmState, ambientState); getNotificationChildrenStates(algorithmState, ambientState); } @@ -144,10 +141,13 @@ public class StackScrollAlgorithm { } - private void updateShelfState(AmbientState ambientState) { + private void updateShelfState( + StackScrollAlgorithmState algorithmState, + AmbientState ambientState) { + NotificationShelf shelf = ambientState.getShelf(); if (shelf != null) { - shelf.updateState(ambientState); + shelf.updateState(algorithmState, ambientState); } } @@ -172,7 +172,8 @@ public class StackScrollAlgorithm { && ((ExpandableNotificationRow) child).isPinned(); if (mClipNotificationScrollToTop && (!state.inShelf || (isHeadsUp && !firstHeadsUp)) - && newYTranslation < clipStart) { + && newYTranslation < clipStart + && !ambientState.isShadeOpening()) { // The previous view is overlapping on top, clip! float overlapAmount = clipStart - newYTranslation; state.clipTopAmount = (int) overlapAmount; @@ -217,7 +218,6 @@ public class StackScrollAlgorithm { private void initAlgorithmState(ViewGroup hostView, StackScrollAlgorithmState state, AmbientState ambientState) { float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */); - int scrollY = ambientState.getScrollY(); // Due to the overScroller, the stackscroller can have negative scroll state. This is @@ -230,7 +230,6 @@ public class StackScrollAlgorithm { state.visibleChildren.clear(); state.visibleChildren.ensureCapacity(childCount); int notGoneIndex = 0; - ExpandableView lastView = null; for (int i = 0; i < childCount; i++) { ExpandableView v = (ExpandableView) hostView.getChildAt(i); if (v.getVisibility() != View.GONE) { @@ -255,12 +254,101 @@ public class StackScrollAlgorithm { } } } - ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification(); - state.indexOfExpandingNotification = expandingNotification != null - ? expandingNotification.isChildInGroup() - ? state.visibleChildren.indexOf(expandingNotification.getNotificationParent()) - : state.visibleChildren.indexOf(expandingNotification) - : -1; + + state.firstViewInShelf = null; + // Save y, sectionStart, sectionEnd from when shade is fully expanded. + // Consider updating these states in updateContentView instead so that we don't have to + // recalculate in every frame. + float currentY = -scrollY; + int sectionStartIndex = 0; + int sectionEndIndex = 0; + for (int i = 0; i < state.visibleChildren.size(); i++) { + final ExpandableView view = state.visibleChildren.get(i); + // Add space between sections. + final boolean applyGapHeight = childNeedsGapHeight( + ambientState.getSectionProvider(), i, + view, getPreviousView(i, state)); + if (applyGapHeight) { + currentY += mGapHeight; + } + + // Save index of first view in the shelf + final float shelfStart = ambientState.getStackEndHeight() + - ambientState.getShelf().getIntrinsicHeight(); + if (currentY >= shelfStart + && !(view instanceof FooterView) + && state.firstViewInShelf == null) { + state.firstViewInShelf = view; + } + + // Record y position when fully expanded + ExpansionData expansionData = new ExpansionData(); + expansionData.fullyExpandedY = currentY; + state.expansionData.put(view, expansionData); + + if (ambientState.getSectionProvider() + .beginsSection(view, getPreviousView(i, state))) { + + // Save section start/end for views in the section before this new section + ExpandableView sectionStartView = state.visibleChildren.get(sectionStartIndex); + final float sectionStart = + state.expansionData.get(sectionStartView).fullyExpandedY; + + ExpandableView sectionEndView = state.visibleChildren.get(sectionEndIndex); + float sectionEnd = state.expansionData.get(sectionEndView).fullyExpandedY + + sectionEndView.getIntrinsicHeight(); + + // If we show the shelf, trim section end to shelf start + // This means section end > start for views in the shelf + if (state.firstViewInShelf != null && sectionEnd > shelfStart) { + sectionEnd = shelfStart; + } + + // Update section bounds of every view in the previous section + // Consider using shared SectionInfo for views in same section to avoid looping back + for (int j = sectionStartIndex; j < i; j++) { + ExpandableView sectionView = state.visibleChildren.get(j); + ExpansionData viewExpansionData = + state.expansionData.get(sectionView); + viewExpansionData.sectionStart = sectionStart; + viewExpansionData.sectionEnd = sectionEnd; + state.expansionData.put(sectionView, viewExpansionData); + } + sectionStartIndex = i; + + if (view instanceof FooterView) { + // Also record section bounds for FooterView (same as its own) + // because it is the last view and we won't get to this point again + // after the loop ends + ExpansionData footerExpansionData = state.expansionData.get(view); + footerExpansionData.sectionStart = expansionData.fullyExpandedY; + footerExpansionData.sectionEnd = expansionData.fullyExpandedY + + view.getIntrinsicHeight(); + state.expansionData.put(view, footerExpansionData); + } + } + sectionEndIndex = i; + currentY = currentY + + getMaxAllowedChildHeight(view) + + mPaddingBetweenElements; + } + + // Which view starts the section of the view right before the shelf? + // Save it for later when we clip views in that section to shelf start. + state.firstViewInOverflowSection = null; + if (state.firstViewInShelf != null) { + ExpandableView nextView = null; + final int startIndex = state.visibleChildren.indexOf(state.firstViewInShelf); + for (int i = startIndex - 1; i >= 0; i--) { + ExpandableView view = state.visibleChildren.get(i); + if (nextView != null && ambientState.getSectionProvider() + .beginsSection(nextView, view)) { + break; + } + nextView = view; + } + state.firstViewInOverflowSection = nextView; + } } private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex, @@ -272,6 +360,10 @@ public class StackScrollAlgorithm { return notGoneIndex; } + private ExpandableView getPreviousView(int i, StackScrollAlgorithmState algorithmState) { + return i > 0 ? algorithmState.visibleChildren.get(i - 1) : null; + } + /** * Determine the positions for the views. This is the main part of the algorithm. * @@ -288,6 +380,15 @@ public class StackScrollAlgorithm { } } + private void setLocation(ExpandableViewState expandableViewState, float currentYPosition, + int i) { + expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; + if (currentYPosition <= 0) { + expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; + } + } + + // TODO(b/172289889) polish shade open from HUN /** * Populates the {@link ExpandableViewState} for a single child. * @@ -306,53 +407,84 @@ public class StackScrollAlgorithm { StackScrollAlgorithmState algorithmState, AmbientState ambientState, float currentYPosition) { - ExpandableView child = algorithmState.visibleChildren.get(i); - ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null; + + ExpandableView view = algorithmState.visibleChildren.get(i); + ExpandableViewState viewState = view.getViewState(); + viewState.location = ExpandableViewState.LOCATION_UNKNOWN; + viewState.alpha = 1f - ambientState.getHideAmount(); + + if (view.mustStayOnScreen() && viewState.yTranslation >= 0) { + // Even if we're not scrolled away we're in view and we're also not in the + // shelf. We can relax the constraints and let us scroll off the top! + float end = viewState.yTranslation + viewState.height + ambientState.getStackY(); + viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation(); + } + + // TODO(b/172289889) move sectionFraction and showSection to initAlgorithmState + // Get fraction of section showing, and later apply it to view height and gaps between views + float sectionFraction = 1f; + boolean showSection = true; + + if (!ambientState.isOnKeyguard() + && !ambientState.isPulseExpanding() + && ambientState.isExpansionChanging()) { + + final ExpansionData expansionData = algorithmState.expansionData.get(view); + final float sectionHeight = expansionData.sectionEnd - expansionData.sectionStart; + sectionFraction = MathUtils.constrain( + (ambientState.getStackHeight() - expansionData.sectionStart) / sectionHeight, + 0f, 1f); + showSection = expansionData.sectionStart < ambientState.getStackHeight(); + } + + // Add gap between sections. final boolean applyGapHeight = childNeedsGapHeight( ambientState.getSectionProvider(), i, - child, previousChild); - ExpandableViewState childViewState = child.getViewState(); - childViewState.location = ExpandableViewState.LOCATION_UNKNOWN; - + view, getPreviousView(i, algorithmState)); if (applyGapHeight) { - currentYPosition += mGapHeight; + currentYPosition += sectionFraction * mGapHeight; } - int childHeight = getMaxAllowedChildHeight(child); - childViewState.yTranslation = currentYPosition; - childViewState.alpha = 1f - ambientState.getHideAmount(); - boolean isFooterView = child instanceof FooterView; - boolean isEmptyShadeView = child instanceof EmptyShadeView; + viewState.yTranslation = currentYPosition; - childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; - float inset = ambientState.getTopPadding() + ambientState.getStackTranslation() - + ambientState.getSectionPadding(); - if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) { - // Even if we're not scrolled away we're in view and we're also not in the - // shelf. We can relax the constraints and let us scroll off the top! - float end = childViewState.yTranslation + childViewState.height + inset; - childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation(); - } - if (isFooterView) { - childViewState.yTranslation = Math.min(childViewState.yTranslation, - ambientState.getInnerHeight() - childHeight); - } else if (isEmptyShadeView) { - childViewState.yTranslation = ambientState.getInnerHeight() - childHeight - + ambientState.getStackTranslation() * 0.25f; - } else if (child != ambientState.getTrackedHeadsUpRow()) { - clampPositionToShelf(child, childViewState, ambientState); + if (view instanceof SectionHeaderView) { + // Add padding before sections for overscroll effect. + viewState.yTranslation += ambientState.getSectionPadding(); } - currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; - if (currentYPosition <= 0) { - childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; - } - if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) { - Log.wtf(LOG_TAG, "Failed to assign location for child " + i); + if (view != ambientState.getTrackedHeadsUpRow()) { + if (ambientState.isExpansionChanging()) { + viewState.hidden = !showSection; + viewState.inShelf = algorithmState.firstViewInShelf != null + && i >= algorithmState.visibleChildren.indexOf( + algorithmState.firstViewInShelf) + && !(view instanceof FooterView); + } else { + // When pulsing (incoming notification on AOD), innerHeight is 0; clamp all + // to shelf start, thereby hiding all notifications (except the first one, which we + // later unhide in updatePulsingState) + final int shelfStart = ambientState.getInnerHeight() + - ambientState.getShelf().getIntrinsicHeight(); + if (!(view instanceof FooterView)) { + viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart); + } + if (viewState.yTranslation >= shelfStart) { + viewState.hidden = !view.isExpandAnimationRunning() + && !view.hasExpandingChild() + && !(view instanceof FooterView); + viewState.inShelf = true; + // Notifications in the shelf cannot be visible HUNs. + viewState.headsUpIsVisible = false; + } + } + viewState.height = (int) MathUtils.lerp( + 0, getMaxAllowedChildHeight(view), sectionFraction); } - childViewState.yTranslation += inset; + currentYPosition += viewState.height + sectionFraction * mPaddingBetweenElements; + setLocation(view.getViewState(), currentYPosition, i); + viewState.yTranslation += ambientState.getStackY(); return currentYPosition; } @@ -393,10 +525,10 @@ public class StackScrollAlgorithm { int visibleIndex, View child, View previousChild) { - - boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild) - && visibleIndex > 0; - return needsGapHeight; + return sectionProvider.beginsSection(child, previousChild) + && visibleIndex > 0 + && !(previousChild instanceof SilentHeader) + && !(child instanceof FooterView); } private void updatePulsingStates(StackScrollAlgorithmState algorithmState, @@ -514,42 +646,6 @@ public class StackScrollAlgorithm { childState.yTranslation = newTranslation; } - /** - * Clamp the height of the child down such that its end is at most on the beginning of - * the shelf. - * - * @param childViewState the view state of the child - * @param ambientState the ambient state - */ - private void clampPositionToShelf(ExpandableView child, - ExpandableViewState childViewState, - AmbientState ambientState) { - if (ambientState.getShelf() == null) { - return; - } - - ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow(); - boolean isBeforeTrackedHeadsUp = trackedHeadsUpRow != null - && mHostView.indexOfChild(child) < mHostView.indexOfChild(trackedHeadsUpRow); - - int shelfStart = ambientState.getInnerHeight() - - ambientState.getShelf().getIntrinsicHeight(); - if (ambientState.isAppearing() && !child.isAboveShelf() && !isBeforeTrackedHeadsUp) { - // Don't show none heads-up notifications while in appearing phase. - childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart); - } - childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart); - if (child instanceof SectionHeaderView) { - // Add padding before sections for overscroll effect. - childViewState.yTranslation += ambientState.getSectionPadding(); - } - if (childViewState.yTranslation >= shelfStart) { - childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild(); - childViewState.inShelf = true; - childViewState.headsUpIsVisible = false; - } - } - protected int getMaxAllowedChildHeight(View child) { if (child instanceof ExpandableView) { ExpandableView expandableView = (ExpandableView) child; @@ -641,6 +737,35 @@ public class StackScrollAlgorithm { this.mIsExpanded = isExpanded; } + /** + * Data used to layout views while shade expansion changes. + */ + public class ExpansionData { + + /** + * Y position of top of first view in section. + */ + public float sectionStart; + + /** + * Y position of bottom of last view in section. + */ + public float sectionEnd; + + /** + * Y position of view when shade is fully expanded. + * Does not include distance between top notifications panel and top of screen. + */ + public float fullyExpandedY; + + /** + * Whether this notification is in the same section as the notification right before the + * shelf. Used to determine which notification should be clipped to shelf start while + * shade expansion changes. + */ + public boolean inOverflowingSection; + } + public class StackScrollAlgorithmState { /** @@ -649,15 +774,26 @@ public class StackScrollAlgorithm { public int scrollY; /** - * The children from the host view which are not gone. + * First view in shelf. */ - public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>(); + public ExpandableView firstViewInShelf; - private int indexOfExpandingNotification; + /** + * First view in section overflowing into shelf while shade expansion changes. + */ + public ExpandableView firstViewInOverflowSection; - public int getIndexOfExpandingNotification() { - return indexOfExpandingNotification; - } + /** + * Map of view to ExpansionData used for layout during shade expansion. + * Use view instead of index as key, because visibleChildren indices do not match the ones + * used in the shelf. + */ + public Map<ExpandableView, ExpansionData> expansionData = new HashMap<>(); + + /** + * The children from the host view which are not gone. + */ + public final ArrayList<ExpandableView> visibleChildren = new ArrayList<>(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 40e6b40ffa14..4e57e44f38d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -19,10 +19,16 @@ import static android.app.StatusBarManager.DISABLE_CLOCK; import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS; import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE; + +import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.Fragment; import android.os.Bundle; import android.os.Parcelable; +import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; @@ -36,6 +42,9 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; @@ -44,6 +53,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import org.jetbrains.annotations.NotNull; + import java.util.ArrayList; import java.util.List; @@ -55,7 +66,8 @@ import javax.inject.Inject; * updated by the StatusBarIconController and DarkIconManager while it is attached. */ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks, - StatusBarStateController.StateListener { + StatusBarStateController.StateListener, + SystemStatusAnimationCallback { public static final String TAG = "CollapsedStatusBarFragment"; private static final String EXTRA_PANEL_STATE = "panel_state"; @@ -78,6 +90,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private View mOperatorNameFrame; private CommandQueue mCommandQueue; private OngoingCallController mOngoingCallController; + private final SystemStatusAnimationScheduler mAnimationScheduler; + private final PrivacyDotViewController mDotViewController; private List<String> mBlockedIcons = new ArrayList<>(); @@ -103,8 +117,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue }; @Inject - public CollapsedStatusBarFragment(OngoingCallController ongoingCallController) { + public CollapsedStatusBarFragment( + OngoingCallController ongoingCallController, + SystemStatusAnimationScheduler animationScheduler, + PrivacyDotViewController dotViewController + ) { mOngoingCallController = ongoingCallController; + mAnimationScheduler = animationScheduler; + mDotViewController = dotViewController; } @Override @@ -127,6 +147,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mStatusBar = (PhoneStatusBarView) view; + View contents = mStatusBar.findViewById(R.id.status_bar_contents); + contents.addOnLayoutChangeListener(mStatusBarLayoutListener); + updateStatusBarLocation(contents.getLeft(), contents.getRight()); if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) { mStatusBar.restoreHierarchyState( savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE)); @@ -143,6 +166,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue showClock(false); initEmergencyCryptkeeperText(); initOperatorName(); + mAnimationScheduler.addCallback(this); } @Override @@ -208,6 +232,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue if (displayId != getContext().getDisplayId()) { return; } + Log.d(TAG, "disable: "); state1 = adjustDisableFlags(state1); final int old1 = mDisabled1; final int diff1 = state1 ^ old1; @@ -292,19 +317,22 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue return false; } - public void hideSystemIconArea(boolean animate) { + private void hideSystemIconArea(boolean animate) { animateHide(mSystemIconArea, animate); } - public void showSystemIconArea(boolean animate) { - animateShow(mSystemIconArea, animate); + private void showSystemIconArea(boolean animate) { + // Only show the system icon area if we are not currently animating + if (mAnimationScheduler.getAnimationState() == IDLE) { + animateShow(mSystemIconArea, animate); + } } - public void hideClock(boolean animate) { + private void hideClock(boolean animate) { animateHiddenState(mClockView, clockHiddenMode(), animate); } - public void showClock(boolean animate) { + private void showClock(boolean animate) { animateShow(mClockView, animate); } @@ -425,12 +453,60 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } @Override - public void onStateChanged(int newState) { - - } + public void onStateChanged(int newState) { } @Override public void onDozingChanged(boolean isDozing) { disable(getContext().getDisplayId(), mDisabled1, mDisabled1, false /* animate */); } + + @Override + public void onSystemChromeAnimationStart() { + if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT + && !isSystemIconAreaDisabled()) { + mSystemIconArea.setVisibility(View.VISIBLE); + mSystemIconArea.setAlpha(0f); + } + } + + @Override + public void onSystemChromeAnimationEnd() { + // Make sure the system icons are out of the way + if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) { + mSystemIconArea.setVisibility(View.INVISIBLE); + mSystemIconArea.setAlpha(0f); + } else { + if (isSystemIconAreaDisabled()) { + // don't unhide + return; + } + + mSystemIconArea.setAlpha(1f); + mSystemIconArea.setVisibility(View.VISIBLE); + } + } + + @Override + public void onSystemChromeAnimationUpdate(@NotNull ValueAnimator animator) { + mSystemIconArea.setAlpha((float) animator.getAnimatedValue()); + } + + private boolean isSystemIconAreaDisabled() { + return (mDisabled1 & DISABLE_SYSTEM_INFO) != 0 || (mDisabled2 & DISABLE2_SYSTEM_ICONS) != 0; + } + + private void updateStatusBarLocation(int left, int right) { + int leftMargin = left - mStatusBar.getLeft(); + int rightMargin = mStatusBar.getRight() - right; + + mDotViewController.setStatusBarMargins(leftMargin, rightMargin); + } + + // Listen for view end changes of PhoneStatusBarView and publish that to the privacy dot + private View.OnLayoutChangeListener mStatusBarLayoutListener = + (view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + if (left != oldLeft || right != oldRight) { + updateStatusBarLocation(left, right); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index c3325b103022..b83039b9f7f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -68,6 +68,7 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; @@ -154,6 +155,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private StatusBar mStatusBar; private KeyguardAffordanceHelper mAffordanceHelper; private FalsingManager mFalsingManager; + @Nullable private Executor mUiExecutor; private boolean mUserSetupComplete; private boolean mPrewarmBound; private Messenger mPrewarmMessenger; @@ -429,7 +431,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } private void updateWalletVisibility() { - if (mDozing || !mWalletEnabled) { + if (mDozing || !mWalletEnabled || !mHasCard) { mWalletButton.setVisibility(GONE); } else { mWalletButton.setVisibility(VISIBLE); @@ -659,6 +661,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL updateCameraVisibility(); } + @Override + public void onKeyguardShowingChanged() { + if (mKeyguardStateController.isShowing()) { + queryWalletCards(); + } + } + private void inflateCameraPreview() { View previewBefore = mCameraPreview; boolean visibleBefore = false; @@ -897,18 +906,20 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public void initWallet(QuickAccessWalletClient client, Executor uiExecutor, boolean enabled) { mQuickAccessWalletClient = client; mWalletEnabled = enabled && client.isWalletFeatureAvailable(); + mUiExecutor = uiExecutor; + queryWalletCards(); - if (mWalletEnabled) { - queryWalletCards(uiExecutor); - } updateWalletVisibility(); } - private void queryWalletCards(Executor uiExecutor) { + private void queryWalletCards() { + if (!mWalletEnabled || mUiExecutor == null) { + return; + } GetWalletCardsRequest request = new GetWalletCardsRequest(1 /* cardWidth */, 1 /* cardHeight */, 1 /* iconSizePx */, 2 /* maxCards */); - mQuickAccessWalletClient.getWalletCards(uiExecutor, request, mCardRetriever); + mQuickAccessWalletClient.getWalletCards(mUiExecutor, request, mCardRetriever); } private void onWalletClick(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 06947376b9f1..c22fec97db01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -18,7 +18,10 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT; +import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.content.Context; import android.content.res.Configuration; @@ -47,6 +50,8 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -64,8 +69,11 @@ import java.util.List; /** * The header group on Keyguard. */ -public class KeyguardStatusBarView extends RelativeLayout - implements BatteryStateChangeCallback, OnUserInfoChangedListener, ConfigurationListener { +public class KeyguardStatusBarView extends RelativeLayout implements + BatteryStateChangeCallback, + OnUserInfoChangedListener, + ConfigurationListener, + SystemStatusAnimationCallback { private static final int LAYOUT_NONE = 0; private static final int LAYOUT_CUTOUT = 1; @@ -96,6 +104,8 @@ public class KeyguardStatusBarView extends RelativeLayout private ViewGroup mStatusIconArea; private int mLayoutState = LAYOUT_NONE; + private SystemStatusAnimationScheduler mAnimationScheduler; + /** * Draw this many pixels into the left/right side of the cutout to optimally use the space */ @@ -125,6 +135,7 @@ public class KeyguardStatusBarView extends RelativeLayout loadDimens(); loadBlockList(); mBatteryController = Dependency.get(BatteryController.class); + mAnimationScheduler = Dependency.get(SystemStatusAnimationScheduler.class); } @Override @@ -349,6 +360,7 @@ public class KeyguardStatusBarView extends RelativeLayout mIconManager = new TintedIconManager(findViewById(R.id.statusIcons)); mIconManager.setBlockList(mBlockedIcons); Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager); + mAnimationScheduler.addCallback(this); onThemeChanged(); } @@ -358,6 +370,7 @@ public class KeyguardStatusBarView extends RelativeLayout Dependency.get(UserInfoController.class).removeCallback(this); Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager); Dependency.get(ConfigurationController.class).removeCallback(this); + mAnimationScheduler.removeCallback(this); } @Override @@ -509,4 +522,30 @@ public class KeyguardStatusBarView extends RelativeLayout mBatteryView.dump(fd, pw, args); } } + + /** SystemStatusAnimationCallback */ + @Override + public void onSystemChromeAnimationStart() { + if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT) { + mSystemIconsContainer.setVisibility(View.VISIBLE); + mSystemIconsContainer.setAlpha(0f); + } + } + + @Override + public void onSystemChromeAnimationEnd() { + // Make sure the system icons are out of the way + if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) { + mSystemIconsContainer.setVisibility(View.INVISIBLE); + mSystemIconsContainer.setAlpha(0f); + } else { + mSystemIconsContainer.setAlpha(1f); + mSystemIconsContainer.setVisibility(View.VISIBLE); + } + } + + @Override + public void onSystemChromeAnimationUpdate(ValueAnimator anim) { + mSystemIconsContainer.setAlpha((float) anim.getAnimatedValue()); + } } 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 1c8bda7f7344..c4d884071a78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -2018,7 +2018,7 @@ public class NotificationPanelViewController extends PanelViewController { right = getView().getRight(); } else { top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding); - bottom = getExpandedHeight() - mSplitShadeNotificationsTopPadding; + bottom = mNotificationStackScrollLayoutController.getHeight(); left = mNotificationStackScrollLayoutController.getLeft(); right = mNotificationStackScrollLayoutController.getRight(); } 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 5aecb727517f..0c8122cc9afb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -666,7 +666,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW pw.println(TAG + ":"); pw.println(" mKeyguardDisplayMode=" + mKeyguardDisplayMode); pw.println(mCurrentState); - mNotificationShadeView.getViewRootImpl().dump(" ", pw); + if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) { + mNotificationShadeView.getViewRootImpl().dump(" ", pw); + } } @Override 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 d386ebde26bf..43d525d5098e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -208,6 +208,8 @@ import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.charging.WiredChargingRippleController; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; @@ -426,6 +428,8 @@ public class StatusBar extends SystemUI implements DemoMode, private final DemoModeController mDemoModeController; private NotificationsController mNotificationsController; private final OngoingCallController mOngoingCallController; + private final SystemStatusAnimationScheduler mAnimationScheduler; + private final PrivacyDotViewController mDotViewController; // expanded notifications // the sliding/resizing panel within the notification window @@ -794,6 +798,8 @@ public class StatusBar extends SystemUI implements DemoMode, BrightnessSlider.Factory brightnessSliderFactory, WiredChargingRippleController chargingRippleAnimationController, OngoingCallController ongoingCallController, + SystemStatusAnimationScheduler animationScheduler, + PrivacyDotViewController dotViewController, TunerService tunerService, FeatureFlags featureFlags) { super(context); @@ -875,6 +881,8 @@ public class StatusBar extends SystemUI implements DemoMode, mBrightnessSliderFactory = brightnessSliderFactory; mChargingRippleAnimationController = chargingRippleAnimationController; mOngoingCallController = ongoingCallController; + mAnimationScheduler = animationScheduler; + mDotViewController = dotViewController; mFeatureFlags = featureFlags; tunerService.addTunable( @@ -1171,7 +1179,10 @@ public class StatusBar extends SystemUI implements DemoMode, }).getFragmentManager() .beginTransaction() .replace(R.id.status_bar_container, - new CollapsedStatusBarFragment(mOngoingCallController), + new CollapsedStatusBarFragment( + mOngoingCallController, + mAnimationScheduler, + mDotViewController), CollapsedStatusBarFragment.TAG) .commit(); @@ -1788,7 +1799,15 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void startActivity(Intent intent, boolean dismissShade) { - startActivityDismissingKeyguard(intent, false, dismissShade); + startActivityDismissingKeyguard(intent, false /* onlyProvisioned */, dismissShade); + } + + @Override + public void startActivity(Intent intent, boolean dismissShade, + ActivityLaunchAnimator.Controller animationController) { + startActivityDismissingKeyguard(intent, false, dismissShade, + false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, + 0 /* flags */, animationController); } @Override 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 3404528be447..4356b52d27ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -39,6 +39,7 @@ import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.EventLog; +import android.view.View; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.NotificationVisibility; @@ -462,7 +463,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mActivityStarter.dismissKeyguardThenExecute(() -> { AsyncTask.execute(() -> { ActivityLaunchAnimator.Controller animationController = null; - if (!mStatusBar.isOccluded() && mStatusBar.areLaunchAnimationsEnabled()) { + if (mStatusBar.areLaunchAnimationsEnabled()) { animationController = new StatusBarLaunchAnimatorController( mNotificationAnimationProvider.getAnimatorController(row), mStatusBar, true /* isActivityIntent */); @@ -495,7 +496,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } @Override - public void startHistoryIntent(boolean showHistory) { + public void startHistoryIntent(View view, boolean showHistory) { mActivityStarter.dismissKeyguardThenExecute(() -> { AsyncTask.execute(() -> { Intent intent = showHistory ? new Intent( @@ -506,11 +507,27 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit if (showHistory) { tsb.addNextIntent(intent); } - tsb.startActivities(null, UserHandle.CURRENT); - // Putting it back on the main thread, since we're touching views - mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */)); + ActivityLaunchAnimator.Controller animationController = null; + if (mStatusBar.areLaunchAnimationsEnabled()) { + animationController = new StatusBarLaunchAnimatorController( + ActivityLaunchAnimator.Controller.fromView(view), mStatusBar, + true /* isActivityIntent */); + } + + mActivityLaunchAnimator.startIntentWithAnimation(animationController, + (adapter) -> tsb.startActivities( + getActivityOptions(mStatusBar.getDisplayId(), adapter), + UserHandle.CURRENT)); + + // Note that other cases when we should still collapse (like activity already on + // top) is handled by the StatusBarLaunchAnimatorController. + boolean shouldCollapse = animationController == null; + if (shouldCollapse) { + // Putting it back on the main thread, since we're touching views + mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */)); + } }); return true; }, null, false /* afterKeyguardGone */); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 2c2779e53e16..24e6db818ef6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -61,6 +61,8 @@ import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.charging.WiredChargingRippleController; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; @@ -209,6 +211,8 @@ public interface StatusBarPhoneModule { BrightnessSlider.Factory brightnessSliderFactory, WiredChargingRippleController chargingRippleAnimationController, OngoingCallController ongoingCallController, + SystemStatusAnimationScheduler animationScheduler, + PrivacyDotViewController dotViewController, TunerService tunerService, FeatureFlags featureFlags) { return new StatusBar( @@ -293,6 +297,8 @@ public interface StatusBarPhoneModule { brightnessSliderFactory, chargingRippleAnimationController, ongoingCallController, + animationScheduler, + dotViewController, tunerService, featureFlags); } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java index d19506212d91..d1a2c8a49a95 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java @@ -222,6 +222,11 @@ public class WalletScreenController implements if (mIsDismissed) { return; } + int cardWidthPx = mCardCarousel.getCardWidthPx(); + int cardHeightPx = mCardCarousel.getCardHeightPx(); + if (cardWidthPx == 0 || cardHeightPx == 0) { + return; + } if (!mHasRegisteredListener) { // Listener is registered even when device is locked. Should only be registered once. mWalletClient.addWalletServiceEventListener(this); @@ -231,8 +236,6 @@ public class WalletScreenController implements mWalletView.show(); mWalletView.hideErrorMessage(); int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size); - int cardWidthPx = mCardCarousel.getCardWidthPx(); - int cardHeightPx = mCardCarousel.getCardHeightPx(); GetWalletCardsRequest request = new GetWalletCardsRequest(cardWidthPx, cardHeightPx, iconSizePx, MAX_CARDS); mWalletClient.getWalletCards(mExecutor, request, this); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 74b79d57adcb..81bb819d77ea 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -268,6 +268,7 @@ public final class WMShell extends SystemUI public void onStop() { mSysUiMainExecutor.execute(() -> { if (oneHanded.isOneHandedEnabled()) { + // Log metrics for 3-button navigation mode. oneHanded.stopOneHanded( OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT); } else if (oneHanded.isSwipeToNotificationEnabled()) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 59262cf3231b..325275047ebb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -63,6 +63,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R.dimen; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; @@ -94,6 +95,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private UserTracker mUserTracker; + @Mock + private PrivacyDotViewController mDotViewController; @Before public void setup() { @@ -116,7 +119,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { mContext.addMockSystemService(DisplayManager.class, mDisplayManager); mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler, mSecureSettings, - mBroadcastDispatcher, mTunerService, mUserTracker) { + mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController) { @Override public void start() { super.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 3f0831cadd1f..78c67170d185 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -233,9 +233,9 @@ public class AppOpsControllerTest extends SysuiTestCase { TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED); assertEquals(2, - mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size()); - assertEquals(1, - mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER)).size()); + mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID), false).size()); + assertEquals(1, mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER), + false).size()); } @Test @@ -245,11 +245,11 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true); assertEquals(0, mController.getActiveAppOpsForUser( - UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); + UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size()); mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, SYSTEM_PKG, true); assertEquals(0, mController.getActiveAppOpsForUser( - UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); + UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size()); } @Test @@ -441,7 +441,19 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test - public void testOnlyRecordAudioPaused() { + public void testPausedPhoneCallMicrophoneFilteredOut() { + mController.addCallback(new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE}, mCallback); + mTestableLooper.processAllMessages(); + + mController.onOpActiveChanged( + AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + + assertTrue(mController.getActiveAppOps().isEmpty()); + } + + @Test + public void testOnlyRecordAudioPhoneCallMicrophonePaused() { mController.addCallback(new int[]{ AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA @@ -532,6 +544,40 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test + public void testPhoneCallMicrophoneFilteredWhenMicDisabled() { + mController.addCallback( + new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_CAMERA}, + mCallback); + mTestableLooper.processAllMessages(); + mController.onOpActiveChanged( + AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + List<AppOpItem> list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(0).getCode()); + assertFalse(list.get(0).isDisabled()); + + // Add a camera op, and disable the microphone. The camera op should be the only op returned + mController.onSensorBlockedChanged(MICROPHONE, true); + mController.onOpActiveChanged( + AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode()); + + + // Re enable the microphone, and verify the op returns + mController.onSensorBlockedChanged(MICROPHONE, false); + mTestableLooper.processAllMessages(); + + list = mController.getActiveAppOps(); + assertEquals(2, list.size()); + int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0; + assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(micIdx).getCode()); + } + + @Test public void testCameraFilteredWhenCameraDisabled() { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA}, mCallback); @@ -563,6 +609,39 @@ public class AppOpsControllerTest extends SysuiTestCase { assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode()); } + @Test + public void testPhoneCallCameraFilteredWhenCameraDisabled() { + mController.addCallback( + new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_PHONE_CALL_CAMERA}, + mCallback); + mTestableLooper.processAllMessages(); + mController.onOpActiveChanged( + AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + List<AppOpItem> list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(0).getCode()); + assertFalse(list.get(0).isDisabled()); + + // Add an audio op, and disable the camera. The audio op should be the only op returned + mController.onSensorBlockedChanged(CAMERA, true); + mController.onOpActiveChanged( + AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode()); + + // Re enable the camera, and verify the op returns + mController.onSensorBlockedChanged(CAMERA, false); + mTestableLooper.processAllMessages(); + + list = mController.getActiveAppOps(); + assertEquals(2, list.size()); + int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_PHONE_CALL_CAMERA ? 0 : 1; + assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode()); + } + private class TestHandler extends AppOpsControllerImpl.H { TestHandler(Looper looper) { mController.super(looper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java index 42e88b0f393d..63ce98a170a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.app.IWallpaperManager; import android.os.PowerManager; import android.testing.AndroidTestingRunner; @@ -43,9 +44,12 @@ public class WakefulnessLifecycleTest extends SysuiTestCase { private WakefulnessLifecycle mWakefulness; private WakefulnessLifecycle.Observer mWakefulnessObserver; + private IWallpaperManager mWallpaperManager; + @Before public void setUp() throws Exception { - mWakefulness = new WakefulnessLifecycle(); + mWallpaperManager = mock(IWallpaperManager.class); + mWakefulness = new WakefulnessLifecycle(mContext, mWallpaperManager); mWakefulnessObserver = mock(WakefulnessLifecycle.Observer.class); mWakefulness.addObserver(mWakefulnessObserver); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java index 0ce03ad2ac2b..81ca4c898290 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java @@ -22,8 +22,6 @@ import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; @@ -35,10 +33,7 @@ import static org.mockito.Mockito.when; import android.app.INotificationManager; import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; import android.app.Person; -import android.app.people.ConversationChannel; import android.app.people.IPeopleManager; import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; @@ -47,7 +42,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; -import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -57,7 +51,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; import android.provider.ContactsContract; -import android.service.notification.ConversationChannelWrapper; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; @@ -81,10 +74,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; @RunWith(AndroidTestingRunner.class) @SmallTest @@ -185,8 +176,6 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { @Mock private IPeopleManager mPeopleManager; @Mock - private LauncherApps mLauncherApps; - @Mock private IAppWidgetService mIAppWidgetService; @Mock private AppWidgetManager mAppWidgetManager; @@ -239,84 +228,6 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { } @Test - public void testGetRecentTilesReturnsSortedListWithOnlyRecentConversations() throws Exception { - // Ensure the less-recent Important conversation is before more recent conversations. - ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID_1, false, 3); - ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID_1 + 1, true, 3); - ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID_1 + 2, - true, 1); - when(mNotificationManager.getConversations(anyBoolean())).thenReturn( - new ParceledListSlice(Arrays.asList( - newerNonImportantConversation, newerImportantConversation, - olderImportantConversation))); - - // Ensure the non-Important conversation is sorted between these recent conversations. - ConversationChannel recentConversationBeforeNonImportantConversation = - getConversationChannel( - SHORTCUT_ID_1 + 3, 4); - ConversationChannel recentConversationAfterNonImportantConversation = - getConversationChannel(SHORTCUT_ID_1 + 4, - 2); - when(mPeopleManager.getRecentConversations()).thenReturn( - new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation, - recentConversationBeforeNonImportantConversation))); - - List<String> orderedShortcutIds = PeopleSpaceUtils.getRecentTiles( - mContext, mNotificationManager, mPeopleManager, - mLauncherApps, mNotificationEntryManager) - .stream().map(tile -> tile.getId()).collect(Collectors.toList()); - - // Check for sorted recent conversations. - assertThat(orderedShortcutIds).containsExactly( - recentConversationBeforeNonImportantConversation.getShortcutInfo().getId(), - newerNonImportantConversation.getShortcutInfo().getId(), - recentConversationAfterNonImportantConversation.getShortcutInfo().getId()) - .inOrder(); - } - - @Test - public void testGetPriorityTilesReturnsSortedListWithOnlyImportantConversations() - throws Exception { - // Ensure the less-recent Important conversation is before more recent conversations. - ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID_1, false, 3); - ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID_1 + 1, true, 3); - ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID_1 + 2, - true, 1); - when(mNotificationManager.getConversations(anyBoolean())).thenReturn( - new ParceledListSlice(Arrays.asList( - newerNonImportantConversation, newerImportantConversation, - olderImportantConversation))); - - // Ensure the non-Important conversation is sorted between these recent conversations. - ConversationChannel recentConversationBeforeNonImportantConversation = - getConversationChannel( - SHORTCUT_ID_1 + 3, 4); - ConversationChannel recentConversationAfterNonImportantConversation = - getConversationChannel(SHORTCUT_ID_1 + 4, - 2); - when(mPeopleManager.getRecentConversations()).thenReturn( - new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation, - recentConversationBeforeNonImportantConversation))); - - List<String> orderedShortcutIds = PeopleSpaceUtils.getPriorityTiles( - mContext, mNotificationManager, mPeopleManager, - mLauncherApps, mNotificationEntryManager) - .stream().map(tile -> tile.getId()).collect(Collectors.toList()); - - // Check for sorted priority conversations. - assertThat(orderedShortcutIds).containsExactly( - newerImportantConversation.getShortcutInfo().getId(), - olderImportantConversation.getShortcutInfo().getId()) - .inOrder(); - } - - @Test public void testGetMessagingStyleMessagesNoMessage() { Notification notification = new Notification.Builder(mContext, "test") .setContentTitle("TEST_TITLE") @@ -570,30 +481,4 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), any()); } - - private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId, - boolean importantConversation, long lastInteractionTimestamp) throws Exception { - ConversationChannelWrapper convo = new ConversationChannelWrapper(); - NotificationChannel notificationChannel = new NotificationChannel(shortcutId, - "channel" + shortcutId, - NotificationManager.IMPORTANCE_DEFAULT); - notificationChannel.setImportantConversation(importantConversation); - convo.setNotificationChannel(notificationChannel); - convo.setShortcutInfo(new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel( - "name").build()); - when(mPeopleManager.getLastInteraction(anyString(), anyInt(), - eq(shortcutId))).thenReturn(lastInteractionTimestamp); - return convo; - } - - private ConversationChannel getConversationChannel(String shortcutId, - long lastInteractionTimestamp) throws Exception { - ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel( - "name").build(); - ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null, - lastInteractionTimestamp, false); - when(mPeopleManager.getLastInteraction(anyString(), anyInt(), - eq(shortcutId))).thenReturn(lastInteractionTimestamp); - return convo; - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java index 107ac831811f..3cc55f2f9070 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java @@ -114,8 +114,6 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { when(mMockContext.getString(R.string.birthday_status)).thenReturn( mContext.getString(R.string.birthday_status)); - when(mMockContext.getString(R.string.basic_status)).thenReturn( - mContext.getString(R.string.basic_status)); when(mMockContext.getPackageManager()).thenReturn(mPackageManager); when(mMockContext.getString(R.string.over_timestamp)).thenReturn( mContext.getString(R.string.over_timestamp)); @@ -126,7 +124,6 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { when(resources.getConfiguration()).thenReturn(configuration); when(resources.getDisplayMetrics()).thenReturn(displayMetrics); TextView textView = mock(TextView.class); - // when(new TextView(mMockContext)).thenReturn(textView); when(textView.getLineHeight()).thenReturn(16); when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null); mPeopleTileViewHelper = new PeopleTileViewHelper(mContext, @@ -134,16 +131,41 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { } @Test - public void testCreateRemoteViewsWithLastInteractionTime() { + public void testCreateRemoteViewsWithLastInteractionTimeUnderOneDayHidden() { RemoteViews views = new PeopleTileViewHelper(mContext, PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews(); View result = views.apply(mContext, null); + // Not showing last interaction. + assertEquals(View.GONE, result.findViewById(R.id.last_interaction).getVisibility()); + + mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, + getSizeInDp(R.dimen.required_width_for_large)); + mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, + getSizeInDp(R.dimen.required_height_for_large)); + RemoteViews largeView = new PeopleTileViewHelper(mContext, + PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews(); + View largeResult = largeView.apply(mContext, null); + + // Not showing last interaction. + assertEquals(View.GONE, largeResult.findViewById(R.id.last_interaction).getVisibility()); + } + + @Test + public void testCreateRemoteViewsWithLastInteractionTime() { + PeopleSpaceTile tileWithLastInteraction = + PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setLastInteractionTimestamp( + 123445L).build(); + RemoteViews views = new PeopleTileViewHelper(mContext, + tileWithLastInteraction, 0, mOptions).getViews(); + View result = views.apply(mContext, null); + TextView name = (TextView) result.findViewById(R.id.name); assertEquals(name.getText(), NAME); // Has last interaction. + assertEquals(View.VISIBLE, result.findViewById(R.id.last_interaction).getVisibility()); TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction); - assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status)); + assertEquals(lastInteraction.getText(), "Over 2 weeks ago"); // No availability. assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility()); // Shows person icon. @@ -154,7 +176,7 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, getSizeInDp(R.dimen.required_width_for_medium) - 1); RemoteViews smallView = new PeopleTileViewHelper(mContext, - PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews(); + tileWithLastInteraction, 0, mOptions).getViews(); View smallResult = smallView.apply(mContext, null); // Show name over predefined icon. @@ -171,14 +193,15 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, getSizeInDp(R.dimen.required_height_for_large)); RemoteViews largeView = new PeopleTileViewHelper(mContext, - PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews(); + tileWithLastInteraction, 0, mOptions).getViews(); View largeResult = largeView.apply(mContext, null); name = (TextView) largeResult.findViewById(R.id.name); assertEquals(name.getText(), NAME); // Has last interaction. + assertEquals(View.VISIBLE, largeResult.findViewById(R.id.last_interaction).getVisibility()); lastInteraction = (TextView) result.findViewById(R.id.last_interaction); - assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status)); + assertEquals(lastInteraction.getText(), "Over 2 weeks ago"); // No availability. assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility()); // Shows person icon. @@ -202,8 +225,7 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { TextView name = (TextView) result.findViewById(R.id.name); assertEquals(name.getText(), NAME); // Has last interaction over status. - TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction); - assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status)); + assertEquals(View.GONE, result.findViewById(R.id.last_interaction).getVisibility()); // Has availability. assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility()); // Has person icon. @@ -237,14 +259,13 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { name = (TextView) largeResult.findViewById(R.id.name); assertEquals(name.getText(), NAME); // Has last interaction. - lastInteraction = (TextView) result.findViewById(R.id.last_interaction); - assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status)); + assertEquals(View.GONE, largeResult.findViewById(R.id.last_interaction).getVisibility()); // Has availability. - assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility()); + assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility()); // Shows person icon. - assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility()); + assertEquals(View.VISIBLE, largeResult.findViewById(R.id.person_icon).getVisibility()); // No status. - assertThat((View) result.findViewById(R.id.text_content)).isNull(); + assertThat((View) largeResult.findViewById(R.id.text_content)).isNull(); } @Test 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 0ef3ca29bdf6..ccb40e116115 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 @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.os.Bundle; import android.os.UserHandle; +import android.os.UserManager; import android.service.notification.NotificationListenerService; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -83,6 +84,8 @@ public class LaunchConversationActivityTest extends SysuiTestCase { private BubblesManager mBubblesManager; @Mock private NotificationListenerService.Ranking mRanking; + @Mock + private UserManager mUserManager; @Captor private ArgumentCaptor<NotificationVisibility> mNotificationVisibilityCaptor; @@ -93,7 +96,7 @@ public class LaunchConversationActivityTest extends SysuiTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mActivity = new LaunchConversationActivity(mNotificationEntryManager, - Optional.of(mBubblesManager)); + Optional.of(mBubblesManager), mUserManager); mActivity.setIsForTesting(true, mIStatusBarService); mIntent = new Intent(); mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, "tile ID"); @@ -113,6 +116,7 @@ public class LaunchConversationActivityTest extends SysuiTestCase { when(mNotifEntryCanBubble.canBubble()).thenReturn(true); when(mNotifEntryNoRanking.getRanking()).thenReturn(null); when(mRanking.getRank()).thenReturn(NOTIF_RANK); + when(mUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false); } @Test @@ -176,4 +180,18 @@ public class LaunchConversationActivityTest extends SysuiTestCase { anyInt(), any(), anyInt(), anyInt(), any()); verify(mBubblesManager, times(1)).expandStackAndSelectBubble(eq(mNotifEntryCanBubble)); } + + @Test + public void testQuietModeOpensQuietModeDialog() throws Exception { + mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY, + NOTIF_KEY); + when(mUserManager.isQuietModeEnabled(eq(USER_HANDLE))).thenReturn(true); + mActivity.setIntent(mIntent); + mActivity.onCreate(new Bundle()); + + assertThat(mActivity.isFinishing()).isTrue(); + verify(mIStatusBarService, never()).onNotificationClear(any(), + anyInt(), any(), anyInt(), anyInt(), any()); + verify(mBubblesManager, never()).expandStackAndSelectBubble(any()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index 7125500c1fe6..e9be8d8dad3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -35,7 +35,9 @@ import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -45,8 +47,10 @@ import static org.mockito.Mockito.when; import static java.util.Objects.requireNonNull; +import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.Person; import android.app.people.ConversationChannel; import android.app.people.ConversationStatus; @@ -59,11 +63,14 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; +import android.os.UserManager; +import android.service.notification.ConversationChannelWrapper; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; @@ -95,6 +102,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -169,6 +177,10 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private NotificationEntryManager mNotificationEntryManager; @Mock private PackageManager mPackageManager; + @Mock + private INotificationManager mNotificationManager; + @Mock + private UserManager mUserManager; @Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor; @@ -189,7 +201,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { mProvider = new PeopleSpaceWidgetProvider(); mProvider.setPeopleSpaceWidgetManager(mManager); mManager.setAppWidgetManager(mAppWidgetManager, mIPeopleManager, mPeopleManager, - mLauncherApps, mNotificationEntryManager, mPackageManager, true, mProvider); + mLauncherApps, mNotificationEntryManager, mPackageManager, true, mProvider, + mUserManager, mNotificationManager); mManager.attach(mListenerService); verify(mListenerService).addNotificationHandler(mListenerCaptor.capture()); @@ -201,6 +214,98 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { addTileForWidget(PERSON_TILE_WITH_SAME_URI, WIDGET_ID_WITH_SAME_URI); when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT))) .thenReturn(new Bundle()); + when(mUserManager.isQuietModeEnabled(any())).thenReturn(false); + } + + @Test + public void testGetRecentTilesReturnsSortedListWithOnlyRecentConversations() throws Exception { + // Ensure the less-recent Important conversation is before more recent conversations. + ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper( + SHORTCUT_ID, false, 3); + ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper( + SHORTCUT_ID + 1, true, 3); + ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper( + SHORTCUT_ID + 2, + true, 1); + when(mNotificationManager.getConversations(anyBoolean())).thenReturn( + new ParceledListSlice(Arrays.asList( + newerNonImportantConversation, newerImportantConversation, + olderImportantConversation))); + + // Ensure the non-Important conversation is sorted between these recent conversations. + ConversationChannel recentConversationBeforeNonImportantConversation = + getConversationChannel( + SHORTCUT_ID + 3, 4); + ConversationChannel recentConversationAfterNonImportantConversation = + getConversationChannel(SHORTCUT_ID + 4, + 2); + when(mIPeopleManager.getRecentConversations()).thenReturn( + new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation, + recentConversationBeforeNonImportantConversation))); + + List<String> orderedShortcutIds = mManager.getRecentTiles() + .stream().map(tile -> tile.getId()).collect(Collectors.toList()); + + // Check for sorted recent conversations. + assertThat(orderedShortcutIds).containsExactly( + recentConversationBeforeNonImportantConversation.getShortcutInfo().getId(), + newerNonImportantConversation.getShortcutInfo().getId(), + recentConversationAfterNonImportantConversation.getShortcutInfo().getId()) + .inOrder(); + } + + @Test + public void testGetPriorityTilesReturnsSortedListWithOnlyImportantConversations() + throws Exception { + // Ensure the less-recent Important conversation is before more recent conversations. + ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper( + SHORTCUT_ID, false, 3); + ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper( + SHORTCUT_ID + 1, true, 3); + ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper( + SHORTCUT_ID + 2, + true, 1); + when(mNotificationManager.getConversations(anyBoolean())).thenReturn( + new ParceledListSlice(Arrays.asList( + newerNonImportantConversation, newerImportantConversation, + olderImportantConversation))); + + List<String> orderedShortcutIds = mManager.getPriorityTiles() + .stream().map(tile -> tile.getId()).collect(Collectors.toList()); + + // Check for sorted priority conversations. + assertThat(orderedShortcutIds).containsExactly( + newerImportantConversation.getShortcutInfo().getId(), + olderImportantConversation.getShortcutInfo().getId()) + .inOrder(); + } + + @Test + public void testGetTilesReturnsNothingInQuietMode() + throws Exception { + // Ensure the less-recent Important conversation is before more recent conversations. + ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper( + SHORTCUT_ID, false, 3); + ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper( + SHORTCUT_ID + 1, true, 3); + ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper( + SHORTCUT_ID + 2, + true, 1); + when(mNotificationManager.getConversations(anyBoolean())).thenReturn( + new ParceledListSlice(Arrays.asList( + newerNonImportantConversation, newerImportantConversation, + olderImportantConversation))); + ConversationChannel recentConversation = + getConversationChannel( + SHORTCUT_ID + 3, 4); + when(mIPeopleManager.getRecentConversations()).thenReturn( + new ParceledListSlice(Arrays.asList(recentConversation))); + + when(mUserManager.isQuietModeEnabled(any())).thenReturn(true); + + // Check nothing returned. + assertThat(mManager.getPriorityTiles()).isEmpty(); + assertThat(mManager.getRecentTiles()).isEmpty(); } @Test @@ -1208,4 +1313,30 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { editor.putStringSet(contactUri.toString(), storedWidgetIdsByUri); editor.apply(); } + + private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId, + boolean importantConversation, long lastInteractionTimestamp) throws Exception { + ConversationChannelWrapper convo = new ConversationChannelWrapper(); + NotificationChannel notificationChannel = new NotificationChannel(shortcutId, + "channel" + shortcutId, + NotificationManager.IMPORTANCE_DEFAULT); + notificationChannel.setImportantConversation(importantConversation); + convo.setNotificationChannel(notificationChannel); + convo.setShortcutInfo(new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel( + "name").build()); + when(mIPeopleManager.getLastInteraction(anyString(), anyInt(), + eq(shortcutId))).thenReturn(lastInteractionTimestamp); + return convo; + } + + private ConversationChannel getConversationChannel(String shortcutId, + long lastInteractionTimestamp) throws Exception { + ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel( + "name").build(); + ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null, + lastInteractionTimestamp, false); + when(mIPeopleManager.getLastInteraction(anyString(), anyInt(), + eq(shortcutId))).thenReturn(lastInteractionTimestamp); + return convo; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt index 791dd121852f..05a1e4ff474d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt @@ -28,6 +28,7 @@ import android.permission.PermissionManager import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.appops.AppOpsController import com.android.systemui.plugins.ActivityStarter import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.settings.UserTracker @@ -43,6 +44,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Captor @@ -86,6 +88,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { private lateinit var privacyLogger: PrivacyLogger @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var appOpsController: AppOpsController @Captor private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialog.OnDialogDismissed> @Captor @@ -131,6 +135,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { uiExecutor, privacyLogger, keyguardStateController, + appOpsController, dialogProvider ) } @@ -143,18 +148,27 @@ class PrivacyDialogControllerTest : SysuiTestCase() { } @Test + fun testMicMutedParameter() { + `when`(appOpsController.isMicMuted).thenReturn(true) + controller.showDialog(context) + backgroundExecutor.runAllReady() + + verify(permissionManager).getIndicatorAppOpUsageData(true) + } + + @Test fun testPermissionManagerOnlyCalledInBackgroundThread() { controller.showDialog(context) - verify(permissionManager, never()).indicatorAppOpUsageData + verify(permissionManager, never()).getIndicatorAppOpUsageData(anyBoolean()) backgroundExecutor.runAllReady() - verify(permissionManager).indicatorAppOpUsageData + verify(permissionManager).getIndicatorAppOpUsageData(anyBoolean()) } @Test fun testPackageManagerOnlyCalledInBackgroundThread() { val usage = createMockPermGroupUsage() `when`(usage.isPhoneCall).thenReturn(false) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context) verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt()) @@ -217,7 +231,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { isPhoneCall = false, attribution = TEST_ATTRIBUTION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context) exhaustExecutors() @@ -246,7 +260,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { packageName = "${TEST_PACKAGE_NAME}_microphone", permGroupName = PERM_MICROPHONE ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_microphone, usage_camera) ) @@ -269,7 +283,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { packageName = "${TEST_PACKAGE_NAME}_recent", isActive = false ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_recent, usage_active) ) @@ -292,7 +306,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { isActive = true, lastAccess = 1L ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_active, usage_active_moreRecent) ) controller.showDialog(context) @@ -319,7 +333,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { isActive = false, lastAccess = 2L ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_recent, usage_mostRecent, usage_moreRecent) ) @@ -342,7 +356,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.micCameraAvailable).thenReturn(false) @@ -366,7 +380,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.locationAvailable).thenReturn(false) @@ -392,7 +406,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.micCameraAvailable).thenReturn(true) @@ -416,7 +430,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.micCameraAvailable).thenReturn(false) @@ -433,7 +447,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { val usage_enterprise = createMockPermGroupUsage( uid = generateUidForUser(ENT_USER_ID) ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_enterprise)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_enterprise)) controller.showDialog(context) exhaustExecutors() @@ -446,7 +461,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { val usage_other = createMockPermGroupUsage( uid = generateUidForUser(ENT_USER_ID + 1) ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_other)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_other)) controller.showDialog(context) exhaustExecutors() @@ -514,7 +530,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { } private fun setUpDefaultMockResponses() { - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(emptyList()) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(emptyList()) + `when`(appOpsController.isMicMuted).thenReturn(false) `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) .thenAnswer { diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index bba1c6a00675..e4d7b1b7d451 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -46,7 +46,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyList import org.mockito.Captor import org.mockito.Mock @@ -156,7 +156,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testDistinctItems() { doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -168,7 +168,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testSimilarItemsDifferentTimeStamp() { doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -215,7 +215,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testMultipleCallbacksAreUpdated() { - doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean()) val otherCallback = mock(PrivacyItemController.Callback::class.java) privacyItemController.addCallback(callback) @@ -233,7 +233,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testRemoveCallback() { - doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean()) val otherCallback = mock(PrivacyItemController.Callback::class.java) privacyItemController.addCallback(callback) privacyItemController.addCallback(otherCallback) @@ -254,7 +254,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testListShouldNotHaveNull() { doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() executor.runAllReady() @@ -292,7 +292,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -306,7 +306,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testNotUpdated_LocationChangeWhenOnlyMicCamera() { doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) changeLocation(false) @@ -338,7 +338,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testLogListUpdated() { doReturn(listOf( AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -362,10 +362,10 @@ class PrivacyItemControllerTest : SysuiTestCase() { } @Test - fun testListRequestedForAllUsers() { + fun testListRequestedShowPaused() { privacyItemController.addCallback(callback) executor.runAllReady() - verify(appOpsController).getActiveAppOpsForUser(UserHandle.USER_ALL) + verify(appOpsController).getActiveAppOps(true) } @Test @@ -377,7 +377,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { doReturn(listOf( AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_CAMERA, otherUserUid, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext) executor.runAllReady() @@ -401,7 +401,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext) executor.runAllReady() @@ -424,7 +424,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext) executor.runAllReady() @@ -442,7 +442,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testPassageOfTimeDoesNotRemoveIndicators() { doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) @@ -458,12 +458,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { // Start with some element at time 0 doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD + 1 - doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean()) fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS + 1) verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) @@ -481,12 +481,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { // Start with some element at time 0 doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD - 1 - doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean()) fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1) verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) @@ -504,12 +504,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { // Start with some element at time 0 doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD - 1 - doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean()) fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1) verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) @@ -525,6 +525,30 @@ class PrivacyItemControllerTest : SysuiTestCase() { assertTrue(privacyItemController.privacyList.isEmpty()) } + @Test + fun testPausedElementsAreRemoved() { + val item = AppOpItem( + AppOpsManager.OP_RECORD_AUDIO, + TEST_UID, + TEST_PACKAGE_NAME, + elapsedTime + ) + `when`(appOpsController.getActiveAppOps(anyBoolean())).thenReturn(listOf(item)) + privacyItemController.addCallback(callback) + executor.runAllReady() + + item.isDisabled = true + fakeClock.advanceTime(1) + verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) + argCaptorCallback.value.onActiveStateChanged( + AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false) + + executor.runAllReady() + + verify(callback).onPrivacyItemsChanged(emptyList()) + assertTrue(privacyItemController.privacyList.isEmpty()) + } + private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value) private fun changeLocation(value: Boolean?) = changeProperty(LOCATION_INDICATOR, value) 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 d236023499d2..4bba0d0d23e4 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 @@ -233,6 +233,11 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { } @Test + public void testGetTileLabel() { + assertEquals(mContext.getString(R.string.wallet_title), mTile.getTileLabel().toString()); + } + + @Test public void testHandleUpdateState_hasCard_deviceLocked_tileInactive() { when(mKeyguardStateController.isUnlocked()).thenReturn(false); QSTile.State state = new QSTile.State(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java index 67fd5eb1acac..929a7e1543fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java @@ -37,6 +37,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; @@ -58,6 +60,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private View mCenteredNotificationAreaView; private StatusBarStateController mStatusBarStateController; private OngoingCallController mOngoingCallController; + private SystemStatusAnimationScheduler mAnimationScheduler; + private PrivacyDotViewController mDotViewController; public CollapsedStatusBarFragmentTest() { super(CollapsedStatusBarFragment.class); @@ -212,6 +216,11 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { mOngoingCallController = mock(OngoingCallController.class); - return new CollapsedStatusBarFragment(mOngoingCallController); + mAnimationScheduler = mock(SystemStatusAnimationScheduler.class); + mDotViewController = mock(PrivacyDotViewController.class); + return new CollapsedStatusBarFragment( + mOngoingCallController, + mAnimationScheduler, + mDotViewController); } } 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 08d6d2d7c72d..11f96c87c0e5 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 @@ -38,6 +38,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.IWallpaperManager; import android.app.Notification; import android.app.StatusBarManager; import android.app.trust.TrustManager; @@ -113,6 +114,8 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.charging.WiredChargingRippleController; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -263,8 +266,11 @@ public class StatusBarTest extends SysuiTestCase { @Mock private BrightnessSlider.Factory mBrightnessSliderFactory; @Mock private WiredChargingRippleController mWiredChargingRippleController; @Mock private OngoingCallController mOngoingCallController; + @Mock private SystemStatusAnimationScheduler mAnimationScheduler; + @Mock private PrivacyDotViewController mDotViewController; @Mock private TunerService mTunerService; @Mock private FeatureFlags mFeatureFlags; + @Mock private IWallpaperManager mWallpaperManager; private ShadeController mShadeController; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private InitController mInitController = new InitController(); @@ -323,7 +329,8 @@ public class StatusBarTest extends SysuiTestCase { when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); - WakefulnessLifecycle wakefulnessLifecycle = new WakefulnessLifecycle(); + WakefulnessLifecycle wakefulnessLifecycle = + new WakefulnessLifecycle(mContext, mWallpaperManager); wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); wakefulnessLifecycle.dispatchFinishedWakingUp(); @@ -429,6 +436,8 @@ public class StatusBarTest extends SysuiTestCase { mBrightnessSliderFactory, mWiredChargingRippleController, mOngoingCallController, + mAnimationScheduler, + mDotViewController, mTunerService, mFeatureFlags); when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 4471778a03ab..40439a2bebb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -36,7 +36,6 @@ import static org.mockito.Mockito.when; import android.app.Instrumentation; import android.net.ConnectivityManager; -import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; @@ -323,34 +322,37 @@ public class NetworkControllerBaseTest extends SysuiTestCase { public void setConnectivityViaCallbackInNetworkControllerForVcn( int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) { - mNetCapabilities.setTransportInfo(info); - setConnectivityCommon(networkType, validated, isConnected); - mDefaultCallbackInNetworkController.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder(mNetCapabilities); + builder.setTransportInfo(info); + setConnectivityCommon(builder, networkType, validated, isConnected); + mDefaultCallbackInNetworkController.onCapabilitiesChanged(mNetwork, builder.build()); } public void setConnectivityViaCallbackInNetworkController( int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) { + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder(mNetCapabilities); if (networkType == NetworkCapabilities.TRANSPORT_WIFI) { - mNetCapabilities.setTransportInfo(wifiInfo); + builder.setTransportInfo(wifiInfo); } - setConnectivityCommon(networkType, validated, isConnected); - mDefaultCallbackInNetworkController.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); + setConnectivityCommon(builder, networkType, validated, isConnected); + mDefaultCallbackInNetworkController.onCapabilitiesChanged(mNetwork, builder.build()); } public void setConnectivityViaCallbackInWifiTracker( int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) { + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder(mNetCapabilities); if (networkType == NetworkCapabilities.TRANSPORT_WIFI) { - mNetCapabilities.setTransportInfo(wifiInfo); + builder.setTransportInfo(wifiInfo); } - setConnectivityCommon(networkType, validated, isConnected); + setConnectivityCommon(builder, networkType, validated, isConnected); if (networkType == NetworkCapabilities.TRANSPORT_WIFI) { if (isConnected) { - mNetworkCallback.onAvailable(mNetwork, - new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false); - mNetworkCallback.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); + final NetworkCapabilities newCap = builder.build(); + mNetworkCallback.onAvailable(mNetwork); + mNetworkCallback.onCapabilitiesChanged(mNetwork, newCap); } else { mNetworkCallback.onLost(mNetwork); } @@ -359,16 +361,16 @@ public class NetworkControllerBaseTest extends SysuiTestCase { public void setConnectivityViaCallbackInWifiTrackerForVcn( int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) { - mNetCapabilities.setTransportInfo(info); - setConnectivityCommon(networkType, validated, isConnected); + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder(mNetCapabilities); + builder.setTransportInfo(info); + setConnectivityCommon(builder, networkType, validated, isConnected); if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) { if (isConnected) { - mNetworkCallback.onAvailable(mNetwork, - new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false); - mNetworkCallback.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); - mDefaultCallbackInWifiTracker.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); + final NetworkCapabilities newCap = builder.build(); + mNetworkCallback.onAvailable(mNetwork); + mNetworkCallback.onCapabilitiesChanged(mNetwork, newCap); + mDefaultCallbackInWifiTracker.onCapabilitiesChanged(mNetwork, newCap); } else { mNetworkCallback.onLost(mNetwork); } @@ -377,26 +379,28 @@ public class NetworkControllerBaseTest extends SysuiTestCase { public void setConnectivityViaDefaultCallbackInWifiTracker( int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) { + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder(mNetCapabilities); if (networkType == NetworkCapabilities.TRANSPORT_WIFI) { - mNetCapabilities.setTransportInfo(wifiInfo); + builder.setTransportInfo(wifiInfo); } - setConnectivityCommon(networkType, validated, isConnected); + setConnectivityCommon(builder, networkType, validated, isConnected); mDefaultCallbackInWifiTracker.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); + mNetwork, builder.build()); } - private void setConnectivityCommon( + private static void setConnectivityCommon(NetworkCapabilities.Builder builder, int networkType, boolean validated, boolean isConnected){ // TODO: Separate out into several NetworkCapabilities. if (isConnected) { - mNetCapabilities.addTransportType(networkType); + builder.addTransportType(networkType); } else { - mNetCapabilities.removeTransportType(networkType); + builder.removeTransportType(networkType); } if (validated) { - mNetCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + builder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); } else { - mNetCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java index ed87a4040022..c38a54771e12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java @@ -202,8 +202,8 @@ public class SecurityControllerTest extends SysuiTestCase { @Test public void testNetworkRequest() { verify(mConnectivityManager, times(1)).registerNetworkCallback(argThat( - (NetworkRequest request) -> request.networkCapabilities.getUids() == null - && request.networkCapabilities.getCapabilities().length == 0 + (NetworkRequest request) -> + request.equals(new NetworkRequest.Builder().clearCapabilities().build()) ), any(NetworkCallback.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java index 653946ea5cc8..6f6ef7261020 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java @@ -71,15 +71,17 @@ import java.util.Collections; public class WalletScreenControllerTest extends SysuiTestCase { private static final int MAX_CARDS = 10; + private static final int CARD_CAROUSEL_WIDTH = 10; private static final String CARD_ID = "card_id"; private static final CharSequence SHORTCUT_SHORT_LABEL = "View all"; private static final CharSequence SHORTCUT_LONG_LABEL = "Add a payment method"; private static final CharSequence SERVICE_LABEL = "Wallet app"; - private final WalletView mWalletView = new WalletView(mContext); private final Drawable mWalletLogo = mContext.getDrawable(android.R.drawable.ic_lock_lock); private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET) .setComponent(new ComponentName(mContext.getPackageName(), "WalletActivity")); + private WalletView mWalletView; + @Mock QuickAccessWalletClient mWalletClient; @Mock @@ -104,6 +106,8 @@ public class WalletScreenControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); when(mUserTracker.getUserContext()).thenReturn(mContext); + mWalletView = new WalletView(mContext); + mWalletView.getCardCarousel().setExpectedViewWidth(CARD_CAROUSEL_WIDTH); when(mWalletClient.getLogo()).thenReturn(mWalletLogo); when(mWalletClient.getShortcutLongLabel()).thenReturn(SHORTCUT_LONG_LABEL); when(mWalletClient.getShortcutShortLabel()).thenReturn(SHORTCUT_SHORT_LABEL); @@ -132,7 +136,12 @@ public class WalletScreenControllerTest extends SysuiTestCase { verify(mWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + QuickAccessWalletClient.OnWalletCardsRetrievedCallback callback = + mCallbackCaptor.getValue(); + + assertEquals(mController, callback); + + callback.onWalletCardsRetrieved(response); mTestableLooper.processAllMessages(); assertEquals(VISIBLE, mWalletView.getCardCarouselContainer().getVisibility()); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 4d96162149e2..16e6671fdf65 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -630,7 +630,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } private static IDnsResolver getDnsResolver(Context context) { - return IDnsResolver.Stub.asInterface(DnsResolverServiceManager.getService(context)); + final DnsResolverServiceManager dsm = context.getSystemService( + DnsResolverServiceManager.class); + return IDnsResolver.Stub.asInterface(dsm.getService()); } /** Handler thread used for all of the handlers below. */ @@ -1924,7 +1926,7 @@ public class ConnectivityService extends IConnectivityManager.Stub newNc.setAdministratorUids(new int[0]); if (!checkAnyPermissionOf( callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) { - newNc.setSubIds(Collections.emptySet()); + newNc.setSubscriptionIds(Collections.emptySet()); } return newNc; @@ -3969,17 +3971,16 @@ public class ConnectivityService extends IConnectivityManager.Stub // multilayer requests, returning as soon as a NetworkAgentInfo satisfies a request // is important so as to not evaluate lower priority requests further in // nri.mRequests. - final boolean isNetworkNeeded = candidate.isSatisfyingRequest(req.requestId) - // Note that this catches two important cases: - // 1. Unvalidated cellular will not be reaped when unvalidated WiFi - // is currently satisfying the request. This is desirable when - // cellular ends up validating but WiFi does not. - // 2. Unvalidated WiFi will not be reaped when validated cellular - // is currently satisfying the request. This is desirable when - // WiFi ends up validating and out scoring cellular. - || nri.getSatisfier().getCurrentScore() - < candidate.getCurrentScoreAsValidated(); - return isNetworkNeeded; + final NetworkAgentInfo champion = req.equals(nri.getActiveRequest()) + ? nri.getSatisfier() : null; + // Note that this catches two important cases: + // 1. Unvalidated cellular will not be reaped when unvalidated WiFi + // is currently satisfying the request. This is desirable when + // cellular ends up validating but WiFi does not. + // 2. Unvalidated WiFi will not be reaped when validated cellular + // is currently satisfying the request. This is desirable when + // WiFi ends up validating and out scoring cellular. + return mNetworkRanker.mightBeat(req, champion, candidate.getValidatedScoreable()); } } @@ -5726,7 +5727,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } mAppOpsManager.checkPackage(callerUid, callerPackageName); - if (!nc.getSubIds().isEmpty()) { + if (!nc.getSubscriptionIds().isEmpty()) { enforceNetworkFactoryPermission(); } } @@ -6148,6 +6149,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public int registerNetworkProvider(Messenger messenger, String name) { enforceNetworkFactoryOrSettingsPermission(); + Objects.requireNonNull(messenger, "messenger must be non-null"); NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, nextNetworkProviderId(), () -> unregisterNetworkProvider(messenger)); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi)); @@ -7811,7 +7813,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull final Collection<NetworkRequestInfo> networkRequests) { final NetworkReassignment changes = new NetworkReassignment(); - // Gather the list of all relevant agents and sort them by score. + // Gather the list of all relevant agents. final ArrayList<NetworkAgentInfo> nais = new ArrayList<>(); for (final NetworkAgentInfo nai : mNetworkAgentInfos) { if (!nai.everConnected) { @@ -9186,6 +9188,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void unregisterConnectivityDiagnosticsCallback( @NonNull IConnectivityDiagnosticsCallback callback) { + Objects.requireNonNull(callback, "callback must be non-null"); mConnectivityDiagnosticsHandler.sendMessage( mConnectivityDiagnosticsHandler.obtainMessage( ConnectivityDiagnosticsHandler @@ -9556,6 +9559,7 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @Override public void unregisterQosCallback(@NonNull final IQosCallback callback) { + Objects.requireNonNull(callback, "callback must be non-null"); mQosCallbackTracker.unregisterCallback(callback); } diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 9f91dd6b982b..483250ad2257 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -174,9 +174,9 @@ option java_package com.android.server # Disk usage stats for verifying quota correctness 3121 pm_package_stats (manual_time|2|3),(quota_time|2|3),(manual_data|2|2),(quota_data|2|2),(manual_cache|2|2),(quota_cache|2|2) # Snapshot statistics -3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(quick_rebuilds|1|1),(max_build_time|1|3),(cumm_build_time|1|3) +3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(short_lived|1|1),(max_build_time|1|3),(cumm_build_time|2|3) # Snapshot rebuild instance -3131 pm_snapshot_rebuild (build_time|1|3),(elapsed|1|3) +3131 pm_snapshot_rebuild (build_time|1|3),(lifetime|1|3) # --------------------------- # InputMethodManagerService.java diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index df6ab5da70d4..3ba4c34fb1a7 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -19,9 +19,10 @@ package com.android.server; import static android.Manifest.permission.MANAGE_SENSOR_PRIVACY; import static android.app.ActivityManager.RunningServiceInfo; import static android.app.ActivityManager.RunningTaskInfo; -import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR; @@ -140,11 +141,15 @@ public final class SensorPrivacyService extends SystemService { private final UserManagerInternal mUserManagerInternal; private final ActivityManager mActivityManager; private final ActivityTaskManager mActivityTaskManager; + private final AppOpsManager mAppOpsManager; + + private final IBinder mAppOpsRestrictionToken = new Binder(); private SensorPrivacyManagerInternalImpl mSensorPrivacyManagerInternal; public SensorPrivacyService(Context context) { super(context); + mAppOpsManager = context.getSystemService(AppOpsManager.class); mUserManagerInternal = getLocalService(UserManagerInternal.class); mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context); mActivityManager = context.getSystemService(ActivityManager.class); @@ -194,10 +199,20 @@ public final class SensorPrivacyService extends SystemService { } } + for (int i = 0; i < mIndividualEnabled.size(); i++) { + int userId = mIndividualEnabled.keyAt(i); + SparseBooleanArray userIndividualEnabled = + mIndividualEnabled.get(i); + for (int j = 0; j < userIndividualEnabled.size(); j++) { + int sensor = userIndividualEnabled.keyAt(i); + boolean enabled = userIndividualEnabled.valueAt(j); + setUserRestriction(userId, sensor, enabled); + } + } + int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_CAMERA}; - AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class); - appOpsManager.startWatchingNoted(micAndCameraOps, this); - appOpsManager.startWatchingStarted(micAndCameraOps, this); + mAppOpsManager.startWatchingNoted(micAndCameraOps, this); + mAppOpsManager.startWatchingStarted(micAndCameraOps, this); mContext.registerReceiver(new BroadcastReceiver() { @Override @@ -221,7 +236,7 @@ public final class SensorPrivacyService extends SystemService { public void onOpNoted(int code, int uid, String packageName, String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) { - if (result != MODE_ALLOWED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) { + if (result != MODE_IGNORED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) { return; } @@ -1125,6 +1140,9 @@ public final class SensorPrivacyService extends SystemService { mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled); SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser = mIndividualSensorListeners.get(userId); + + setUserRestriction(userId, sensor, enabled); + if (listenersForUser == null) { return; } @@ -1152,6 +1170,18 @@ public final class SensorPrivacyService extends SystemService { } } + private void setUserRestriction(int userId, int sensor, boolean enabled) { + if (sensor == CAMERA) { + mAppOpsManager.setUserRestrictionForUser(OP_CAMERA, enabled, + mAppOpsRestrictionToken, new String[]{}, userId); + } else if (sensor == MICROPHONE) { + mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO, enabled, + mAppOpsRestrictionToken, new String[]{}, userId); + mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO_HOTWORD, enabled, + mAppOpsRestrictionToken, new String[]{}, userId); + } + } + private final class DeathRecipient implements IBinder.DeathRecipient { private ISensorPrivacyListener mListener; diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 98bfa28d6415..2d486c47d3c8 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -2551,11 +2551,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { * Send a notification to registrants that the configs of physical channel has changed for * a particular subscription. * + * @param phoneId the phone id. * @param subId the subId * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel. */ - public void notifyPhysicalChannelConfigForSubscriber( - int subId, List<PhysicalChannelConfig> configs) { + public void notifyPhysicalChannelConfigForSubscriber(int phoneId, int subId, + List<PhysicalChannelConfig> configs) { if (!checkNotifyPermission("notifyPhysicalChannelConfig()")) { return; } @@ -2567,7 +2568,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } synchronized (mRecords) { - int phoneId = SubscriptionManager.getPhoneId(subId); if (validatePhoneId(phoneId)) { mPhysicalChannelConfigs.set(phoneId, configs); for (Record r : mRecords) { diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 039f4d9f3cce..4b5205711f33 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -729,7 +729,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { // If multiple subscription IDs exist, they MUST all point to the same subscription // group. Otherwise undefined behavior may occur. - for (int subId : networkCapabilities.getSubIds()) { + for (int subId : networkCapabilities.getSubscriptionIds()) { // Verify that all subscriptions point to the same group if (subGrp != null && !subGrp.equals(snapshot.getGroupForSubId(subId))) { Slog.wtf(TAG, "Got multiple subscription groups for a single network"); @@ -1003,14 +1003,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { } private boolean requiresRestartForCarrierWifi(NetworkCapabilities caps) { - if (!caps.hasTransport(TRANSPORT_WIFI) || caps.getSubIds() == null) { + if (!caps.hasTransport(TRANSPORT_WIFI) || caps.getSubscriptionIds() == null) { return false; } synchronized (mCaps) { for (NetworkCapabilities existing : mCaps.values()) { if (existing.hasTransport(TRANSPORT_WIFI) - && caps.getSubIds().equals(existing.getSubIds())) { + && caps.getSubscriptionIds().equals(existing.getSubscriptionIds())) { // Restart if any immutable capabilities have changed return existing.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) != caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED); diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index b103defbbe21..b7ef10a5d45d 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -199,13 +199,12 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onPropertiesChanged(Properties properties) { synchronized (mDeviceConfigLock) { - for (String key : properties.getKeyset()) { + for (final String packageName : properties.getKeyset()) { try { // Check if the package is installed before caching it. - final String packageName = keyToPackageName(key); mPackageManager.getPackageInfo(packageName, 0); final GamePackageConfiguration config = - GamePackageConfiguration.fromProperties(key, properties); + GamePackageConfiguration.fromProperties(packageName, properties); if (config.isValid()) { putConfig(config); } else { @@ -290,8 +289,8 @@ public final class GameManagerService extends IGameManagerService.Stub { private final String mPackageName; private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs; - private GamePackageConfiguration(String keyName) { - mPackageName = keyToPackageName(keyName); + private GamePackageConfiguration(String packageName) { + mPackageName = packageName; mModeConfigs = new ArrayMap<>(); } @@ -563,9 +562,9 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - private void loadDeviceConfigLocked() { + void loadDeviceConfigLocked() { final List<PackageInfo> packages = mPackageManager.getInstalledPackages(0); - final String[] packageNames = packages.stream().map(e -> packageNameToKey(e.packageName)) + final String[] packageNames = packages.stream().map(e -> e.packageName) .toArray(String[]::new); synchronized (mDeviceConfigLock) { final Properties properties = DeviceConfig.getProperties( @@ -680,8 +679,7 @@ public final class GameManagerService extends IGameManagerService.Stub { case ACTION_PACKAGE_CHANGED: synchronized (mDeviceConfigLock) { Properties properties = DeviceConfig.getProperties( - DeviceConfig.NAMESPACE_GAME_OVERLAY, - packageNameToKey(packageName)); + DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName); for (String key : properties.getKeyset()) { GamePackageConfiguration config = GamePackageConfiguration.fromProperties(key, @@ -692,7 +690,9 @@ public final class GameManagerService extends IGameManagerService.Stub { break; case ACTION_PACKAGE_REMOVED: disableCompatScale(packageName); - mConfigs.remove(packageName); + synchronized (mDeviceConfigLock) { + mConfigs.remove(packageName); + } break; default: // do nothing @@ -710,23 +710,6 @@ public final class GameManagerService extends IGameManagerService.Stub { mDeviceConfigListener = new DeviceConfigListener(); } - /** - * Valid package name characters are [a-zA-Z0-9_] with a '.' delimiter. Policy keys can only use - * [a-zA-Z0-9_] so we must handle periods. We do this by appending a '_' to any existing - * sequence of '_', then we replace all '.' chars with '_'; - */ - private static String packageNameToKey(String name) { - return name.replaceAll("(_+)", "_$1").replaceAll("\\.", "_"); - } - - /** - * Replace the last '_' in a sequence with '.' (this can be one or more chars), then replace the - * resulting special case '_.' with just '_' to get the original package name. - */ - private static String keyToPackageName(String key) { - return key.replaceAll("(_)(?!\\1)", ".").replaceAll("_\\.", "_"); - } - private String dumpDeviceConfigs() { StringBuilder out = new StringBuilder(); for (String key : mConfigs.keySet()) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index d56fd1221aa9..b795b549fd61 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -375,12 +375,12 @@ public class Sensor { } @Override - public void onFeaturesRetrieved(byte[] features, int enrollmentId) { + public void onFeaturesRetrieved(byte[] features) { } @Override - public void onFeatureSet(int enrollmentId, byte feature) { + public void onFeatureSet(byte feature) { } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java index c63af7e4b60c..f1bfd53a5753 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java @@ -32,6 +32,7 @@ import android.util.Slog; */ public class TestHal extends IFace.Stub { private static final String TAG = "face.aidl.TestHal"; + @Override public SensorProps[] getSensorProps() { Slog.w(TAG, "getSensorProps"); @@ -102,16 +103,16 @@ public class TestHal extends IFace.Stub { } @Override - public void getFeatures(int enrollmentId) throws RemoteException { + public void getFeatures() throws RemoteException { Slog.w(TAG, "getFeatures"); - cb.onFeaturesRetrieved(new byte[0], enrollmentId); + cb.onFeaturesRetrieved(new byte[0]); } @Override - public void setFeature(HardwareAuthToken hat, int enrollmentId, - byte feature, boolean enabled) throws RemoteException { + public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled) + throws RemoteException { Slog.w(TAG, "setFeature"); - cb.onFeatureSet(enrollmentId, feature); + cb.onFeatureSet(feature); } @Override diff --git a/services/core/java/com/android/server/connectivity/FullScore.java b/services/core/java/com/android/server/connectivity/FullScore.java index 52da56690721..14cec0956070 100644 --- a/services/core/java/com/android/server/connectivity/FullScore.java +++ b/services/core/java/com/android/server/connectivity/FullScore.java @@ -259,6 +259,14 @@ public class FullScore { } /** + * Returns this score but validated. + */ + public FullScore asValidated() { + return new FullScore(mLegacyInt, mPolicies | (1L << POLICY_IS_VALIDATED), + mKeepConnectedReason); + } + + /** * For backward compatibility, get the legacy int. * This will be removed before S is published. */ diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 584174e237a3..18becd45f6d8 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -950,6 +950,26 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa } /** + * Returns a Scoreable identical to this NAI, but validated. + * + * This is useful to probe what scoring would be if this network validated, to know + * whether to provisionally keep a network that may or may not validate. + * + * @return a Scoreable identical to this NAI, but validated. + */ + public NetworkRanker.Scoreable getValidatedScoreable() { + return new NetworkRanker.Scoreable() { + @Override public FullScore getScore() { + return mScore.asValidated(); + } + + @Override public NetworkCapabilities getCapsNoCopy() { + return networkCapabilities; + } + }; + } + + /** * Return a {@link NetworkStateSnapshot} for this network. */ @NonNull diff --git a/services/core/java/com/android/server/connectivity/NetworkRanker.java b/services/core/java/com/android/server/connectivity/NetworkRanker.java index 2b345e5aa79f..346af44e55f3 100644 --- a/services/core/java/com/android/server/connectivity/NetworkRanker.java +++ b/services/core/java/com/android/server/connectivity/NetworkRanker.java @@ -234,16 +234,17 @@ public class NetworkRanker { NetworkAgentInfo bestNetwork = null; int bestScore = Integer.MIN_VALUE; for (final NetworkAgentInfo nai : nais) { - if (nai.getCurrentScore() > bestScore) { + final int naiScore = nai.getCurrentScore(); + if (naiScore > bestScore) { bestNetwork = nai; - bestScore = nai.getCurrentScore(); + bestScore = naiScore; } } return bestNetwork; } /** - * Returns whether an offer has a chance to beat a champion network for a request. + * Returns whether a {@link Scoreable} has a chance to beat a champion network for a request. * * Offers are sent by network providers when they think they might be able to make a network * with the characteristics contained in the offer. If the offer has no chance to beat @@ -257,15 +258,15 @@ public class NetworkRanker { * * @param request The request to evaluate against. * @param champion The currently best network for this request. - * @param offer The offer. + * @param contestant The offer. * @return Whether the offer stands a chance to beat the champion. */ public boolean mightBeat(@NonNull final NetworkRequest request, @Nullable final NetworkAgentInfo champion, - @NonNull final NetworkOffer offer) { + @NonNull final Scoreable contestant) { // If this network can't even satisfy the request then it can't beat anything, not // even an absence of network. It can't satisfy it anyway. - if (!request.canBeSatisfiedBy(offer.caps)) return false; + if (!request.canBeSatisfiedBy(contestant.getCapsNoCopy())) return false; // If there is no satisfying network, then this network can beat, because some network // is always better than no network. if (null == champion) return true; @@ -274,25 +275,24 @@ public class NetworkRanker { // Otherwise rank them. final ArrayList<Scoreable> candidates = new ArrayList<>(); candidates.add(champion); - candidates.add(offer); - return offer == getBestNetworkByPolicy(candidates, champion); + candidates.add(contestant); + return contestant == getBestNetworkByPolicy(candidates, champion); } else { - return mightBeatByLegacyInt(request, champion.getScore(), offer); + return mightBeatByLegacyInt(champion.getScore(), contestant); } } /** - * Returns whether an offer might beat a champion according to the legacy int. + * Returns whether a contestant might beat a champion according to the legacy int. */ - public boolean mightBeatByLegacyInt(@NonNull final NetworkRequest request, - @Nullable final FullScore championScore, - @NonNull final NetworkOffer offer) { + private boolean mightBeatByLegacyInt(@Nullable final FullScore championScore, + @NonNull final Scoreable contestant) { final int offerIntScore; - if (offer.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + if (contestant.getCapsNoCopy().hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { // If the offer might have Internet access, then it might validate. - offerIntScore = offer.score.getLegacyIntAsValidated(); + offerIntScore = contestant.getScore().getLegacyIntAsValidated(); } else { - offerIntScore = offer.score.getLegacyInt(); + offerIntScore = contestant.getScore().getLegacyInt(); } return championScore.getLegacyInt() < offerIntScore; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 7994fccbd650..94a5099b45da 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3177,7 +3177,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void reportPerceptible(IBinder windowToken, boolean perceptible) { + public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) { Objects.requireNonNull(windowToken, "windowToken must not be null"); int uid = Binder.getCallingUid(); synchronized (mMethodMap) { @@ -5992,9 +5992,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void reportStartInput(IBinder startInputToken, IVoidResultCallback resultCallback) { - CallbackUtils.onResult(resultCallback, - () -> mImms.reportStartInput(mToken, startInputToken)); + public void reportStartInputAsync(IBinder startInputToken) { + mImms.reportStartInput(mToken, startInputToken); } @BinderThread diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 62447439003b..885093d61486 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1840,7 +1840,7 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public void reportPerceptible(IBinder windowClient, boolean perceptible) { + public void reportPerceptibleAsync(IBinder windowClient, boolean perceptible) { reportNotSupported(); } diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java index 045e06d001e6..2ffc62a0731b 100644 --- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java @@ -178,8 +178,9 @@ public class LocationEventLog extends LocalEventLog { } /** Logs that a provider has entered or exited stationary throttling. */ - public void logProviderStationaryThrottled(String provider, boolean throttled) { - addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled); + public void logProviderStationaryThrottled(String provider, boolean throttled, + ProviderRequest request) { + addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled, request); } /** Logs that the location power save mode has changed. */ @@ -217,7 +218,7 @@ public class LocationEventLog extends LocalEventLog { (Integer) args[1], (CallerIdentity) args[2]); case EVENT_PROVIDER_STATIONARY_THROTTLED: return new ProviderStationaryThrottledEvent(timeDelta, (String) args[0], - (Boolean) args[1]); + (Boolean) args[1], (ProviderRequest) args[2]); case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE: return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]); default: @@ -355,17 +356,19 @@ public class LocationEventLog extends LocalEventLog { private static final class ProviderStationaryThrottledEvent extends ProviderEvent { private final boolean mStationaryThrottled; + private final ProviderRequest mRequest; ProviderStationaryThrottledEvent(long timeDelta, String provider, - boolean stationaryThrottled) { + boolean stationaryThrottled, ProviderRequest request) { super(timeDelta, provider); mStationaryThrottled = stationaryThrottled; + mRequest = request; } @Override public String getLogString() { return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled" - : "unthrottled"); + : "unthrottled") + ", request = " + mRequest; } } diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java index ab7e526a8e68..22a675ad39ab 100644 --- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java @@ -206,7 +206,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation if (D) { Log.d(TAG, mName + " provider stationary throttled"); } - EVENT_LOG.logProviderStationaryThrottled(mName, true); + EVENT_LOG.logProviderStationaryThrottled(mName, true, mOutgoingRequest); } if (mDeliverLastLocationCallback != null) { @@ -224,7 +224,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation } } else { if (oldThrottlingIntervalMs != INTERVAL_DISABLED) { - EVENT_LOG.logProviderStationaryThrottled(mName, false); + EVENT_LOG.logProviderStationaryThrottled(mName, false, mOutgoingRequest); if (D) { Log.d(TAG, mName + " provider stationary unthrottled"); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 59f00a2a4bc7..6cded508f5b0 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -1261,7 +1261,8 @@ public class LockSettingsService extends ILockSettings.Stub { return getCredentialTypeInternal(userId) != CREDENTIAL_TYPE_NONE; } - private void setKeystorePassword(byte[] password, int userHandle) { + @VisibleForTesting /** Note: this method is overridden in unit tests */ + void setKeystorePassword(byte[] password, int userHandle) { AndroidKeyStoreMaintenance.onUserPasswordChanged(userHandle, password); } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index abcf4fb939e1..b10d56b62acc 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -384,7 +384,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (mPlaybackState == null) { return false; } - return mPlaybackState.isActive() == expected; + return mPlaybackState.isActiveState() == expected; } /** diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java index 8bd3b1e0b6ac..42b7c9d388a0 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java @@ -100,7 +100,7 @@ public class NotificationHistoryDatabase { IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION); deletionFilter.addDataScheme(SCHEME_DELETION); - mContext.registerReceiver(mFileCleaupReceiver, deletionFilter); + mContext.registerReceiver(mFileCleanupReceiver, deletionFilter); } public void init() { @@ -273,13 +273,36 @@ public class NotificationHistoryDatabase { } } + /** + * Remove the first entry from the list of history files whose file matches the given file path. + * + * This method is necessary for anything that only has an absolute file path rather than an + * AtomicFile object from the list of history files. + * + * filePath should be an absolute path. + */ + void removeFilePathFromHistory(String filePath) { + if (filePath == null) { + return; + } + + Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator(); + while (historyFileItr.hasNext()) { + final AtomicFile af = historyFileItr.next(); + if (af != null && filePath.equals(af.getBaseFile().getAbsolutePath())) { + historyFileItr.remove(); + return; + } + } + } + private void deleteFile(AtomicFile file) { if (DEBUG) { Slog.d(TAG, "Removed " + file.getBaseFile().getName()); } file.delete(); // TODO: delete all relevant bitmaps, once they exist - mHistoryFiles.remove(file); + removeFilePathFromHistory(file.getBaseFile().getAbsolutePath()); } private void scheduleDeletion(File file, long creationTime, int retentionDays) { @@ -342,7 +365,7 @@ public class NotificationHistoryDatabase { } } - private final BroadcastReceiver mFileCleaupReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mFileCleanupReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -358,7 +381,7 @@ public class NotificationHistoryDatabase { Slog.d(TAG, "Removed " + fileToDelete.getBaseFile().getName()); } fileToDelete.delete(); - mHistoryFiles.remove(fileToDelete); + removeFilePathFromHistory(filePath); } } catch (Exception e) { Slog.e(TAG, "Failed to delete notification history file", e); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 3ddfe6cdbdb4..1e6e5cb97f45 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -380,8 +380,6 @@ public class NotificationManagerService extends SystemService { static final int INVALID_UID = -1; static final String ROOT_PKG = "root"; - static final boolean ENABLE_BLOCKED_TOASTS = true; - static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] { Adjustment.KEY_CONTEXTUAL_ACTIONS, Adjustment.KEY_TEXT_REPLIES, @@ -3165,34 +3163,11 @@ public class NotificationManagerService extends SystemService { } final int callingUid = Binder.getCallingUid(); - final UserHandle callingUser = Binder.getCallingUserHandle(); + checkCallerIsSameApp(pkg); final boolean isSystemToast = isCallerSystemOrPhone() || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg); - final boolean isPackageSuspended = isPackagePaused(pkg); - final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg, - callingUid); - - final boolean appIsForeground; - final long callingIdentity = Binder.clearCallingIdentity(); - try { - appIsForeground = mActivityManager.getUidImportance(callingUid) - == IMPORTANCE_FOREGROUND; - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - - if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage - && !appIsForeground) || isPackageSuspended)) { - Slog.e(TAG, "Suppressing toast from package " + pkg - + (isPackageSuspended ? " due to package suspended." - : " by user request.")); - return; - } - boolean isAppRenderedToast = (callback != null); - if (blockToast(callingUid, isSystemToast, isAppRenderedToast)) { - Slog.w(TAG, "Blocking custom toast from package " + pkg - + " due to package not in the foreground at time the toast was posted"); + if (!checkCanEnqueueToast(pkg, callingUid, isAppRenderedToast, isSystemToast)) { return; } @@ -3246,6 +3221,39 @@ public class NotificationManagerService extends SystemService { } } + private boolean checkCanEnqueueToast(String pkg, int callingUid, + boolean isAppRenderedToast, boolean isSystemToast) { + final boolean isPackageSuspended = isPackagePaused(pkg); + final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg, + callingUid); + + final boolean appIsForeground; + final long callingIdentity = Binder.clearCallingIdentity(); + try { + appIsForeground = mActivityManager.getUidImportance(callingUid) + == IMPORTANCE_FOREGROUND; + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + + if (!isSystemToast && ((notificationsDisabledForPackage && !appIsForeground) + || isPackageSuspended)) { + Slog.e(TAG, "Suppressing toast from package " + pkg + + (isPackageSuspended ? " due to package suspended." + : " by user request.")); + return false; + } + + if (blockToast(callingUid, isSystemToast, isAppRenderedToast, + isPackageInForegroundForToast(callingUid))) { + Slog.w(TAG, "Blocking custom toast from package " + pkg + + " due to package not in the foreground at time the toast was posted"); + return false; + } + + return true; + } + @Override public void cancelToast(String pkg, IBinder token) { Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token); @@ -7692,12 +7700,13 @@ public class NotificationManagerService extends SystemService { boolean isWithinQuota = mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG) || isExemptFromRateLimiting(record.pkg, userId); + boolean isPackageInForeground = isPackageInForegroundForToast(record.uid); if (tryShowToast( - record, rateLimitingEnabled, isWithinQuota)) { + record, rateLimitingEnabled, isWithinQuota, isPackageInForeground)) { scheduleDurationReachedLocked(record, lastToastWasTextRecord); mIsCurrentToastShown = true; - if (rateLimitingEnabled) { + if (rateLimitingEnabled && !isPackageInForeground) { mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG); } return; @@ -7713,14 +7722,15 @@ public class NotificationManagerService extends SystemService { /** Returns true if it successfully showed the toast. */ private boolean tryShowToast(ToastRecord record, boolean rateLimitingEnabled, - boolean isWithinQuota) { - if (rateLimitingEnabled && !isWithinQuota) { + boolean isWithinQuota, boolean isPackageInForeground) { + if (rateLimitingEnabled && !isWithinQuota && !isPackageInForeground) { reportCompatRateLimitingToastsChange(record.uid); Slog.w(TAG, "Package " + record.pkg + " is above allowed toast quota, the " + "following toast was blocked and discarded: " + record); return false; } - if (blockToast(record.uid, record.isSystemToast, record.isAppRendered())) { + if (blockToast(record.uid, record.isSystemToast, record.isAppRendered(), + isPackageInForeground)) { Slog.w(TAG, "Blocking custom toast from package " + record.pkg + " due to package not in the foreground at the time of showing the toast"); return false; @@ -7911,10 +7921,11 @@ public class NotificationManagerService extends SystemService { * with targetSdk < R. For apps with targetSdk R+, text toasts are not app-rendered, so * isAppRenderedToast == true means it's a custom toast. */ - private boolean blockToast(int uid, boolean isSystemToast, boolean isAppRenderedToast) { + private boolean blockToast(int uid, boolean isSystemToast, boolean isAppRenderedToast, + boolean isPackageInForeground) { return isAppRenderedToast && !isSystemToast - && !isPackageInForegroundForToast(uid) + && !isPackageInForeground && CompatChanges.isChangeEnabled(CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, uid); } diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java index a9dbb2f29535..94a1f3b1c502 100644 --- a/services/core/java/com/android/server/om/OverlayReferenceMapper.java +++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java @@ -19,6 +19,8 @@ package com.android.server.om; import android.annotation.NonNull; import android.annotation.Nullable; import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Pair; import android.util.Slog; @@ -28,10 +30,10 @@ import com.android.internal.util.CollectionUtils; import com.android.server.SystemConfig; import com.android.server.pm.parsing.pkg.AndroidPackage; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -84,14 +86,15 @@ public class OverlayReferenceMapper { * See class comment for specific types. */ @GuardedBy("mLock") - private final Map<String, Map<String, Set<String>>> mActorToTargetToOverlays = new HashMap<>(); + private final ArrayMap<String, ArrayMap<String, ArraySet<String>>> mActorToTargetToOverlays = + new ArrayMap<>(); /** * Keys are actor package names, values are generic package names the actor should be able * to see. */ @GuardedBy("mLock") - private final Map<String, Set<String>> mActorPkgToPkgs = new HashMap<>(); + private final ArrayMap<String, Set<String>> mActorPkgToPkgs = new ArrayMap<>(); @GuardedBy("mLock") private boolean mDeferRebuild; @@ -160,21 +163,26 @@ public class OverlayReferenceMapper { * * @param pkg the package to add * @param otherPkgs map of other packages to consider, excluding {@param pkg} + * @return Set of packages that may have changed visibility */ - public void addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) { + public ArraySet<String> addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) { synchronized (mLock) { + ArraySet<String> changed = new ArraySet<>(); + if (!pkg.getOverlayables().isEmpty()) { - addTarget(pkg, otherPkgs); + addTarget(pkg, otherPkgs, changed); } // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) { - addOverlay(pkg, otherPkgs); + addOverlay(pkg, otherPkgs, changed); } if (!mDeferRebuild) { rebuild(); } + + return changed; } } @@ -184,27 +192,40 @@ public class OverlayReferenceMapper { * of {@link SystemConfig#getNamedActors()}. * * @param pkgName name to remove, as was added through {@link #addPkg(AndroidPackage, Map)} + * @return Set of packages that may have changed visibility */ - public void removePkg(String pkgName) { + public ArraySet<String> removePkg(String pkgName) { synchronized (mLock) { - removeTarget(pkgName); - removeOverlay(pkgName); + ArraySet<String> changedPackages = new ArraySet<>(); + removeTarget(pkgName, changedPackages); + removeOverlay(pkgName, changedPackages); if (!mDeferRebuild) { rebuild(); } + + return changedPackages; } } - private void removeTarget(String target) { + /** + * @param changedPackages Ongoing collection of packages that may have changed visibility + */ + private void removeTarget(String target, @NonNull Collection<String> changedPackages) { synchronized (mLock) { - Iterator<Map<String, Set<String>>> iterator = - mActorToTargetToOverlays.values().iterator(); - while (iterator.hasNext()) { - Map<String, Set<String>> next = iterator.next(); - next.remove(target); - if (next.isEmpty()) { - iterator.remove(); + int size = mActorToTargetToOverlays.size(); + for (int index = size - 1; index >= 0; index--) { + ArrayMap<String, ArraySet<String>> targetToOverlays = + mActorToTargetToOverlays.valueAt(index); + if (targetToOverlays.containsKey(target)) { + targetToOverlays.remove(target); + + String actor = mActorToTargetToOverlays.keyAt(index); + changedPackages.add(mProvider.getActorPkg(actor)); + + if (targetToOverlays.isEmpty()) { + mActorToTargetToOverlays.removeAt(index); + } } } } @@ -215,16 +236,19 @@ public class OverlayReferenceMapper { * * If a target overlays itself, it will not be associated with itself, as only one half of the * relationship needs to exist for visibility purposes. + * + * @param changedPackages Ongoing collection of packages that may have changed visibility */ - private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs) { + private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs, + @NonNull Collection<String> changedPackages) { synchronized (mLock) { String target = targetPkg.getPackageName(); - removeTarget(target); + removeTarget(target, changedPackages); Map<String, String> overlayablesToActors = targetPkg.getOverlayables(); for (String overlayable : overlayablesToActors.keySet()) { String actor = overlayablesToActors.get(overlayable); - addTargetToMap(actor, target); + addTargetToMap(actor, target, changedPackages); for (AndroidPackage overlayPkg : otherPkgs.values()) { Map<String, Set<String>> targetToOverlayables = @@ -235,18 +259,38 @@ public class OverlayReferenceMapper { } if (overlayables.contains(overlayable)) { - addOverlayToMap(actor, target, overlayPkg.getPackageName()); + String overlay = overlayPkg.getPackageName(); + addOverlayToMap(actor, target, overlay, changedPackages); } } } } } - private void removeOverlay(String overlay) { + /** + * @param changedPackages Ongoing collection of packages that may have changed visibility + */ + private void removeOverlay(String overlay, @NonNull Collection<String> changedPackages) { synchronized (mLock) { - for (Map<String, Set<String>> targetToOverlays : mActorToTargetToOverlays.values()) { - for (Set<String> overlays : targetToOverlays.values()) { - overlays.remove(overlay); + int actorsSize = mActorToTargetToOverlays.size(); + for (int actorIndex = actorsSize - 1; actorIndex >= 0; actorIndex--) { + ArrayMap<String, ArraySet<String>> targetToOverlays = + mActorToTargetToOverlays.valueAt(actorIndex); + int targetsSize = targetToOverlays.size(); + for (int targetIndex = targetsSize - 1; targetIndex >= 0; targetIndex--) { + final Set<String> overlays = targetToOverlays.valueAt(targetIndex); + + if (overlays.remove(overlay)) { + String actor = mActorToTargetToOverlays.keyAt(actorIndex); + changedPackages.add(mProvider.getActorPkg(actor)); + + // targetToOverlays should not be removed here even if empty as the actor + // will still have visibility to the target even if no overlays exist + } + } + + if (targetToOverlays.isEmpty()) { + mActorToTargetToOverlays.removeAt(actorIndex); } } } @@ -257,11 +301,14 @@ public class OverlayReferenceMapper { * * If an overlay targets itself, it will not be associated with itself, as only one half of the * relationship needs to exist for visibility purposes. + * + * @param changedPackages Ongoing collection of packages that may have changed visibility */ - private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs) { + private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs, + @NonNull Collection<String> changedPackages) { synchronized (mLock) { String overlay = overlayPkg.getPackageName(); - removeOverlay(overlay); + removeOverlay(overlay, changedPackages); Map<String, Set<String>> targetToOverlayables = mProvider.getTargetToOverlayables(overlayPkg); @@ -280,7 +327,7 @@ public class OverlayReferenceMapper { if (TextUtils.isEmpty(actor)) { continue; } - addOverlayToMap(actor, targetPkgName, overlay); + addOverlayToMap(actor, targetPkgName, overlay, changedPackages); } } } @@ -312,7 +359,8 @@ public class OverlayReferenceMapper { continue; } - Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor); + ArrayMap<String, ArraySet<String>> targetToOverlays = + mActorToTargetToOverlays.get(actor); Set<String> pkgs = new HashSet<>(); for (String target : targetToOverlays.keySet()) { @@ -326,36 +374,51 @@ public class OverlayReferenceMapper { } } - private void addTargetToMap(String actor, String target) { - Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor); + /** + * @param changedPackages Ongoing collection of packages that may have changed visibility + */ + private void addTargetToMap(String actor, String target, + @NonNull Collection<String> changedPackages) { + ArrayMap<String, ArraySet<String>> targetToOverlays = mActorToTargetToOverlays.get(actor); if (targetToOverlays == null) { - targetToOverlays = new HashMap<>(); + targetToOverlays = new ArrayMap<>(); mActorToTargetToOverlays.put(actor, targetToOverlays); } - Set<String> overlays = targetToOverlays.get(target); + ArraySet<String> overlays = targetToOverlays.get(target); if (overlays == null) { - overlays = new HashSet<>(); + overlays = new ArraySet<>(); targetToOverlays.put(target, overlays); } + + // For now, only actors themselves can gain or lose visibility through package changes + changedPackages.add(mProvider.getActorPkg(actor)); } - private void addOverlayToMap(String actor, String target, String overlay) { + /** + * @param changedPackages Ongoing collection of packages that may have changed visibility + */ + private void addOverlayToMap(String actor, String target, String overlay, + @NonNull Collection<String> changedPackages) { synchronized (mLock) { - Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor); + ArrayMap<String, ArraySet<String>> targetToOverlays = + mActorToTargetToOverlays.get(actor); if (targetToOverlays == null) { - targetToOverlays = new HashMap<>(); + targetToOverlays = new ArrayMap<>(); mActorToTargetToOverlays.put(actor, targetToOverlays); } - Set<String> overlays = targetToOverlays.get(target); + ArraySet<String> overlays = targetToOverlays.get(target); if (overlays == null) { - overlays = new HashSet<>(); + overlays = new ArraySet<>(); targetToOverlays.put(target, overlays); } overlays.add(overlay); } + + // For now, only actors themselves can gain or lose visibility through package changes + changedPackages.add(mProvider.getActorPkg(actor)); } public interface Provider { diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index ca8202f5f94b..4f527f26cbb3 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -662,11 +662,27 @@ public class AppsFilter implements Watchable, Snappable { removePackage(newPkgSetting); } mStateProvider.runWithState((settings, users) -> { - addPackageInternal(newPkgSetting, settings); + ArraySet<String> additionalChangedPackages = + addPackageInternal(newPkgSetting, settings); synchronized (mCacheLock) { if (mShouldFilterCache != null) { updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting, settings, users, settings.size()); + if (additionalChangedPackages != null) { + for (int index = 0; index < additionalChangedPackages.size(); index++) { + String changedPackage = additionalChangedPackages.valueAt(index); + PackageSetting changedPkgSetting = settings.get(changedPackage); + if (changedPkgSetting == null) { + // It's possible for the overlay mapper to know that an actor + // package changed via an explicit reference, even if the actor + // isn't installed, so skip if that's the case. + continue; + } + + updateShouldFilterCacheForPackage(mShouldFilterCache, null, + changedPkgSetting, settings, users, settings.size()); + } + } } // else, rebuild entire cache when system is ready } }); @@ -676,7 +692,12 @@ public class AppsFilter implements Watchable, Snappable { } } - private void addPackageInternal(PackageSetting newPkgSetting, + /** + * @return Additional packages that may have had their viewing visibility changed and may need + * to be updated in the cache. Returns null if there are no additional packages. + */ + @Nullable + private ArraySet<String> addPackageInternal(PackageSetting newPkgSetting, ArrayMap<String, PackageSetting> existingSettings) { if (Objects.equals("android", newPkgSetting.name)) { // let's set aside the framework signatures @@ -692,8 +713,7 @@ public class AppsFilter implements Watchable, Snappable { final AndroidPackage newPkg = newPkgSetting.pkg; if (newPkg == null) { - // nothing to add - return; + return null; } if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) { @@ -765,8 +785,13 @@ public class AppsFilter implements Watchable, Snappable { existingPkgs.put(pkgSetting.name, pkgSetting.pkg); } } - mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs); + + ArraySet<String> changedPackages = + mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs); + mFeatureConfig.updatePackageState(newPkgSetting, false /*removed*/); + + return changedPackages; } @GuardedBy("mCacheLock") @@ -1080,7 +1105,9 @@ public class AppsFilter implements Watchable, Snappable { } } - mOverlayReferenceMapper.removePkg(setting.name); + ArraySet<String> additionalChangedPackages = + mOverlayReferenceMapper.removePkg(setting.name); + mFeatureConfig.updatePackageState(setting, true /*removed*/); // After removing all traces of the package, if it's part of a shared user, re-add other @@ -1109,6 +1136,25 @@ public class AppsFilter implements Watchable, Snappable { siblingSetting, settings, users, settings.size()); } } + + if (mShouldFilterCache != null) { + if (additionalChangedPackages != null) { + for (int index = 0; index < additionalChangedPackages.size(); index++) { + String changedPackage = additionalChangedPackages.valueAt(index); + PackageSetting changedPkgSetting = settings.get(changedPackage); + if (changedPkgSetting == null) { + // It's possible for the overlay mapper to know that an actor + // package changed via an explicit reference, even if the actor + // isn't installed, so skip if that's the case. + continue; + } + + updateShouldFilterCacheForPackage(mShouldFilterCache, null, + changedPkgSetting, settings, users, settings.size()); + } + } + } + onChanged(); } }); diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java index 6875b8a5abeb..ec79483e0f34 100644 --- a/services/core/java/com/android/server/pm/DumpState.java +++ b/services/core/java/com/android/server/pm/DumpState.java @@ -45,6 +45,7 @@ public final class DumpState { public static final int DUMP_QUERIES = 1 << 26; public static final int DUMP_KNOWN_PACKAGES = 1 << 27; public static final int DUMP_PER_UID_READ_TIMEOUTS = 1 << 28; + public static final int DUMP_SNAPSHOT_STATISTICS = 1 << 29; public static final int OPTION_SHOW_FILTERS = 1 << 0; public static final int OPTION_DUMP_ALL_COMPONENTS = 1 << 1; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 85c5a5ea84b8..59039baf2764 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1914,6 +1914,18 @@ public class PackageManagerService extends IPackageManager.Stub */ private interface Computer { + /** + * Administrative statistics: record that the snapshot has been used. Every call + * to use() increments the usage counter. + */ + void use(); + + /** + * Fetch the snapshot usage counter. + * @return The number of times this snapshot was used. + */ + int getUsed(); + @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits); @@ -2065,6 +2077,9 @@ public class PackageManagerService extends IPackageManager.Stub */ private static class ComputerEngine implements Computer { + // The administrative use counter. + private int mUsed = 0; + // Cached attributes. The names in this class are the same as the // names in PackageManagerService; see that class for documentation. protected final Settings mSettings; @@ -2157,6 +2172,20 @@ public class PackageManagerService extends IPackageManager.Stub mService = args.service; } + /** + * Record that the snapshot was used. + */ + public void use() { + mUsed++; + } + + /** + * Return the usage counter. + */ + public int getUsed() { + return mUsed; + } + public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, @@ -4885,121 +4914,11 @@ public class PackageManagerService extends IPackageManager.Stub */ private final Object mSnapshotLock = new Object(); - // A counter of all queries that hit the current snapshot. - @GuardedBy("mSnapshotLock") - private int mSnapshotHits = 0; - - // A class to record snapshot statistics. - private static class SnapshotStatistics { - // A build time is "big" if it takes longer than 5ms. - private static final long SNAPSHOT_BIG_BUILD_TIME_NS = TimeUnit.MILLISECONDS.toNanos(5); - - // A snapshot is in quick succession to the previous snapshot if it less than - // 100ms since the previous snapshot. - private static final long SNAPSHOT_QUICK_REBUILD_INTERVAL_NS = - TimeUnit.MILLISECONDS.toNanos(100); - - // The interval between snapshot statistics logging, in ns. - private static final long SNAPSHOT_LOG_INTERVAL_NS = TimeUnit.MINUTES.toNanos(10); - - // The throttle parameters for big build reporting. Do not report more than this - // many events in a single log interval. - private static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10; - - // The time the snapshot statistics were last logged. - private long mStatisticsSent = 0; - - // The number of build events logged since the last periodic log. - private int mLoggedBuilds = 0; - - // The time of the last build. - private long mLastBuildTime = 0; - - // The number of times the snapshot has been rebuilt since the statistics were - // last logged. - private int mRebuilds = 0; - - // The number of times the snapshot has been used since it was rebuilt. - private int mReused = 0; - - // The number of "big" build times since the last log. "Big" is defined by - // SNAPSHOT_BIG_BUILD_TIME. - private int mBigBuilds = 0; - - // The number of quick rebuilds. "Quick" is defined by - // SNAPSHOT_QUICK_REBUILD_INTERVAL_NS. - private int mQuickRebuilds = 0; - - // The time take to build a snapshot. This is cumulative over the rebuilds recorded - // in mRebuilds, so the average time to build a snapshot is given by - // mBuildTimeNs/mRebuilds. - private int mBuildTimeNs = 0; - - // The maximum build time since the last log. - private long mMaxBuildTimeNs = 0; - - // The constant that converts ns to ms. This is the divisor. - private final long NS_TO_MS = TimeUnit.MILLISECONDS.toNanos(1); - - // Convert ns to an int ms. The maximum range of this method is about 24 days. - // There is no expectation that an event will take longer than that. - private int nsToMs(long ns) { - return (int) (ns / NS_TO_MS); - } - - // The single method records a rebuild. The "now" parameter is passed in because - // the caller needed it to computer the duration, so pass it in to avoid - // recomputing it. - private void rebuild(long now, long done, int hits) { - if (mStatisticsSent == 0) { - mStatisticsSent = now; - } - final long elapsed = now - mLastBuildTime; - final long duration = done - now; - mLastBuildTime = now; - - if (mMaxBuildTimeNs < duration) { - mMaxBuildTimeNs = duration; - } - mRebuilds++; - mReused += hits; - mBuildTimeNs += duration; - - boolean log_build = false; - if (duration > SNAPSHOT_BIG_BUILD_TIME_NS) { - log_build = true; - mBigBuilds++; - } - if (elapsed < SNAPSHOT_QUICK_REBUILD_INTERVAL_NS) { - log_build = true; - mQuickRebuilds++; - } - if (log_build && mLoggedBuilds < SNAPSHOT_BUILD_REPORT_LIMIT) { - EventLogTags.writePmSnapshotRebuild(nsToMs(duration), nsToMs(elapsed)); - mLoggedBuilds++; - } - - final long log_interval = now - mStatisticsSent; - if (log_interval >= SNAPSHOT_LOG_INTERVAL_NS) { - EventLogTags.writePmSnapshotStats(mRebuilds, mReused, - mBigBuilds, mQuickRebuilds, - nsToMs(mMaxBuildTimeNs), - nsToMs(mBuildTimeNs)); - mStatisticsSent = now; - mRebuilds = 0; - mReused = 0; - mBuildTimeNs = 0; - mMaxBuildTimeNs = 0; - mBigBuilds = 0; - mQuickRebuilds = 0; - mLoggedBuilds = 0; - } - } - } - - // Snapshot statistics. - @GuardedBy("mLock") - private final SnapshotStatistics mSnapshotStatistics = new SnapshotStatistics(); + /** + * The snapshot statistics. These are collected to track performance and to identify + * situations in which the snapshots are misbehaving. + */ + private final SnapshotStatistics mSnapshotStatistics; // The snapshot disable/enable switch. An image with the flag set true uses snapshots // and an image with the flag set false does not use snapshots. @@ -5033,10 +4952,9 @@ public class PackageManagerService extends IPackageManager.Stub Computer c = mSnapshotComputer; if (sSnapshotCorked && (c != null)) { // Snapshots are corked, which means new ones should not be built right now. + c.use(); return c; } - // Deliberately capture the value pre-increment - final int hits = mSnapshotHits++; if (sSnapshotInvalid || (c == null)) { // The snapshot is invalid if it is marked as invalid or if it is null. If it // is null, then it is currently being rebuilt by rebuildSnapshot(). @@ -5046,7 +4964,7 @@ public class PackageManagerService extends IPackageManager.Stub // self-consistent (the lock is being held) and is current as of the time // this function is entered. if (sSnapshotInvalid) { - rebuildSnapshot(hits); + rebuildSnapshot(); } // Guaranteed to be non-null. mSnapshotComputer is only be set to null @@ -5056,6 +4974,7 @@ public class PackageManagerService extends IPackageManager.Stub c = mSnapshotComputer; } } + c.use(); return c; } } @@ -5065,16 +4984,16 @@ public class PackageManagerService extends IPackageManager.Stub * threads from using the invalid computer until it is rebuilt. */ @GuardedBy("mLock") - private void rebuildSnapshot(int hits) { - final long now = System.nanoTime(); + private void rebuildSnapshot() { + final long now = SystemClock.currentTimeMicro(); + final int hits = mSnapshotComputer == null ? -1 : mSnapshotComputer.getUsed(); mSnapshotComputer = null; sSnapshotInvalid = false; final Snapshot args = new Snapshot(Snapshot.SNAPPED); mSnapshotComputer = new ComputerEngine(args); - final long done = System.nanoTime(); + final long done = SystemClock.currentTimeMicro(); mSnapshotStatistics.rebuild(now, done, hits); - mSnapshotHits = 0; } /** @@ -6327,6 +6246,7 @@ public class PackageManagerService extends IPackageManager.Stub mSnapshotEnabled = false; mLiveComputer = createLiveComputer(); mSnapshotComputer = null; + mSnapshotStatistics = null; mPackages.putAll(testParams.packages); mEnableFreeCacheV2 = testParams.enableFreeCacheV2; @@ -6479,17 +6399,20 @@ public class PackageManagerService extends IPackageManager.Stub mDomainVerificationManager = injector.getDomainVerificationManagerInternal(); mDomainVerificationManager.setConnection(mDomainVerificationConnection); - // Create the computer as soon as the state objects have been installed. The - // cached computer is the same as the live computer until the end of the - // constructor, at which time the invalidation method updates it. The cache is - // corked initially to ensure a cached computer is not built until the end of the - // constructor. - mSnapshotEnabled = SNAPSHOT_ENABLED; - sSnapshotCorked = true; - sSnapshotInvalid = true; - mLiveComputer = createLiveComputer(); - mSnapshotComputer = null; - registerObserver(); + synchronized (mLock) { + // Create the computer as soon as the state objects have been installed. The + // cached computer is the same as the live computer until the end of the + // constructor, at which time the invalidation method updates it. The cache is + // corked initially to ensure a cached computer is not built until the end of the + // constructor. + mSnapshotEnabled = SNAPSHOT_ENABLED; + sSnapshotCorked = true; + sSnapshotInvalid = true; + mSnapshotStatistics = new SnapshotStatistics(); + mLiveComputer = createLiveComputer(); + mSnapshotComputer = null; + registerObserver(); + } // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { @@ -23957,6 +23880,7 @@ public class PackageManagerService extends IPackageManager.Stub pw.println(" dexopt: dump dexopt state"); pw.println(" compiler-stats: dump compiler statistics"); pw.println(" service-permissions: dump permissions required by services"); + pw.println(" snapshot: dump snapshot statistics"); pw.println(" known-packages: dump known packages"); pw.println(" <package.name>: info about given package"); return; @@ -24105,6 +24029,8 @@ public class PackageManagerService extends IPackageManager.Stub dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES); } else if ("t".equals(cmd) || "timeouts".equals(cmd)) { dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS); + } else if ("snapshot".equals(cmd)) { + dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS); } else if ("write".equals(cmd)) { synchronized (mLock) { writeSettingsLPrTEMP(); @@ -24433,6 +24359,22 @@ public class PackageManagerService extends IPackageManager.Stub pw.println(")"); } } + + if (!checkin && dumpState.isDumping(DumpState.DUMP_SNAPSHOT_STATISTICS)) { + pw.println("Snapshot statistics"); + if (!mSnapshotEnabled) { + pw.println(" Snapshots disabled"); + } else { + int hits = 0; + synchronized (mSnapshotLock) { + if (mSnapshotComputer != null) { + hits = mSnapshotComputer.getUsed(); + } + } + final long now = SystemClock.currentTimeMicro(); + mSnapshotStatistics.dump(pw, " ", now, hits, true); + } + } } /** diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java new file mode 100644 index 000000000000..c425bad50ae8 --- /dev/null +++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java @@ -0,0 +1,622 @@ +/* + * 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.pm; + +import android.annotation.Nullable; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.text.TextUtils; + +import com.android.server.EventLogTags; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Locale; + +/** + * This class records statistics about PackageManagerService snapshots. It maintains two sets of + * statistics: a periodic set which represents the last 10 minutes, and a cumulative set since + * process boot. The key metrics that are recorded are: + * <ul> + * <li> The time to create a snapshot - this is the performance cost of a snapshot + * <li> The lifetime of the snapshot - creation time over lifetime is the amortized cost + * <li> The number of times a snapshot is reused - this is the number of times lock + * contention was avoided. + * </ul> + + * The time conversions in this class are designed to keep arithmetic using ints, rather + * than longs. Raw times are supplied as longs in units of us. These are left long. + * Rebuild durations however, are converted to ints. An int can express a duration of + * approximately 35 minutes. This is longer than any expected snapshot rebuild time, so + * an int is satisfactory. The exception is the cumulative rebuild time over the course + * of a monitoring cycle: this value is kept long since the cycle time is one week and in + * a badly behaved system, the rebuild time might exceed 35 minutes. + + * @hide + */ +public class SnapshotStatistics { + /** + * The interval at which statistics should be ticked. It is 60s. The interval is in + * units of milliseconds because that is what's required by Handler.sendMessageDelayed(). + */ + public static final int SNAPSHOT_TICK_INTERVAL_MS = 60 * 1000; + + /** + * The number of ticks for long statistics. This is one week. + */ + public static final int SNAPSHOT_LONG_TICKS = 7 * 24 * 60; + + /** + * The number snapshot event logs that can be generated in a single logging interval. + * A small number limits the logging generated by this class. A snapshot event log is + * generated for every big snapshot build time, up to the limit, or whenever the + * maximum build time is exceeded in the logging interval. + */ + public static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10; + + /** + * The number of microseconds in a millisecond. + */ + private static final int US_IN_MS = 1000; + + /** + * A snapshot build time is "big" if it takes longer than 10ms. + */ + public static final int SNAPSHOT_BIG_BUILD_TIME_US = 10 * US_IN_MS; + + /** + * A snapshot build time is reportable if it takes longer than 30ms. Testing shows + * that this is very rare. + */ + public static final int SNAPSHOT_REPORTABLE_BUILD_TIME_US = 30 * US_IN_MS; + + /** + * A snapshot is short-lived it used fewer than 5 times. + */ + public static final int SNAPSHOT_SHORT_LIFETIME = 5; + + /** + * The lock to control access to this object. + */ + private final Object mLock = new Object(); + + /** + * The bins for the build time histogram. Values are in us. + */ + private final BinMap mTimeBins; + + /** + * The bins for the snapshot use histogram. + */ + private final BinMap mUseBins; + + /** + * The number of events reported in the current tick. + */ + private int mEventsReported = 0; + + /** + * The tick counter. At the default tick interval, this wraps every 4000 years or so. + */ + private int mTicks = 0; + + /** + * The handler used for the periodic ticks. + */ + private Handler mHandler = null; + + /** + * Convert ns to an int ms. The maximum range of this method is about 24 days. There + * is no expectation that an event will take longer than that. + */ + private int usToMs(int us) { + return us / US_IN_MS; + } + + /** + * This class exists to provide a fast bin lookup for histograms. An instance has an + * integer array that maps incoming values to bins. Values larger than the array are + * mapped to the top-most bin. + */ + private static class BinMap { + + // The number of bins + private int mCount; + // The mapping of low integers to bins + private int[] mBinMap; + // The maximum mapped value. Values at or above this are mapped to the + // top bin. + private int mMaxBin; + // A copy of the original key + private int[] mUserKey; + + /** + * Create a bin map. The input is an array of integers, which must be + * monotonically increasing (this is not checked). The result is an integer array + * as long as the largest value in the input. + */ + BinMap(int[] userKey) { + mUserKey = Arrays.copyOf(userKey, userKey.length); + // The number of bins is the length of the keys, plus 1 (for the max). + mCount = mUserKey.length + 1; + // The maximum value is one more than the last one in the map. + mMaxBin = mUserKey[mUserKey.length - 1] + 1; + mBinMap = new int[mMaxBin + 1]; + + int j = 0; + for (int i = 0; i < mUserKey.length; i++) { + while (j <= mUserKey[i]) { + mBinMap[j] = i; + j++; + } + } + mBinMap[mMaxBin] = mUserKey.length; + } + + /** + * Map a value to a bin. + */ + public int getBin(int x) { + if (x >= 0 && x < mMaxBin) { + return mBinMap[x]; + } else if (x >= mMaxBin) { + return mBinMap[mMaxBin]; + } else { + // x is negative. The bin will not be used. + return 0; + } + } + + /** + * The number of bins in this map + */ + public int count() { + return mCount; + } + + /** + * For convenience, return the user key. + */ + public int[] userKeys() { + return mUserKey; + } + } + + /** + * A complete set of statistics. These are public, making it simpler for a client to + * fetch the individual fields. + */ + public class Stats { + + /** + * The start time for this set of statistics, in us. + */ + public long mStartTimeUs = 0; + + /** + * The completion time for this set of statistics, in ns. A value of zero means + * the statistics are still active. + */ + public long mStopTimeUs = 0; + + /** + * The build-time histogram. The total number of rebuilds is the sum over the + * histogram entries. + */ + public int[] mTimes; + + /** + * The reuse histogram. The total number of snapshot uses is the sum over the + * histogram entries. + */ + public int[] mUsed; + + /** + * The total number of rebuilds. This could be computed by summing over the use + * bins, but is maintained separately for convenience. + */ + public int mTotalBuilds = 0; + + /** + * The total number of times any snapshot was used. + */ + public int mTotalUsed = 0; + + /** + * The total number of builds that count as big, which means they took longer than + * SNAPSHOT_BIG_BUILD_TIME_NS. + */ + public int mBigBuilds = 0; + + /** + * The total number of short-lived snapshots + */ + public int mShortLived = 0; + + /** + * The time taken to build snapshots. This is cumulative over the rebuilds + * recorded in mRebuilds, so the average time to build a snapshot is given by + * mBuildTimeNs/mRebuilds. Note that this cannot be computed from the histogram. + */ + public long mTotalTimeUs = 0; + + /** + * The maximum build time since the last log. + */ + public int mMaxBuildTimeUs = 0; + + /** + * Record the rebuild. The parameters are the length of time it took to build the + * latest snapshot, and the number of times the _previous_ snapshot was used. A + * negative value for used signals an invalid value, which is the case the first + * time a snapshot is every built. + */ + private void rebuild(int duration, int used, + int buildBin, int useBin, boolean big, boolean quick) { + mTotalBuilds++; + mTimes[buildBin]++; + + if (used >= 0) { + mTotalUsed += used; + mUsed[useBin]++; + } + + mTotalTimeUs += duration; + boolean reportIt = false; + + if (big) { + mBigBuilds++; + } + if (quick) { + mShortLived++; + } + if (mMaxBuildTimeUs < duration) { + mMaxBuildTimeUs = duration; + } + } + + private Stats(long now) { + mStartTimeUs = now; + mTimes = new int[mTimeBins.count()]; + mUsed = new int[mUseBins.count()]; + } + + /** + * Create a copy of the argument. The copy is made under lock but can then be + * used without holding the lock. + */ + private Stats(Stats orig) { + mStartTimeUs = orig.mStartTimeUs; + mStopTimeUs = orig.mStopTimeUs; + mTimes = Arrays.copyOf(orig.mTimes, orig.mTimes.length); + mUsed = Arrays.copyOf(orig.mUsed, orig.mUsed.length); + mTotalBuilds = orig.mTotalBuilds; + mTotalUsed = orig.mTotalUsed; + mBigBuilds = orig.mBigBuilds; + mShortLived = orig.mShortLived; + mTotalTimeUs = orig.mTotalTimeUs; + mMaxBuildTimeUs = orig.mMaxBuildTimeUs; + } + + /** + * Set the end time for the statistics. The end time is used only for reporting + * in the dump() method. + */ + private void complete(long stop) { + mStopTimeUs = stop; + } + + /** + * Format a time span into ddd:HH:MM:SS. The input is in us. + */ + private String durationToString(long us) { + // s has a range of several years + int s = (int) (us / (1000 * 1000)); + int m = s / 60; + s %= 60; + int h = m / 60; + m %= 60; + int d = h / 24; + h %= 24; + if (d != 0) { + return TextUtils.formatSimple("%2d:%02d:%02d:%02d", d, h, m, s); + } else if (h != 0) { + return TextUtils.formatSimple("%2s %02d:%02d:%02d", "", h, m, s); + } else { + return TextUtils.formatSimple("%2s %2s %2d:%02d", "", "", m, s); + } + } + + /** + * Print the prefix for dumping. This does not generate a line to the output. + */ + private void dumpPrefix(PrintWriter pw, String indent, long now, boolean header, + String title) { + pw.print(indent + " "); + if (header) { + pw.format(Locale.US, "%-23s", title); + } else { + pw.format(Locale.US, "%11s", durationToString(now - mStartTimeUs)); + if (mStopTimeUs != 0) { + pw.format(Locale.US, " %11s", durationToString(now - mStopTimeUs)); + } else { + pw.format(Locale.US, " %11s", "now"); + } + } + } + + /** + * Dump the summary statistics record. Choose the header or the data. + * number of builds + * number of uses + * number of big builds + * number of short lifetimes + * cumulative build time, in seconds + * maximum build time, in ms + */ + private void dumpStats(PrintWriter pw, String indent, long now, boolean header) { + dumpPrefix(pw, indent, now, header, "Summary stats"); + if (header) { + pw.format(Locale.US, " %10s %10s %10s %10s %10s %10s", + "TotBlds", "TotUsed", "BigBlds", "ShortLvd", + "TotTime", "MaxTime"); + } else { + pw.format(Locale.US, + " %10d %10d %10d %10d %10d %10d", + mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived, + mTotalTimeUs / 1000, mMaxBuildTimeUs / 1000); + } + pw.println(); + } + + /** + * Dump the build time histogram. Choose the header or the data. + */ + private void dumpTimes(PrintWriter pw, String indent, long now, boolean header) { + dumpPrefix(pw, indent, now, header, "Build times"); + if (header) { + int[] keys = mTimeBins.userKeys(); + for (int i = 0; i < keys.length; i++) { + pw.format(Locale.US, " %10s", + TextUtils.formatSimple("<= %dms", keys[i])); + } + pw.format(Locale.US, " %10s", + TextUtils.formatSimple("> %dms", keys[keys.length - 1])); + } else { + for (int i = 0; i < mTimes.length; i++) { + pw.format(Locale.US, " %10d", mTimes[i]); + } + } + pw.println(); + } + + /** + * Dump the usage histogram. Choose the header or the data. + */ + private void dumpUsage(PrintWriter pw, String indent, long now, boolean header) { + dumpPrefix(pw, indent, now, header, "Use counters"); + if (header) { + int[] keys = mUseBins.userKeys(); + for (int i = 0; i < keys.length; i++) { + pw.format(Locale.US, " %10s", TextUtils.formatSimple("<= %d", keys[i])); + } + pw.format(Locale.US, " %10s", + TextUtils.formatSimple("> %d", keys[keys.length - 1])); + } else { + for (int i = 0; i < mUsed.length; i++) { + pw.format(Locale.US, " %10d", mUsed[i]); + } + } + pw.println(); + } + + /** + * Dump something, based on the "what" parameter. + */ + private void dump(PrintWriter pw, String indent, long now, boolean header, String what) { + if (what.equals("stats")) { + dumpStats(pw, indent, now, header); + } else if (what.equals("times")) { + dumpTimes(pw, indent, now, header); + } else if (what.equals("usage")) { + dumpUsage(pw, indent, now, header); + } else { + throw new IllegalArgumentException("unrecognized choice: " + what); + } + } + + /** + * Report the object via an event. Presumably the record indicates an anomalous + * incident. + */ + private void report() { + EventLogTags.writePmSnapshotStats( + mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived, + mMaxBuildTimeUs / US_IN_MS, mTotalTimeUs / US_IN_MS); + } + } + + /** + * Long statistics. These roll over approximately every week. + */ + private Stats[] mLong; + + /** + * Short statistics. These roll over approximately every minute; + */ + private Stats[] mShort; + + /** + * The time of the last build. This can be used to compute the length of time a + * snapshot existed before being replaced. + */ + private long mLastBuildTime = 0; + + /** + * Create a snapshot object. Initialize the bin levels. The last bin catches + * everything that is not caught earlier, so its value is not really important. + */ + public SnapshotStatistics() { + // Create the bin thresholds. The time bins are in units of us. + mTimeBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 }); + mUseBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 }); + + // Create the raw statistics + final long now = SystemClock.currentTimeMicro(); + mLong = new Stats[2]; + mLong[0] = new Stats(now); + mShort = new Stats[10]; + mShort[0] = new Stats(now); + + // Create the message handler for ticks and start the ticker. + mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + SnapshotStatistics.this.handleMessage(msg); + } + }; + scheduleTick(); + } + + /** + * Handle a message. The only messages are ticks, so the message parameter is ignored. + */ + private void handleMessage(@Nullable Message msg) { + tick(); + scheduleTick(); + } + + /** + * Schedule one tick, a tick interval in the future. + */ + private void scheduleTick() { + mHandler.sendEmptyMessageDelayed(0, SNAPSHOT_TICK_INTERVAL_MS); + } + + /** + * Record a rebuild. Cumulative and current statistics are updated. Events may be + * generated. + * @param now The time at which the snapshot rebuild began, in ns. + * @param done The time at which the snapshot rebuild completed, in ns. + * @param hits The number of times the previous snapshot was used. + */ + public void rebuild(long now, long done, int hits) { + // The duration has a span of about 2000s + final int duration = (int) (done - now); + boolean reportEvent = false; + synchronized (mLock) { + mLastBuildTime = now; + + final int timeBin = mTimeBins.getBin(duration / 1000); + final int useBin = mUseBins.getBin(hits); + final boolean big = duration >= SNAPSHOT_BIG_BUILD_TIME_US; + final boolean quick = hits <= SNAPSHOT_SHORT_LIFETIME; + + mShort[0].rebuild(duration, hits, timeBin, useBin, big, quick); + mLong[0].rebuild(duration, hits, timeBin, useBin, big, quick); + if (duration >= SNAPSHOT_REPORTABLE_BUILD_TIME_US) { + if (mEventsReported++ < SNAPSHOT_BUILD_REPORT_LIMIT) { + reportEvent = true; + } + } + } + // The IO to the logger is done outside the lock. + if (reportEvent) { + // Report the first N big builds, and every new maximum after that. + EventLogTags.writePmSnapshotRebuild(duration / US_IN_MS, hits); + } + } + + /** + * Roll a stats array. Shift the elements up an index and create a new element at + * index zero. The old element zero is completed with the specified time. + */ + private void shift(Stats[] s, long now) { + s[0].complete(now); + for (int i = s.length - 1; i > 0; i--) { + s[i] = s[i - 1]; + } + s[0] = new Stats(now); + } + + /** + * Roll the statistics. + * <ul> + * <li> Roll the quick statistics immediately. + * <li> Roll the long statistics every SNAPSHOT_LONG_TICKER ticks. The long + * statistics hold a week's worth of data. + * <li> Roll the logging statistics every SNAPSHOT_LOGGING_TICKER ticks. The logging + * statistics hold 10 minutes worth of data. + * </ul> + */ + private void tick() { + synchronized (mLock) { + long now = SystemClock.currentTimeMicro(); + mTicks++; + if (mTicks % SNAPSHOT_LONG_TICKS == 0) { + shift(mLong, now); + } + shift(mShort, now); + mEventsReported = 0; + } + } + + /** + * Dump the statistics. The header is dumped from l[0], so that must not be null. + */ + private void dump(PrintWriter pw, String indent, long now, Stats[] l, Stats[] s, String what) { + l[0].dump(pw, indent, now, true, what); + for (int i = 0; i < s.length; i++) { + if (s[i] != null) { + s[i].dump(pw, indent, now, false, what); + } + } + for (int i = 0; i < l.length; i++) { + if (l[i] != null) { + l[i].dump(pw, indent, now, false, what); + } + } + } + + /** + * Dump the statistics. The format is compatible with the PackageManager dumpsys + * output. + */ + public void dump(PrintWriter pw, String indent, long now, int unrecorded, boolean full) { + // Grab the raw statistics under lock, but print them outside of the lock. + Stats[] l; + Stats[] s; + synchronized (mLock) { + l = Arrays.copyOf(mLong, mLong.length); + l[0] = new Stats(l[0]); + s = Arrays.copyOf(mShort, mShort.length); + s[0] = new Stats(s[0]); + } + pw.format(Locale.US, "%s Unrecorded hits %d", indent, unrecorded); + pw.println(); + dump(pw, indent, now, l, s, "stats"); + if (!full) { + return; + } + pw.println(); + dump(pw, indent, now, l, s, "times"); + pw.println(); + dump(pw, indent, now, l, s, "usage"); + } +} diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index e913829ea9e2..81cfbf7437fa 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -85,6 +85,15 @@ "include-annotation": "android.platform.test.annotations.Presubmit" } ] + }, + { + "name": "PackageManagerServiceHostTests", + "file_patterns": ["AppsFilter\\.java"], + "options": [ + { + "include-filter": "com.android.server.pm.test.OverlayActorVisibilityTest" + } + ] } ], "postsubmit": [ diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index bf2b3c7f491f..a8a6a723ce74 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.parsing.component.ParsedIntentInfo; import android.os.Build; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Patterns; @@ -32,7 +33,6 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.util.List; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -251,6 +251,10 @@ public class DomainVerificationCollector { * improve the reliability of any legacy verifiers. */ private boolean isValidHost(String host) { + if (TextUtils.isEmpty(host)) { + return false; + } + mDomainMatcher.reset(host); return mDomainMatcher.matches(); } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java index 44ff3eb4b942..246810f4d796 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java @@ -22,6 +22,8 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.text.TextUtils; +import android.util.Patterns; import com.android.internal.util.CollectionUtils; import com.android.server.compat.PlatformCompat; @@ -29,9 +31,13 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.util.Set; +import java.util.regex.Matcher; public final class DomainVerificationUtils { + private static final ThreadLocal<Matcher> sCachedMatcher = ThreadLocal.withInitial( + () -> Patterns.DOMAIN_NAME.matcher("")); + /** * Consolidates package exception messages. A generic unavailable message is included since the * caller doesn't bother to check why the package isn't available. @@ -48,6 +54,15 @@ public final class DomainVerificationUtils { return false; } + String host = intent.getData().getHost(); + if (TextUtils.isEmpty(host)) { + return false; + } + + if (!sCachedMatcher.get().reset(host).matches()) { + return false; + } + Set<String> categories = intent.getCategories(); int categoriesSize = CollectionUtils.size(categories); if (categoriesSize > 2) { diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 20c1c3c8b738..14cab382d405 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -120,6 +120,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @Override public boolean updateConfiguration(TimeConfiguration timeConfiguration) { + enforceManageTimeDetectorPermission(); // TODO(b/172891783) Add actual logic return false; } diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java index a59b368ec321..88180239da67 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java @@ -119,18 +119,18 @@ public class UnderlyingNetworkTracker { if (!mIsQuitting) { mRouteSelectionCallback = new RouteSelectionCallback(); mConnectivityManager.requestBackgroundNetwork( - getRouteSelectionRequest(), mHandler, mRouteSelectionCallback); + getRouteSelectionRequest(), mRouteSelectionCallback, mHandler); mWifiBringupCallback = new NetworkBringupCallback(); mConnectivityManager.requestBackgroundNetwork( - getWifiNetworkRequest(), mHandler, mWifiBringupCallback); + getWifiNetworkRequest(), mWifiBringupCallback, mHandler); for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { final NetworkBringupCallback cb = new NetworkBringupCallback(); mCellBringupCallbacks.add(cb); mConnectivityManager.requestBackgroundNetwork( - getCellNetworkRequestForSubId(subId), mHandler, cb); + getCellNetworkRequestForSubId(subId), cb, mHandler); } } else { mRouteSelectionCallback = null; @@ -154,14 +154,14 @@ public class UnderlyingNetworkTracker { * Builds the Route selection request * * <p>This request is guaranteed to select carrier-owned, non-VCN underlying networks by virtue - * of a populated set of subIds as expressed in NetworkCapabilities#getSubIds(). Only carrier - * owned networks may be selected, as the request specifies only subIds in the VCN's + * of a populated set of subIds as expressed in NetworkCapabilities#getSubscriptionIds(). Only + * carrier owned networks may be selected, as the request specifies only subIds in the VCN's * subscription group, while the VCN networks are excluded by virtue of not having subIds set on * the VCN-exposed networks. */ private NetworkRequest getRouteSelectionRequest() { return getBaseNetworkRequestBuilder() - .setSubIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) + .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) .build(); } @@ -177,7 +177,7 @@ public class UnderlyingNetworkTracker { private NetworkRequest getWifiNetworkRequest() { return getBaseNetworkRequestBuilder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) + .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) .build(); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 89b7bbd45072..9acbdcc177cc 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2378,6 +2378,46 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + /** + * Propagate a wake event to the wallpaper engine. + */ + public void notifyWakingUp(int x, int y, @NonNull Bundle extras) { + synchronized (mLock) { + final WallpaperData data = mWallpaperMap.get(mCurrentUserId); + data.connection.forEachDisplayConnector( + displayConnector -> { + if (displayConnector.mEngine != null) { + try { + displayConnector.mEngine.dispatchWallpaperCommand( + WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + }); + } + } + + /** + * Propagate a sleep event to the wallpaper engine. + */ + public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) { + synchronized (mLock) { + final WallpaperData data = mWallpaperMap.get(mCurrentUserId); + data.connection.forEachDisplayConnector( + displayConnector -> { + if (displayConnector.mEngine != null) { + try { + displayConnector.mEngine.dispatchWallpaperCommand( + WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1, extras); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + }); + } + } + @Override public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) { checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW); diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index e4dc8c2bf797..f02107287123 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -96,6 +96,8 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; +import com.android.server.apphibernation.AppHibernationManagerInternal; +import com.android.server.apphibernation.AppHibernationService; import java.util.ArrayList; import java.util.LinkedList; @@ -163,6 +165,9 @@ class ActivityMetricsLogger { */ private final LaunchObserverRegistryImpl mLaunchObserver; @VisibleForTesting static final int LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE = 512; + private final ArrayMap<String, Boolean> mLastHibernationStates = new ArrayMap<>(); + private AppHibernationManagerInternal mAppHibernationManagerInternal; + private boolean mIsAppHibernationEnabled; /** * The information created when an intent is incoming but we do not yet know whether it will be @@ -789,6 +794,27 @@ class ActivityMetricsLogger { } } + @Nullable + private AppHibernationManagerInternal getAppHibernationManagerInternal() { + if (mAppHibernationManagerInternal == null) { + mIsAppHibernationEnabled = AppHibernationService.isAppHibernationEnabled(); + mAppHibernationManagerInternal = + LocalServices.getService(AppHibernationManagerInternal.class); + } + return mAppHibernationManagerInternal; + } + + /** + * Notifies the tracker before the package is unstopped because of launching activity. + * @param packageName The package to be unstopped. + */ + void notifyBeforePackageUnstopped(@NonNull String packageName) { + final AppHibernationManagerInternal ahmInternal = getAppHibernationManagerInternal(); + if (ahmInternal != null && mIsAppHibernationEnabled) { + mLastHibernationStates.put(packageName, ahmInternal.isHibernatingGlobally(packageName)); + } + } + /** * Notifies the tracker that we called immediately before we call bindApplication on the client. * @@ -823,6 +849,8 @@ class ActivityMetricsLogger { } stopLaunchTrace(info); + final Boolean isHibernating = + mLastHibernationStates.remove(info.mLastLaunchedActivity.packageName); if (abort) { mSupervisor.stopWaitingForActivityVisible(info.mLastLaunchedActivity); launchObserverNotifyActivityLaunchCancelled(info); @@ -830,7 +858,7 @@ class ActivityMetricsLogger { if (info.isInterestingToLoggerAndObserver()) { launchObserverNotifyActivityLaunchFinished(info, timestampNs); } - logAppTransitionFinished(info); + logAppTransitionFinished(info, isHibernating != null ? isHibernating : false); } info.mPendingDrawActivities.clear(); mTransitionInfoList.remove(info); @@ -859,7 +887,7 @@ class ActivityMetricsLogger { } } - private void logAppTransitionFinished(@NonNull TransitionInfo info) { + private void logAppTransitionFinished(@NonNull TransitionInfo info, boolean isHibernating) { if (DEBUG_METRICS) Slog.i(TAG, "logging finished transition " + info); // Take a snapshot of the transition info before sending it to the handler for logging. @@ -868,7 +896,7 @@ class ActivityMetricsLogger { if (info.isInterestingToLoggerAndObserver()) { BackgroundThread.getHandler().post(() -> logAppTransition( info.mCurrentTransitionDeviceUptime, info.mCurrentTransitionDelayMs, - infoSnapshot)); + infoSnapshot, isHibernating)); } BackgroundThread.getHandler().post(() -> logAppDisplayed(infoSnapshot)); if (info.mPendingFullyDrawn != null) { @@ -880,7 +908,7 @@ class ActivityMetricsLogger { // This gets called on a background thread without holding the activity manager lock. private void logAppTransition(int currentTransitionDeviceUptime, int currentTransitionDelayMs, - TransitionInfoSnapshot info) { + TransitionInfoSnapshot info, boolean isHibernating) { final LogMaker builder = new LogMaker(APP_TRANSITION); builder.setPackageName(info.packageName); builder.setType(info.type); @@ -933,7 +961,8 @@ class ActivityMetricsLogger { packageOptimizationInfo.getCompilationReason(), packageOptimizationInfo.getCompilationFilter(), info.sourceType, - info.sourceEventDelayMs); + info.sourceEventDelayMs, + isHibernating); if (DEBUG_METRICS) { Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)", diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index f5cfc9d86cf4..aa9727a28985 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -189,6 +189,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; +import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; @@ -215,14 +216,8 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; -import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; -import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_WALLPAPER; -import static com.android.server.wm.WindowManagerService.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; -import static com.android.server.wm.WindowManagerService.letterboxBackgroundTypeToString; import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY; import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM; @@ -268,7 +263,6 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; @@ -344,7 +338,6 @@ import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.Task.ActivityState; import com.android.server.wm.WindowManagerService.H; -import com.android.server.wm.WindowManagerService.LetterboxBackgroundType; import com.android.server.wm.utils.InsetUtils; import com.google.android.collect.Sets; @@ -587,7 +580,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A AnimatingActivityRegistry mAnimatingActivityRegistry; - private Task mLastParent; + // Set to the previous Task parent of the ActivityRecord when it is reparented to a new Task + // due to picture-in-picture. This gets cleared whenever this activity or the Task + // it references to gets removed. This should also be cleared when we move out of pip. + private Task mLastParentBeforePip; boolean firstWindowDrawn; /** Whether the visible window(s) of this activity is drawn. */ @@ -642,7 +638,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private boolean mWillCloseOrEnterPip; - private Letterbox mLetterbox; + @VisibleForTesting + final LetterboxUiController mLetterboxUiController; /** * The scale to fit at least one side of the activity to its parent. If the activity uses @@ -671,8 +668,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Nullable private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio; - private boolean mShowWallpaperForLetterboxBackground; - // activity is not displayed? // TODO: rename to mNoDisplay @VisibleForTesting @@ -1096,60 +1091,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges)); } } - - dumpLetterboxInfo(pw, prefix); - } - - private void dumpLetterboxInfo(PrintWriter pw, String prefix) { - final WindowState mainWin = findMainWindow(); - if (mainWin == null) { - return; + if (mLastParentBeforePip != null) { + pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId); } - boolean areBoundsLetterboxed = mainWin.isLetterboxedAppWindow(); - pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed); - if (!areBoundsLetterboxed) { - return; - } - - pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin)); - pw.println(prefix + " letterboxAspectRatio=" + computeAspectRatio(getBounds())); - - boolean isLetterboxUiShown = isLetterboxed(mainWin); - pw.println(prefix + "isLetterboxUiShown=" + isLetterboxUiShown); - - if (!isLetterboxUiShown) { - return; - } - pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString( - getLetterboxBackgroundColor().toArgb())); - pw.println(prefix + " letterboxBackgroundType=" - + letterboxBackgroundTypeToString(mWmService.getLetterboxBackgroundType())); - if (mWmService.getLetterboxBackgroundType() == LETTERBOX_BACKGROUND_WALLPAPER) { - pw.println(prefix + " isLetterboxWallpaperBlurSupported=" - + isLetterboxWallpaperBlurSupported()); - pw.println(prefix + " letterboxBackgroundWallpaperDarkScrimAlpha=" - + getLetterboxWallpaperDarkScrimAlpha()); - pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius=" - + getLetterboxWallpaperBlurRadius()); - } - } - - /** - * Returns a string representing the reason for letterboxing. This method assumes the activity - * is letterboxed. - */ - private String getLetterboxReasonString(WindowState mainWin) { - if (inSizeCompatMode()) { - return "SIZE_COMPAT_MODE"; - } - if (isLetterboxedForFixedOrientationAndAspectRatio()) { - return "FIXED_ORIENTATION"; - } - if (mainWin.isLetterboxedForDisplayCutout()) { - return "DISPLAY_CUTOUT"; - } - return "UNKNOWN_REASON"; + mLetterboxUiController.dump(pw, prefix); } void setAppTimeTracker(AppTimeTracker att) { @@ -1349,7 +1295,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (getDisplayContent() != null) { getDisplayContent().mClosingApps.remove(this); } - } else if (mLastParent != null && mLastParent.getRootTask() != null) { + } else if (oldTask != null && oldTask.getRootTask() != null) { task.getRootTask().mExitingActivities.remove(this); } final Task rootTask = getRootTask(); @@ -1362,7 +1308,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ? rootTask.getAnimatingActivityRegistry() : null; - mLastParent = task; + if (task == mLastParentBeforePip) { + // Activity's reparented back from pip, clear the links once established + clearLastParentBeforePip(); + } updateColorTransform(); @@ -1381,6 +1330,26 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + /** + * Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure + * {@link #getTask()} is set before this is called. + */ + void setLastParentBeforePip() { + mLastParentBeforePip = getTask(); + mLastParentBeforePip.mChildPipActivity = this; + } + + private void clearLastParentBeforePip() { + if (mLastParentBeforePip != null) { + mLastParentBeforePip.mChildPipActivity = null; + mLastParentBeforePip = null; + } + } + + @Nullable Task getLastParentBeforePip() { + return mLastParentBeforePip; + } + private void updateColorTransform() { if (mSurfaceControl != null && mLastAppSaturationInfo != null) { getPendingTransaction().setColorTransform(mSurfaceControl, @@ -1415,183 +1384,29 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - if (mLetterbox != null) { - mLetterbox.onMovedToDisplay(mDisplayContent.getDisplayId()); - } + mLetterboxUiController.onMovedToDisplay(mDisplayContent.getDisplayId()); } - // TODO(b/183754168): Move letterbox UI logic into a separate class. void layoutLetterbox(WindowState winHint) { - final WindowState w = findMainWindow(); - if (w == null || winHint != null && w != winHint) { - return; - } - final boolean surfaceReady = w.isDrawn() // Regular case - || w.isDragResizeChanged(); // Waiting for relayoutWindow to call preserveSurface. - final boolean needsLetterbox = surfaceReady && isLetterboxed(w); - updateRoundedCorners(w); - updateWallpaperForLetterbox(w); - if (needsLetterbox) { - if (mLetterbox == null) { - mLetterbox = new Letterbox(() -> makeChildSurface(null), - mWmService.mTransactionFactory, - mWmService::isLetterboxActivityCornersRounded, - this::getLetterboxBackgroundColor, - this::hasWallpaperBackgroudForLetterbox, - this::getLetterboxWallpaperBlurRadius, - this::getLetterboxWallpaperDarkScrimAlpha); - mLetterbox.attachInput(w); - } - getPosition(mTmpPoint); - // Get the bounds of the "space-to-fill". The transformed bounds have the highest - // priority because the activity is launched in a rotated environment. In multi-window - // mode, the task-level represents this. In fullscreen-mode, the task container does - // (since the orientation letterbox is also applied to the task). - final Rect transformedBounds = getFixedRotationTransformDisplayBounds(); - final Rect spaceToFill = transformedBounds != null - ? transformedBounds - : inMultiWindowMode() - ? getRootTask().getBounds() - : getRootTask().getParent().getBounds(); - mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint); - } else if (mLetterbox != null) { - mLetterbox.hide(); - } - } - - private Color getLetterboxBackgroundColor() { - final WindowState w = findMainWindow(); - if (w == null || w.isLetterboxedForDisplayCutout()) { - return Color.valueOf(Color.BLACK); - } - @LetterboxBackgroundType int letterboxBackgroundType = - mWmService.getLetterboxBackgroundType(); - switch (letterboxBackgroundType) { - case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: - if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) { - return Color.valueOf(taskDescription.getBackgroundColorFloating()); - } - break; - case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: - if (taskDescription != null && taskDescription.getBackgroundColor() != 0) { - return Color.valueOf(taskDescription.getBackgroundColor()); - } - break; - case LETTERBOX_BACKGROUND_WALLPAPER: - if (hasWallpaperBackgroudForLetterbox()) { - // Color is used for translucent scrim that dims wallpaper. - return Color.valueOf(Color.BLACK); - } - Slog.w(TAG, "Wallpaper option is selected for letterbox background but " - + "blur is not supported by a device or not supported in the current " - + "window configuration or both alpha scrim and blur radius aren't " - + "provided so using solid color background"); - break; - case LETTERBOX_BACKGROUND_SOLID_COLOR: - return mWmService.getLetterboxBackgroundColor(); - default: - throw new AssertionError( - "Unexpected letterbox background type: " + letterboxBackgroundType); - } - // If picked option configured incorrectly or not supported then default to a solid color - // background. - return mWmService.getLetterboxBackgroundColor(); - } - - /** - * @return {@code true} when the main window is letterboxed, this activity isn't transparent - * and doesn't show a wallpaper. - */ - @VisibleForTesting - boolean isLetterboxed(WindowState mainWindow) { - return mainWindow.isLetterboxedAppWindow() && fillsParent() - // Check for FLAG_SHOW_WALLPAPER explicitly instead of using - // WindowContainer#showWallpaper because the later will return true when this - // activity is using blurred wallpaper for letterbox backgroud. - && (mainWindow.mAttrs.flags & FLAG_SHOW_WALLPAPER) == 0; - } - - private void updateRoundedCorners(WindowState mainWindow) { - int cornersRadius = - // Don't round corners if letterboxed only for display cutout. - isLetterboxed(mainWindow) && !mainWindow.isLetterboxedForDisplayCutout() - ? Math.max(0, mWmService.getLetterboxActivityCornersRadius()) : 0; - setCornersRadius(mainWindow, cornersRadius); - } - - private void setCornersRadius(WindowState mainWindow, int cornersRadius) { - final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface(); - if (windowSurface != null && windowSurface.isValid()) { - Transaction transaction = getSyncTransaction(); - transaction.setCornerRadius(windowSurface, cornersRadius); - } + mLetterboxUiController.layoutLetterbox(winHint); } boolean hasWallpaperBackgroudForLetterbox() { - return mShowWallpaperForLetterboxBackground; - } - - private void updateWallpaperForLetterbox(WindowState mainWindow) { - @LetterboxBackgroundType int letterboxBackgroundType = - mWmService.getLetterboxBackgroundType(); - boolean wallpaperShouldBeShown = - letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER - && isLetterboxed(mainWindow) - // Don't use wallpaper as a background if letterboxed for display cutout. - && !mainWindow.isLetterboxedForDisplayCutout() - // Check that dark scrim alpha or blur radius are provided - && (getLetterboxWallpaperBlurRadius() > 0 - || getLetterboxWallpaperDarkScrimAlpha() > 0) - // Check that blur is supported by a device if blur radius is provided. - && (getLetterboxWallpaperBlurRadius() <= 0 - || isLetterboxWallpaperBlurSupported()); - if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) { - mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown; - requestUpdateWallpaperIfNeeded(); - } - } - - private int getLetterboxWallpaperBlurRadius() { - int blurRadius = mWmService.getLetterboxBackgroundWallpaperBlurRadius(); - return blurRadius < 0 ? 0 : blurRadius; - } - - private float getLetterboxWallpaperDarkScrimAlpha() { - float alpha = mWmService.getLetterboxBackgroundWallpaperDarkScrimAlpha(); - // No scrim by default. - return (alpha < 0 || alpha >= 1) ? 0.0f : alpha; - } - - private boolean isLetterboxWallpaperBlurSupported() { - return mWmService.mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled(); + return mLetterboxUiController.hasWallpaperBackgroudForLetterbox(); } void updateLetterboxSurface(WindowState winHint) { - final WindowState w = findMainWindow(); - if (w != winHint && winHint != null && w != null) { - return; - } - layoutLetterbox(winHint); - if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) { - mLetterbox.applySurfaceChanges(getSyncTransaction()); - } + mLetterboxUiController.updateLetterboxSurface(winHint); } + /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ Rect getLetterboxInsets() { - if (mLetterbox != null) { - return mLetterbox.getInsets(); - } else { - return new Rect(); - } + return mLetterboxUiController.getLetterboxInsets(); } /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */ void getLetterboxInnerBounds(Rect outBounds) { - if (mLetterbox != null) { - outBounds.set(mLetterbox.getInnerFrame()); - } else { - outBounds.setEmpty(); - } + mLetterboxUiController.getLetterboxInnerBounds(outBounds); } /** @@ -1599,7 +1414,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * when the current activity is displayed. */ boolean isFullyTransparentBarAllowed(Rect rect) { - return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect); + return mLetterboxUiController.isFullyTransparentBarAllowed(rect); } /** @@ -1607,7 +1422,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * the given {@code rect}. */ boolean isLetterboxOverlappingWith(Rect rect) { - return mLetterbox != null && mLetterbox.isOverlappingWith(rect); + return mLetterboxUiController.isLetterboxOverlappingWith(rect); } static class Token extends IApplicationToken.Stub { @@ -1856,6 +1671,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mPersistentState = persistentState; taskDescription = _taskDescription; + + mLetterboxUiController = new LetterboxUiController(mWmService, this); + if (_createTime > 0) { createTime = _createTime; } @@ -2140,7 +1958,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s", this, startingData); - WindowManagerPolicy.StartingSurface surface = null; try { surface = startingData.createStartingSurface(ActivityRecord.this); @@ -3434,6 +3251,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ void cleanUp(boolean cleanServices, boolean setState) { task.cleanUpActivityReferences(this); + clearLastParentBeforePip(); deferRelaunchUntilPaused = false; frozenBeforeDestroy = false; @@ -3687,10 +3505,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A dc.setFocusedApp(null); mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); } - if (mLetterbox != null) { - mLetterbox.destroy(); - mLetterbox = null; - } + + mLetterboxUiController.destroy(); if (!delayed) { updateReportedVisibilityLocked(); @@ -7028,11 +6844,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // and back which can cause visible issues (see b/184078928). final int parentWindowingMode = newParentConfiguration.windowConfiguration.getWindowingMode(); + final boolean isFixedOrientationLetterboxAllowed = + isSplitScreenWindowingMode(parentWindowingMode) + || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW + || parentWindowingMode == WINDOWING_MODE_FULLSCREEN; // TODO(b/181207944): Consider removing the if condition and always run // resolveFixedOrientationConfiguration() since this should be applied for all cases. - if (isSplitScreenWindowingMode(parentWindowingMode) - || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW - || parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { + if (isFixedOrientationLetterboxAllowed) { resolveFixedOrientationConfiguration(newParentConfiguration); } @@ -7053,12 +6871,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolveFullscreenConfiguration(newParentConfiguration); } + if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null + // In fullscreen, can be letterboxed for aspect ratio. + || !inMultiWindowMode()) { + updateResolvedBoundsHorizontalPosition(newParentConfiguration); + } + if (mVisibleRequested) { updateCompatDisplayInsets(); } - // TODO(b/175212232): Consolidate position logic from each "resolve" method above here. - // Assign configuration sequence number into hierarchy because there is a different way than // ensureActivityConfiguration() in this class that uses configuration in WindowState during // layout traversals. @@ -7089,6 +6911,48 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + + /** + * Adjusts horizontal position of resolved bounds if they doesn't fill the parent using gravity + * requested in the config or via an ADB command. For more context see {@link + * WindowManagerService#getLetterboxHorizontalPositionMultiplier}. + */ + private void updateResolvedBoundsHorizontalPosition(Configuration newParentConfiguration) { + final Configuration resolvedConfig = getResolvedOverrideConfiguration(); + final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); + final Rect screenResolvedBounds = + mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds; + final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds(); + final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); + if (resolvedBounds.isEmpty() || parentBounds.width() == screenResolvedBounds.width()) { + return; + } + + int offsetX = 0; + if (screenResolvedBounds.width() >= parentAppBounds.width()) { + // If resolved bounds overlap with insets, center within app bounds. + offsetX = getHorizontalCenterOffset( + parentAppBounds.width(), screenResolvedBounds.width()); + } else { + float positionMultiplier = + mWmService.mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(); + positionMultiplier = + (positionMultiplier < 0.0f || positionMultiplier > 1.0f) + // Default to central position if invalid value is provided. + ? 0.5f : positionMultiplier; + offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width()) + * positionMultiplier); + } + + if (mSizeCompatBounds != null) { + mSizeCompatBounds.offset(offsetX, 0 /* offsetY */); + final int dx = mSizeCompatBounds.left - resolvedBounds.left; + offsetBounds(resolvedConfig, dx, 0 /* offsetY */); + } else { + offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */); + } + } + /** * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed * orientation then aspect ratio restrictions are also already respected. @@ -7144,7 +7008,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Override from config_fixedOrientationLetterboxAspectRatio or via ADB with // set-fixed-orientation-letterbox-aspect-ratio. final float letterboxAspectRatioOverride = - mWmService.getFixedOrientationLetterboxAspectRatio(); + mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(); aspect = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO ? letterboxAspectRatioOverride : aspect; @@ -7166,7 +7030,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + height); } else { final int width = (int) Math.rint(parentHeight / aspect); - final int left = parentBounds.centerX() - width / 2; + final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds(); + final int left = width <= parentAppBounds.width() + // Avoid overlapping with the horizontal decor area when possible. + ? parentAppBounds.left : parentBounds.centerX() - width / 2; resolvedBounds.set(left, parentBounds.top, left + width, parentBounds.bottom); } @@ -7218,12 +7085,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, getFixedRotationTransformDisplayInfo()); } - if (needToBeCentered) { - // Offset to center relative to parent's app bounds. - final int offsetX = getHorizontalCenterOffset( - parentAppBounds.width(), resolvedBounds.width()); - offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */); - } } /** @@ -7321,8 +7182,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds(); - // Calculates the scale and offset to horizontal center the size compatibility bounds into - // the region which is available to application. + // Calculates the scale the size compatibility bounds into the region which is available + // to application. final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); final int viewportW = containerAppBounds.width(); @@ -7330,8 +7191,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH); - final int screenTopInset = containerAppBounds.top - containerBounds.top; - final boolean topNotAligned = screenTopInset != resolvedAppBounds.top - resolvedBounds.top; + final int containerTopInset = containerAppBounds.top - containerBounds.top; + final boolean topNotAligned = + containerTopInset != resolvedAppBounds.top - resolvedBounds.top; if (mSizeCompatScale != 1f || topNotAligned) { if (mSizeCompatBounds == null) { mSizeCompatBounds = new Rect(); @@ -7340,18 +7202,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mSizeCompatBounds.offsetTo(0, 0); mSizeCompatBounds.scale(mSizeCompatScale); // The insets are included in height, e.g. the area of real cutout shouldn't be scaled. - mSizeCompatBounds.bottom += screenTopInset; + mSizeCompatBounds.bottom += containerTopInset; } else { mSizeCompatBounds = null; } - // Center horizontally in parent (app bounds) and align to top of parent (bounds) - // - this is a UX choice. - final int offsetX = getHorizontalCenterOffset( - (int) viewportW, (int) (contentW * mSizeCompatScale)); + // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor + // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition. // Above coordinates are in "@" space, now place "*" and "#" to screen space. - final int screenPosX = (fillContainer - ? containerBounds.left : containerAppBounds.left) + offsetX; + final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left; final int screenPosY = containerBounds.top; if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { @@ -7667,7 +7526,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * Returns the aspect ratio of the given {@code rect}. */ - private static float computeAspectRatio(Rect rect) { + static float computeAspectRatio(Rect rect) { final int width = rect.width(); final int height = rect.height(); if (width == 0 || height == 0) { diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java new file mode 100644 index 000000000000..eb7087cbc722 --- /dev/null +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.annotation.IntDef; +import android.content.Context; +import android.graphics.Color; + +import com.android.internal.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Reads letterbox configs from resources and controls their overrides at runtime. */ +final class LetterboxConfiguration { + + /** + * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with + * set-fixed-orientation-letterbox-aspect-ratio or via {@link + * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored + * if it is <= this value. + */ + static final float MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO = 1.0f; + + /** Enum for Letterbox background type. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND, + LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING, LETTERBOX_BACKGROUND_WALLPAPER}) + @interface LetterboxBackgroundType {}; + /** Solid background using color specified in R.color.config_letterboxBackgroundColor. */ + static final int LETTERBOX_BACKGROUND_SOLID_COLOR = 0; + + /** Color specified in R.attr.colorBackground for the letterboxed application. */ + static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND = 1; + + /** Color specified in R.attr.colorBackgroundFloating for the letterboxed application. */ + static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING = 2; + + /** Using wallpaper as a background which can be blurred or dimmed with dark scrim. */ + static final int LETTERBOX_BACKGROUND_WALLPAPER = 3; + + final Context mContext; + + // Aspect ratio of letterbox for fixed orientation, values <= + // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored. + private float mFixedOrientationLetterboxAspectRatio; + + // Corners radius for activities presented in the letterbox mode, values < 0 will be ignored. + private int mLetterboxActivityCornersRadius; + + // Color for {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} letterbox background type. + private Color mLetterboxBackgroundColor; + + @LetterboxBackgroundType + private int mLetterboxBackgroundType; + + // Blur radius for LETTERBOX_BACKGROUND_WALLPAPER option in mLetterboxBackgroundType. + // Values <= 0 are ignored and 0 is used instead. + private int mLetterboxBackgroundWallpaperBlurRadius; + + // Alpha of a black scrim shown over wallpaper letterbox background when + // LETTERBOX_BACKGROUND_WALLPAPER option is selected for mLetterboxBackgroundType. + // Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead. + private float mLetterboxBackgroundWallpaperDarkScrimAlpha; + + // Horizontal position of a center of the letterboxed app window. 0 corresponds to the left + // side of the screen and 1.0 to the right side. + private float mLetterboxHorizontalPositionMultiplier; + + LetterboxConfiguration(Context context) { + mContext = context; + mFixedOrientationLetterboxAspectRatio = context.getResources().getFloat( + R.dimen.config_fixedOrientationLetterboxAspectRatio); + mLetterboxActivityCornersRadius = context.getResources().getInteger( + R.integer.config_letterboxActivityCornersRadius); + mLetterboxBackgroundColor = Color.valueOf(context.getResources().getColor( + R.color.config_letterboxBackgroundColor)); + mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(context); + mLetterboxBackgroundWallpaperBlurRadius = context.getResources().getDimensionPixelSize( + R.dimen.config_letterboxBackgroundWallpaperBlurRadius); + mLetterboxBackgroundWallpaperDarkScrimAlpha = context.getResources().getFloat( + R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha); + mLetterboxHorizontalPositionMultiplier = context.getResources().getFloat( + R.dimen.config_letterboxHorizontalPositionMultiplier); + } + + /** + * Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link + * #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link + * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and + * the framework implementation will be used to determine the aspect ratio. + */ + 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() { + return mFixedOrientationLetterboxAspectRatio; + } + + /** + * 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() { + return getLetterboxActivityCornersRadius() > 0; + } + + /** + * Gets corners raidus for activities presented in the letterbox mode. + */ + int getLetterboxActivityCornersRadius() { + return mLetterboxActivityCornersRadius; + } + + /** + * Gets color of letterbox background which is used when {@link + * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as + * fallback for other backfround types. + */ + Color getLetterboxBackgroundColor() { + 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. + */ + @LetterboxBackgroundType + int getLetterboxBackgroundType() { + 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) { + switch (backgroundType) { + case LETTERBOX_BACKGROUND_SOLID_COLOR: + return "LETTERBOX_BACKGROUND_SOLID_COLOR"; + case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: + return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND"; + case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: + return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING"; + case LETTERBOX_BACKGROUND_WALLPAPER: + return "LETTERBOX_BACKGROUND_WALLPAPER"; + default: + return "unknown=" + backgroundType; + } + } + + @LetterboxBackgroundType + private static int readLetterboxBackgroundTypeFromConfig(Context context) { + int backgroundType = context.getResources().getInteger( + com.android.internal.R.integer.config_letterboxBackgroundType); + return backgroundType == LETTERBOX_BACKGROUND_SOLID_COLOR + || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND + || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING + || backgroundType == LETTERBOX_BACKGROUND_WALLPAPER + ? backgroundType : LETTERBOX_BACKGROUND_SOLID_COLOR; + } + + /** + * 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() { + return mLetterboxBackgroundWallpaperDarkScrimAlpha; + } + + /** + * 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}. + */ + int getLetterboxBackgroundWallpaperBlurRadius() { + return mLetterboxBackgroundWallpaperBlurRadius; + } + + /* + * Gets horizontal position of a center of the letterboxed app window specified + * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} + * or via an ADB command. 0 corresponds to the left side of the screen and 1 to the + * right side. + * + * <p>This value can be outside of [0, 1] range so clients need to check and default to the + * central position (0.5). + */ + float getLetterboxHorizontalPositionMultiplier() { + return mLetterboxHorizontalPositionMultiplier; + } + + /** + * Overrides horizontal position of a center of the letterboxed app window. If given value < 0 + * or > 1, then it and a value of {@link + * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and + * central position (0.5) is used. + */ + 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/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java new file mode 100644 index 000000000000..130f68097331 --- /dev/null +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; + +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +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 static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString; + +import android.annotation.Nullable; +import android.app.ActivityManager.TaskDescription; +import android.graphics.Color; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.Slog; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; +import android.view.WindowManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; + +import java.io.PrintWriter; + +/** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */ +// TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in +// SizeCompatTests and LetterboxTests but not all. +// TODO(b/185264020): Consider making LetterboxUiController applicable to any level of the +// hierarchy in addition to ActivityRecord (Task, DisplayArea, ...). +final class LetterboxUiController { + + private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; + + private final Point mTmpPoint = new Point(); + + private final LetterboxConfiguration mLetterboxConfiguration; + private final ActivityRecord mActivityRecord; + + private boolean mShowWallpaperForLetterboxBackground; + + @Nullable + private Letterbox mLetterbox; + + LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) { + mLetterboxConfiguration = wmService.mLetterboxConfiguration; + // Given activityRecord may not be fully constructed since LetterboxUiController + // is created in its constructor. It shouldn't be used in this constructor but it's safe + // to use it after since controller is only used in ActivityRecord. + mActivityRecord = activityRecord; + } + + /** Cleans up {@link Letterbox} if it exists.*/ + void destroy() { + if (mLetterbox != null) { + mLetterbox.destroy(); + mLetterbox = null; + } + } + + void onMovedToDisplay(int displayId) { + if (mLetterbox != null) { + mLetterbox.onMovedToDisplay(displayId); + } + } + + boolean hasWallpaperBackgroudForLetterbox() { + return mShowWallpaperForLetterboxBackground; + } + + /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ + Rect getLetterboxInsets() { + if (mLetterbox != null) { + return mLetterbox.getInsets(); + } else { + return new Rect(); + } + } + + /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */ + void getLetterboxInnerBounds(Rect outBounds) { + if (mLetterbox != null) { + outBounds.set(mLetterbox.getInnerFrame()); + } else { + outBounds.setEmpty(); + } + } + + /** + * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent + * when the current activity is displayed. + */ + boolean isFullyTransparentBarAllowed(Rect rect) { + return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect); + } + + /** + * @return {@code true} if there is a letterbox and any part of that letterbox overlaps with + * the given {@code rect}. + */ + boolean isLetterboxOverlappingWith(Rect rect) { + return mLetterbox != null && mLetterbox.isOverlappingWith(rect); + } + + void updateLetterboxSurface(WindowState winHint) { + final WindowState w = mActivityRecord.findMainWindow(); + if (w != winHint && winHint != null && w != null) { + return; + } + layoutLetterbox(winHint); + if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) { + mLetterbox.applySurfaceChanges(mActivityRecord.getSyncTransaction()); + } + } + + void layoutLetterbox(WindowState winHint) { + final WindowState w = mActivityRecord.findMainWindow(); + if (w == null || winHint != null && w != winHint) { + return; + } + final boolean surfaceReady = w.isDrawn() // Regular case + || w.isDragResizeChanged(); // Waiting for relayoutWindow to call preserveSurface. + final boolean needsLetterbox = surfaceReady && isLetterboxed(w); + updateRoundedCorners(w); + updateWallpaperForLetterbox(w); + if (needsLetterbox) { + if (mLetterbox == null) { + mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null), + mActivityRecord.mWmService.mTransactionFactory, + mLetterboxConfiguration::isLetterboxActivityCornersRounded, + this::getLetterboxBackgroundColor, + this::hasWallpaperBackgroudForLetterbox, + this::getLetterboxWallpaperBlurRadius, + this::getLetterboxWallpaperDarkScrimAlpha); + mLetterbox.attachInput(w); + } + mActivityRecord.getPosition(mTmpPoint); + // Get the bounds of the "space-to-fill". The transformed bounds have the highest + // priority because the activity is launched in a rotated environment. In multi-window + // mode, the task-level represents this. In fullscreen-mode, the task container does + // (since the orientation letterbox is also applied to the task). + final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds(); + final Rect spaceToFill = transformedBounds != null + ? transformedBounds + : mActivityRecord.inMultiWindowMode() + ? mActivityRecord.getRootTask().getBounds() + : mActivityRecord.getRootTask().getParent().getBounds(); + mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint); + } else if (mLetterbox != null) { + mLetterbox.hide(); + } + } + + /** + * @return {@code true} when the main window is letterboxed, this activity isn't transparent + * and doesn't show a wallpaper. + */ + @VisibleForTesting + boolean isLetterboxed(WindowState mainWindow) { + return mainWindow.isLetterboxedAppWindow() && mActivityRecord.fillsParent() + // Check for FLAG_SHOW_WALLPAPER explicitly instead of using + // WindowContainer#showWallpaper because the later will return true when this + // activity is using blurred wallpaper for letterbox backgroud. + && (mainWindow.mAttrs.flags & FLAG_SHOW_WALLPAPER) == 0; + } + + private Color getLetterboxBackgroundColor() { + final WindowState w = mActivityRecord.findMainWindow(); + if (w == null || w.isLetterboxedForDisplayCutout()) { + return Color.valueOf(Color.BLACK); + } + @LetterboxBackgroundType int letterboxBackgroundType = + mLetterboxConfiguration.getLetterboxBackgroundType(); + TaskDescription taskDescription = mActivityRecord.taskDescription; + switch (letterboxBackgroundType) { + case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: + if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) { + return Color.valueOf(taskDescription.getBackgroundColorFloating()); + } + break; + case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: + if (taskDescription != null && taskDescription.getBackgroundColor() != 0) { + return Color.valueOf(taskDescription.getBackgroundColor()); + } + break; + case LETTERBOX_BACKGROUND_WALLPAPER: + if (hasWallpaperBackgroudForLetterbox()) { + // Color is used for translucent scrim that dims wallpaper. + return Color.valueOf(Color.BLACK); + } + Slog.w(TAG, "Wallpaper option is selected for letterbox background but " + + "blur is not supported by a device or not supported in the current " + + "window configuration or both alpha scrim and blur radius aren't " + + "provided so using solid color background"); + break; + case LETTERBOX_BACKGROUND_SOLID_COLOR: + return mLetterboxConfiguration.getLetterboxBackgroundColor(); + default: + throw new AssertionError( + "Unexpected letterbox background type: " + letterboxBackgroundType); + } + // If picked option configured incorrectly or not supported then default to a solid color + // background. + return mLetterboxConfiguration.getLetterboxBackgroundColor(); + } + + private void updateRoundedCorners(WindowState mainWindow) { + int cornersRadius = + // Don't round corners if letterboxed only for display cutout. + isLetterboxed(mainWindow) + && !mainWindow.isLetterboxedForDisplayCutout() + ? Math.max(0, mLetterboxConfiguration.getLetterboxActivityCornersRadius()) + : 0; + setCornersRadius(mainWindow, cornersRadius); + } + + private void setCornersRadius(WindowState mainWindow, int cornersRadius) { + final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface(); + if (windowSurface != null && windowSurface.isValid()) { + Transaction transaction = mActivityRecord.getSyncTransaction(); + transaction.setCornerRadius(windowSurface, cornersRadius); + } + } + + private void updateWallpaperForLetterbox(WindowState mainWindow) { + @LetterboxBackgroundType int letterboxBackgroundType = + mLetterboxConfiguration.getLetterboxBackgroundType(); + boolean wallpaperShouldBeShown = + letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER + && isLetterboxed(mainWindow) + // Don't use wallpaper as a background if letterboxed for display cutout. + && !mainWindow.isLetterboxedForDisplayCutout() + // Check that dark scrim alpha or blur radius are provided + && (getLetterboxWallpaperBlurRadius() > 0 + || getLetterboxWallpaperDarkScrimAlpha() > 0) + // Check that blur is supported by a device if blur radius is provided. + && (getLetterboxWallpaperBlurRadius() <= 0 + || isLetterboxWallpaperBlurSupported()); + if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) { + mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown; + mActivityRecord.requestUpdateWallpaperIfNeeded(); + } + } + + private int getLetterboxWallpaperBlurRadius() { + int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius(); + return blurRadius < 0 ? 0 : blurRadius; + } + + private float getLetterboxWallpaperDarkScrimAlpha() { + float alpha = mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha(); + // No scrim by default. + return (alpha < 0 || alpha >= 1) ? 0.0f : alpha; + } + + private boolean isLetterboxWallpaperBlurSupported() { + return mLetterboxConfiguration.mContext.getSystemService(WindowManager.class) + .isCrossWindowBlurEnabled(); + } + + void dump(PrintWriter pw, String prefix) { + final WindowState mainWin = mActivityRecord.findMainWindow(); + if (mainWin == null) { + return; + } + + boolean areBoundsLetterboxed = mainWin.isLetterboxedAppWindow(); + pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed); + if (!areBoundsLetterboxed) { + return; + } + + pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin)); + pw.println(prefix + " letterboxAspectRatio=" + + mActivityRecord.computeAspectRatio(mActivityRecord.getBounds())); + + boolean isLetterboxUiShown = isLetterboxed(mainWin); + pw.println(prefix + "isLetterboxUiShown=" + isLetterboxUiShown); + + if (!isLetterboxUiShown) { + return; + } + pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString( + getLetterboxBackgroundColor().toArgb())); + pw.println(prefix + " letterboxBackgroundType=" + + letterboxBackgroundTypeToString( + mLetterboxConfiguration.getLetterboxBackgroundType())); + if (mLetterboxConfiguration.getLetterboxBackgroundType() + == LETTERBOX_BACKGROUND_WALLPAPER) { + pw.println(prefix + " isLetterboxWallpaperBlurSupported=" + + isLetterboxWallpaperBlurSupported()); + pw.println(prefix + " letterboxBackgroundWallpaperDarkScrimAlpha=" + + getLetterboxWallpaperDarkScrimAlpha()); + pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius=" + + getLetterboxWallpaperBlurRadius()); + } + pw.println(prefix + " letterboxHorizontalPositionMultiplier=" + + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier()); + } + + /** + * Returns a string representing the reason for letterboxing. This method assumes the activity + * is letterboxed. + */ + private String getLetterboxReasonString(WindowState mainWin) { + if (mActivityRecord.inSizeCompatMode()) { + return "SIZE_COMPAT_MODE"; + } + if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) { + return "FIXED_ORIENTATION"; + } + if (mainWin.isLetterboxedForDisplayCutout()) { + return "DISPLAY_CUTOUT"; + } + return "UNKNOWN_REASON"; + } + +} diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 6631a3e61e77..4bb48b0bdbe8 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1415,6 +1415,9 @@ class RecentTasks { return true; } + // The given task if always treated as in visible range if it is the origin of pinned task. + if (task.mChildPipActivity != null) return true; + if (mMaxNumVisibleTasks >= 0) { // Always keep up to the max number of recent tasks, but return false afterwards return numVisibleTasks <= mMaxNumVisibleTasks; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 52551ec54b32..b9fc8b1222ee 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2124,6 +2124,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> .setDeferTaskAppear(true) .setHasBeenVisible(true) .build(); + // Establish bi-directional link between the original and pinned task. + r.setLastParentBeforePip(); // It's possible the task entering PIP is in freeform, so save the last // non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore // to its previous freeform bounds. diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 95a4f69edd57..fb66c0493025 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -36,9 +36,11 @@ import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER; import android.animation.ArgbEvaluator; import android.content.Context; import android.graphics.Color; +import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; +import android.hardware.HardwareBuffer; import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -210,9 +212,9 @@ class ScreenRotationAnimation { String name = "RotationLayer"; mScreenshotLayer = displayContent.makeOverlay() .setName(name) - .setBufferSize(mWidth, mHeight) .setSecure(isSecure) .setCallsite("ScreenRotationAnimation") + .setBLASTLayer() .build(); // This is the way to tell the input system to exclude this surface from occlusion // detection since we don't have a window for it. We do this because this window is @@ -225,32 +227,29 @@ class ScreenRotationAnimation { .setCallsite("ScreenRotationAnimation") .build(); - final Surface surface = mService.mSurfaceFactory.get(); - // In case display bounds change, screenshot buffer and surface may mismatch so - // set a scaling mode. - surface.copyFrom(mScreenshotLayer); - surface.setScalingMode(Surface.SCALING_MODE_SCALE_TO_WINDOW); - + HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ScreenRotationAnimation#getMedianBorderLuma"); - mStartLuma = RotationAnimationUtils.getMedianBorderLuma( - screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace()); + mStartLuma = RotationAnimationUtils.getMedianBorderLuma(hardwareBuffer, + screenshotBuffer.getColorSpace()); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - try { - surface.attachAndQueueBufferWithColorSpace(screenshotBuffer.getHardwareBuffer(), - screenshotBuffer.getColorSpace()); - } catch (RuntimeException e) { - Slog.w(TAG, "Failed to attach screenshot - " + e.getMessage()); - } + + GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer( + screenshotBuffer.getHardwareBuffer()); + // Scale the layer to the display size. + float dsdx = (float) mWidth / hardwareBuffer.getWidth(); + float dsdy = (float) mHeight / hardwareBuffer.getHeight(); t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE); t.reparent(mBackColorSurface, displayContent.getSurfaceControl()); t.setLayer(mBackColorSurface, -1); t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); t.setAlpha(mBackColorSurface, 1); + t.setBuffer(mScreenshotLayer, buffer); + t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace()); + t.setMatrix(mScreenshotLayer, dsdx, 0, 0, dsdy); t.show(mScreenshotLayer); t.show(mBackColorSurface); - surface.destroy(); } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate freeze surface", e); diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index b82a30847c8d..c6cd5606770e 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -640,9 +640,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } } - void windowAddedLocked(String packageName) { - mPackageName = packageName; - mRelayoutTag = "relayoutWindow: " + mPackageName; + void windowAddedLocked() { + if (mPackageName == null) { + final WindowProcessController wpc = mService.mAtmService.mProcessMap.getProcess(mPid); + if (wpc != null) { + mPackageName = wpc.mInfo.packageName; + mRelayoutTag = "relayoutWindow: " + mPackageName; + } else { + Slog.e(TAG_WM, "Unknown process pid=" + mPid); + } + } if (mSurfaceSession == null) { if (DEBUG) { Slog.v(TAG_WM, "First window added to " + this + ", creating SurfaceSession"); diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 140ae3e7d756..603bfd153481 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -58,10 +58,12 @@ public class StartingSurfaceController { overrideConfig, displayId); } - final Task task = activity.getTask(); - if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, - activity.token, theme)) { - return new ShellStartingSurface(task); + synchronized (mService.mGlobalLock) { + final Task task = activity.getTask(); + if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow( + task, activity.token, theme)) { + return new ShellStartingSurface(task); + } } return null; } @@ -124,14 +126,13 @@ public class StartingSurfaceController { activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation( topFullscreenActivity, false /* checkOpening */); } + if (DEBUG_ENABLE_SHELL_DRAWER) { + mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, + activity.token, 0 /* launchTheme */); + return new ShellStartingSurface(task); + } } - if (!DEBUG_ENABLE_SHELL_DRAWER) { - return mService.mTaskSnapshotController - .createStartingSurface(activity, taskSnapshot); - } - mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token, - 0 /* launchTheme */); - return new ShellStartingSurface(task); + return mService.mTaskSnapshotController.createStartingSurface(activity, taskSnapshot); } @@ -144,8 +145,9 @@ public class StartingSurfaceController { @Override public void remove(boolean animate) { - mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, - animate); + synchronized (mService.mGlobalLock) { + mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, animate); + } } } } diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java index 6f434e05c800..e0a791e118bb 100644 --- a/services/core/java/com/android/server/wm/SurfaceFreezer.java +++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java @@ -22,6 +22,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATI import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.GraphicBuffer; import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.HardwareBuffer; @@ -153,29 +154,24 @@ class SurfaceFreezer { */ Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t, SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) { - Surface drawSurface = surfaceFactory.get(); // We can't use a delegating constructor since we need to // reference this::onAnimationFinished - HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); - final int width = hardwareBuffer.getWidth(); - final int height = hardwareBuffer.getHeight(); + GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer( + screenshotBuffer.getHardwareBuffer()); mSurfaceControl = mAnimatable.makeAnimationLeash() .setName("snapshot anim: " + mAnimatable.toString()) - .setBufferSize(width, height) .setFormat(PixelFormat.TRANSLUCENT) .setParent(parent) .setSecure(screenshotBuffer.containsSecureLayers()) .setCallsite("SurfaceFreezer.Snapshot") + .setBLASTLayer() .build(); ProtoLog.i(WM_SHOW_TRANSACTIONS, " THUMBNAIL %s: CREATE", mSurfaceControl); - // Transfer the thumbnail to the surface - drawSurface.copyFrom(mSurfaceControl); - drawSurface.attachAndQueueBufferWithColorSpace(hardwareBuffer, - screenshotBuffer.getColorSpace()); - drawSurface.release(); + t.setBuffer(mSurfaceControl, graphicBuffer); + t.setColorSpace(mSurfaceControl, screenshotBuffer.getColorSpace()); t.show(mSurfaceControl); // We parent the thumbnail to the container, and just place it on top of anything else diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 86904991616f..858d9f366a83 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -131,6 +131,7 @@ import static com.android.server.wm.TaskProto.BOUNDS; import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER; import static com.android.server.wm.TaskProto.DISPLAY_ID; import static com.android.server.wm.TaskProto.FILLS_PARENT; +import static com.android.server.wm.TaskProto.HAS_CHILD_PIP_ACTIVITY; import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS; import static com.android.server.wm.TaskProto.MIN_HEIGHT; import static com.android.server.wm.TaskProto.MIN_WIDTH; @@ -836,6 +837,14 @@ class Task extends WindowContainer<WindowContainer> { // The task will be removed when TaskOrganizer, which is managing the task, is destroyed. boolean mRemoveWithTaskOrganizer; + /** + * Reference to the pinned activity that is logically parented to this task, ie. + * the previous top activity within this task is put into pinned mode. + * This always gets cleared in pair with the ActivityRecord-to-Task link as seen in + * {@link ActivityRecord#clearLastParentBeforePip()}. + */ + ActivityRecord mChildPipActivity; + private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, @@ -1841,6 +1850,10 @@ class Task extends WindowContainer<WindowContainer> { /** Completely remove all activities associated with an existing task. */ void performClearTask(String reason) { + // The original task is to be removed, try remove also the pinned task. + if (mChildPipActivity != null && mChildPipActivity.getTask() != null) { + mTaskSupervisor.removeRootTask(mChildPipActivity.getTask()); + } // Broken down into to cases to avoid object create due to capturing mStack. if (getRootTask() == null) { forAllActivities((r) -> { @@ -4449,6 +4462,7 @@ class Task extends WindowContainer<WindowContainer> { } pw.print(prefix); pw.print("taskId=" + mTaskId); pw.println(" rootTaskId=" + getRootTaskId()); + pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null)); pw.print(prefix); pw.print("mHasBeenVisible="); pw.println(getHasBeenVisible()); pw.print(prefix); pw.print("mResizeMode="); pw.print(ActivityInfo.resizeModeToString(mResizeMode)); @@ -5328,7 +5342,6 @@ class Task extends WindowContainer<WindowContainer> { return; } final int currentMode = getWindowingMode(); - final int currentOverrideMode = getRequestedOverrideWindowingMode(); final Task topTask = getTopMostTask(); int windowingMode = preferredWindowingMode; @@ -5397,9 +5410,26 @@ class Task extends WindowContainer<WindowContainer> { mTaskSupervisor.mNoAnimActivities.add(topActivity); } super.setWindowingMode(windowingMode); - // setWindowingMode triggers an onConfigurationChanged cascade which can result in a - // different resolved windowing mode (usually when preferredWindowingMode is UNDEFINED). - windowingMode = getWindowingMode(); + + // Try reparent pinned activity back to its original task after onConfigurationChanged + // cascade finishes. This is done on Task level instead of + // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP, + // we set final windowing mode on the ActivityRecord first and then on its Task when + // the exit PiP transition finishes. Meanwhile, the exit transition is always + // performed on its original task, reparent immediately in ActivityRecord breaks it. + if (currentMode == WINDOWING_MODE_PINNED) { + if (topActivity != null && topActivity.getLastParentBeforePip() != null) { + // Do not reparent if the pinned task is in removal, indicated by the + // force hidden flag. + if (!isForceHidden()) { + final Task lastParentBeforePip = topActivity.getLastParentBeforePip(); + topActivity.reparent(lastParentBeforePip, + lastParentBeforePip.getChildCount() /* top */, + "movePinnedActivityToOriginalTask"); + lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask"); + } + } + } if (creating) { // Nothing else to do if we don't have a window container yet. E.g. call from ctor. @@ -6279,6 +6309,8 @@ class Task extends WindowContainer<WindowContainer> { // Launching this app's activity, make sure the app is no longer // considered stopped. try { + mTaskSupervisor.getActivityMetricsLogger() + .notifyBeforePackageUnstopped(next.packageName); mAtmService.getPackageManager().setPackageStoppedState( next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */ } catch (RemoteException e1) { @@ -7530,7 +7562,11 @@ class Task extends WindowContainer<WindowContainer> { final Task task = getBottomMostTask(); setWindowingMode(WINDOWING_MODE_UNDEFINED); - getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */); + // Task could have been removed from the hierarchy due to windowing mode change + // where its only child is reparented back to their original parent task. + if (isAttached()) { + getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */); + } mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this); }); @@ -7831,6 +7867,7 @@ class Task extends WindowContainer<WindowContainer> { proto.write(CREATED_BY_ORGANIZER, mCreatedByOrganizer); proto.write(AFFINITY, affinity); + proto.write(HAS_CHILD_PIP_ACTIVITY, mChildPipActivity != null); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index dff621c5871a..625cff340912 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -439,6 +439,13 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea(); } + // Re-route to default display if the home activity doesn't support multi-display + if (taskDisplayArea != null && activityRecord.isActivityTypeHome() + && !mSupervisor.mRootWindowContainer.canStartHomeOnDisplayArea(activityRecord.info, + taskDisplayArea, false /* allowInstrumenting */)) { + taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea(); + } + return (taskDisplayArea != null) ? taskDisplayArea : getFallbackDisplayAreaForActivity(activityRecord, request); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 5ffab483202f..a467d82b362e 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -646,8 +646,12 @@ class TaskSnapshotController { if (shouldDisableSnapshots()) { return; } + final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId); + if (displayContent == null) { + return; + } mTmpTasks.clear(); - mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> { + displayContent.forAllTasks(task -> { // Since RecentsAnimation will handle task snapshot while switching apps with the best // capture timing (e.g. IME window capture), No need additional task capture while task // is controlled by RecentsAnimation. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c80a38fdb838..d14a77382532 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -159,7 +159,6 @@ import android.content.res.Configuration; import android.content.res.TypedArray; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Point; @@ -977,52 +976,7 @@ public class WindowManagerService extends IWindowManager.Stub private boolean mAnimationsDisabled = false; boolean mPointerLocationEnabled = false; - /** - * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with - * set-fixed-orientation-letterbox-aspect-ratio or via {@link - * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored - * if it is <= this value. - */ - static final float MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO = 1.0f; - - /** Enum for Letterbox background type. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND, - LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING, LETTERBOX_BACKGROUND_WALLPAPER}) - @interface LetterboxBackgroundType {}; - /** Solid background using color specified in R.color.config_letterboxBackgroundColor. */ - static final int LETTERBOX_BACKGROUND_SOLID_COLOR = 0; - - /** Color specified in R.attr.colorBackground for the letterboxed application. */ - static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND = 1; - - /** Color specified in R.attr.colorBackgroundFloating for the letterboxed application. */ - static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING = 2; - - /** Using wallpaper as a background which can be blurred or dimmed with dark scrim. */ - static final int LETTERBOX_BACKGROUND_WALLPAPER = 3; - - // Aspect ratio of letterbox for fixed orientation, values <= - // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored. - private float mFixedOrientationLetterboxAspectRatio; - - // Corners radius for activities presented in the letterbox mode, values < 0 will be ignored. - private int mLetterboxActivityCornersRadius; - - // Color for {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} letterbox background type. - private Color mLetterboxBackgroundColor; - - @LetterboxBackgroundType - private int mLetterboxBackgroundType; - - // Blur radius for LETTERBOX_BACKGROUND_WALLPAPER option in mLetterboxBackgroundType. - // Values <= 0 are ignored and 0 is used instead. - private int mLetterboxBackgroundWallpaperBlurRadius; - - // Alpha of a black scrim shown over wallpaper letterbox background when - // LETTERBOX_BACKGROUND_WALLPAPER option is selected for mLetterboxBackgroundType. - // Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead. - private float mLetterboxBackgroundWallpaperDarkScrimAlpha; + final LetterboxConfiguration mLetterboxConfiguration; final InputManagerService mInputManager; final DisplayManagerInternal mDisplayManagerInternal; @@ -1254,17 +1208,7 @@ public class WindowManagerService extends IWindowManager.Stub mAssistantOnTopOfDream = context.getResources().getBoolean( com.android.internal.R.bool.config_assistantOnTopOfDream); - mFixedOrientationLetterboxAspectRatio = context.getResources().getFloat( - com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio); - mLetterboxActivityCornersRadius = context.getResources().getInteger( - com.android.internal.R.integer.config_letterboxActivityCornersRadius); - mLetterboxBackgroundColor = Color.valueOf(context.getResources().getColor( - com.android.internal.R.color.config_letterboxBackgroundColor)); - mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(context); - mLetterboxBackgroundWallpaperBlurRadius = context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius); - mLetterboxBackgroundWallpaperDarkScrimAlpha = context.getResources().getFloat( - com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha); + mLetterboxConfiguration = new LetterboxConfiguration(context); mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); @@ -3863,201 +3807,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - /** - * Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link - * #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link - * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and - * the framework implementation will be used to determine the aspect ratio. - */ - 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() { - return mFixedOrientationLetterboxAspectRatio; - } - - /** - * 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() { - return getLetterboxActivityCornersRadius() > 0; - } - - /** - * Gets corners raidus for activities presented in the letterbox mode. - */ - int getLetterboxActivityCornersRadius() { - return mLetterboxActivityCornersRadius; - } - - /** - * Gets color of letterbox background which is used when {@link - * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as - * fallback for other backfround types. - */ - Color getLetterboxBackgroundColor() { - 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. - */ - @LetterboxBackgroundType - int getLetterboxBackgroundType() { - 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); - } - - @LetterboxBackgroundType - private static int readLetterboxBackgroundTypeFromConfig(Context context) { - int backgroundType = context.getResources().getInteger( - com.android.internal.R.integer.config_letterboxBackgroundType); - return backgroundType == LETTERBOX_BACKGROUND_SOLID_COLOR - || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND - || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING - || backgroundType == LETTERBOX_BACKGROUND_WALLPAPER - ? backgroundType : LETTERBOX_BACKGROUND_SOLID_COLOR; - } - - /** Returns a string representing the given {@link LetterboxBackgroundType}. */ - static String letterboxBackgroundTypeToString( - @LetterboxBackgroundType int backgroundType) { - switch (backgroundType) { - case LETTERBOX_BACKGROUND_SOLID_COLOR: - return "LETTERBOX_BACKGROUND_SOLID_COLOR"; - case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: - return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND"; - case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: - return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING"; - case LETTERBOX_BACKGROUND_WALLPAPER: - return "LETTERBOX_BACKGROUND_WALLPAPER"; - default: - return "unknown=" + backgroundType; - } - } - - /** - * 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() { - return mLetterboxBackgroundWallpaperDarkScrimAlpha; - } - - /** - * 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}. - */ - int getLetterboxBackgroundWallpaperBlurRadius() { - return mLetterboxBackgroundWallpaperBlurRadius; - } - @Override public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) { if (!checkCallingPermission( diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 5942f34048e7..1b578d1d452c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -19,10 +19,10 @@ 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.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; -import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; -import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_WALLPAPER; +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; @@ -41,7 +41,7 @@ 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.WindowManagerService.LetterboxBackgroundType; +import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; import java.io.IOException; import java.io.PrintWriter; @@ -64,10 +64,12 @@ 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 @@ -119,30 +121,12 @@ public class WindowManagerShellCommand extends ShellCommand { return runGetIgnoreOrientationRequest(pw); case "dump-visible-window-views": return runDumpVisibleWindowViews(pw); - case "set-fixed-orientation-letterbox-aspect-ratio": - return runSetFixedOrientationLetterboxAspectRatio(pw); - case "get-fixed-orientation-letterbox-aspect-ratio": - return runGetFixedOrientationLetterboxAspectRatio(pw); - case "set-letterbox-activity-corners-radius": - return runSetLetterboxActivityCornersRadius(pw); - case "get-letterbox-activity-corners-radius": - return runGetLetterboxActivityCornersRadius(pw); - case "set-letterbox-background-type": - return runSetLetterboxBackgroundType(pw); - case "get-letterbox-background-type": - return runGetLetterboxBackgroundType(pw); - case "set-letterbox-background-color": - return runSetLetterboxBackgroundColor(pw); - case "get-letterbox-background-color": - return runGetLetterboxBackgroundColor(pw); - case "set-letterbox-background-wallpaper-blur-radius": - return runSetLetterboxBackgroundWallpaperBlurRadius(pw); - case "get-letterbox-background-wallpaper-blur-radius": - return runGetLetterboxBackgroundWallpaperBlurRadius(pw); - case "set-letterbox-background-wallpaper-dark-scrim-alpha": - return runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw); - case "get-letterbox-background-wallpaper-dark-scrim-alpha": - return runGetLetterboxBackgroundWallpaperDarkScrimAlpha(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 "reset": @@ -607,12 +591,6 @@ public class WindowManagerShellCommand extends ShellCommand { final float aspectRatio; try { String arg = getNextArgRequired(); - if ("reset".equals(arg)) { - synchronized (mInternal.mGlobalLock) { - mInternal.resetFixedOrientationLetterboxAspectRatio(); - } - return 0; - } aspectRatio = Float.parseFloat(arg); } catch (NumberFormatException e) { getErrPrintWriter().println("Error: bad aspect ratio format " + e); @@ -623,19 +601,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mInternal.setFixedOrientationLetterboxAspectRatio(aspectRatio); - } - return 0; - } - - private int runGetFixedOrientationLetterboxAspectRatio(PrintWriter pw) throws RemoteException { - synchronized (mInternal.mGlobalLock) { - final float aspectRatio = mInternal.getFixedOrientationLetterboxAspectRatio(); - if (aspectRatio <= WindowManagerService.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) { - pw.println("Letterbox aspect ratio is not set"); - } else { - pw.println("Letterbox aspect ratio is " + aspectRatio); - } + mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(aspectRatio); } return 0; } @@ -644,12 +610,6 @@ public class WindowManagerShellCommand extends ShellCommand { final int cornersRadius; try { String arg = getNextArgRequired(); - if ("reset".equals(arg)) { - synchronized (mInternal.mGlobalLock) { - mInternal.resetLetterboxActivityCornersRadius(); - } - return 0; - } cornersRadius = Integer.parseInt(arg); } catch (NumberFormatException e) { getErrPrintWriter().println("Error: bad corners radius format " + e); @@ -660,110 +620,59 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mInternal.setLetterboxActivityCornersRadius(cornersRadius); - } - return 0; - } - - private int runGetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException { - synchronized (mInternal.mGlobalLock) { - final int cornersRadius = mInternal.getLetterboxActivityCornersRadius(); - if (cornersRadius < 0) { - pw.println("Letterbox corners radius is not set"); - } else { - pw.println("Letterbox corners radius is " + cornersRadius); - } + mLetterboxConfiguration.setLetterboxActivityCornersRadius(cornersRadius); } return 0; } private int runSetLetterboxBackgroundType(PrintWriter pw) throws RemoteException { @LetterboxBackgroundType final int backgroundType; - - String arg = getNextArgRequired(); - if ("reset".equals(arg)) { - synchronized (mInternal.mGlobalLock) { - mInternal.resetLetterboxBackgroundType(); - } - return 0; - } - 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; - } - synchronized (mInternal.mGlobalLock) { - mInternal.setLetterboxBackgroundType(backgroundType); - } - return 0; - } - - private int runGetLetterboxBackgroundType(PrintWriter pw) throws RemoteException { - synchronized (mInternal.mGlobalLock) { - @LetterboxBackgroundType final int backgroundType = - mInternal.getLetterboxBackgroundType(); - switch (backgroundType) { - case LETTERBOX_BACKGROUND_SOLID_COLOR: - pw.println("Letterbox background type is 'solid_color'"); + try { + String arg = getNextArgRequired(); + switch (arg) { + case "solid_color": + backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR; break; - case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: - pw.println("Letterbox background type is 'app_color_background'"); + case "app_color_background": + backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; break; - case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: - pw.println("Letterbox background type is 'app_color_background_floating'"); + case "app_color_background_floating": + backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; break; - case LETTERBOX_BACKGROUND_WALLPAPER: - pw.println("Letterbox background type is 'wallpaper'"); + case "wallpaper": + backgroundType = LETTERBOX_BACKGROUND_WALLPAPER; break; default: - throw new AssertionError( - "Unexpected letterbox background type: " + backgroundType); + 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; - String arg = getNextArgRequired(); try { - if ("reset".equals(arg)) { - synchronized (mInternal.mGlobalLock) { - mInternal.resetLetterboxBackgroundColor(); - } - return 0; - } + 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 + " but got " + arg); + + "an argument " + e); return -1; } synchronized (mInternal.mGlobalLock) { - mInternal.setLetterboxBackgroundColor(color); - } - return 0; - } - - private int runGetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException { - synchronized (mInternal.mGlobalLock) { - final Color color = mInternal.getLetterboxBackgroundColor(); - pw.println("Letterbox background color is " + Integer.toHexString(color.toArgb())); + mLetterboxConfiguration.setLetterboxBackgroundColor(color); } return 0; } @@ -773,12 +682,6 @@ public class WindowManagerShellCommand extends ShellCommand { final int radius; try { String arg = getNextArgRequired(); - if ("reset".equals(arg)) { - synchronized (mInternal.mGlobalLock) { - mInternal.resetLetterboxBackgroundWallpaperBlurRadius(); - } - return 0; - } radius = Integer.parseInt(arg); } catch (NumberFormatException e) { getErrPrintWriter().println("Error: blur radius format " + e); @@ -789,20 +692,7 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mInternal.setLetterboxBackgroundWallpaperBlurRadius(radius); - } - return 0; - } - - private int runGetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw) - throws RemoteException { - synchronized (mInternal.mGlobalLock) { - final int radius = mInternal.getLetterboxBackgroundWallpaperBlurRadius(); - if (radius <= 0) { - pw.println("Letterbox background wallpaper blur radius is not set"); - } else { - pw.println("Letterbox background wallpaper blur radius is " + radius); - } + mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius); } return 0; } @@ -812,12 +702,6 @@ public class WindowManagerShellCommand extends ShellCommand { final float alpha; try { String arg = getNextArgRequired(); - if ("reset".equals(arg)) { - synchronized (mInternal.mGlobalLock) { - mInternal.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); - } - return 0; - } alpha = Float.parseFloat(arg); } catch (NumberFormatException e) { getErrPrintWriter().println("Error: bad alpha format " + e); @@ -828,24 +712,140 @@ public class WindowManagerShellCommand extends ShellCommand { return -1; } synchronized (mInternal.mGlobalLock) { - mInternal.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha); + mLetterboxConfiguration.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha); } return 0; } - private int runGetLetterboxBackgroundWallpaperDarkScrimAlpha(PrintWriter pw) - throws RemoteException { + 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) { - final float alpha = mInternal.getLetterboxBackgroundWallpaperDarkScrimAlpha(); - if (alpha < 0 || alpha >= 1) { - pw.println("Letterbox dark scrim alpha is not set"); - } else { - pw.println("Letterbox dark scrim alpha is " + alpha); + 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 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()); @@ -870,23 +870,8 @@ public class WindowManagerShellCommand extends ShellCommand { // set-ignore-orientation-request mInterface.setIgnoreOrientationRequest(displayId, false /* ignoreOrientationRequest */); - // set-fixed-orientation-letterbox-aspect-ratio - mInternal.resetFixedOrientationLetterboxAspectRatio(); - - // set-letterbox-activity-corners-radius - mInternal.resetLetterboxActivityCornersRadius(); - - // set-letterbox-background-type - mInternal.resetLetterboxBackgroundType(); - - // set-letterbox-background-color - mInternal.resetLetterboxBackgroundColor(); - - // set-letterbox-background-wallpaper-blur-radius - mInternal.resetLetterboxBackgroundWallpaperBlurRadius(); - - // set-letterbox-background-wallpaper-dark-scrim-alpha - mInternal.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); + // set-letterbox-style + resetLetterboxStyle(); // set-sandbox-display-apis mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true); @@ -922,42 +907,13 @@ 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-fixed-orientation-letterbox-aspect-ratio [reset|aspectRatio]"); - pw.println(" get-fixed-orientation-letterbox-aspect-ratio"); - pw.println(" Aspect ratio of letterbox for fixed orientation. If aspectRatio <= " - + WindowManagerService.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); - pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will be"); - pw.println(" ignored and framework implementation will determine aspect ratio."); - pw.println(" set-letterbox-activity-corners-radius [reset|cornersRadius]"); - pw.println(" get-letterbox-activity-corners-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(" set-letterbox-background-color [reset|colorName|'\\#RRGGBB']"); - pw.println(" get-letterbox-background-color"); - pw.println(" Color of letterbox background which is be used when letterbox background"); - pw.println(" type is 'solid-color'. Use get(set)-letterbox-background-type to check"); - pw.println(" and control letterbox background type. See Color#parseColor for allowed"); - pw.println(" color formats (#RRGGBB and some colors by name, e.g. magenta or olive). "); - pw.println(" set-letterbox-background-type [reset|solid_color|app_color_background"); - pw.println(" |app_color_background_floating|wallpaper]"); - pw.println(" get-letterbox-background-type"); - pw.println(" Type of background used in the letterbox mode."); - pw.println(" set-letterbox-background-wallpaper-blur-radius [reset|radius]"); - pw.println(" get-letterbox-background-wallpaper-blur-radius"); - pw.println(" Blur radius for 'wallpaper' letterbox background. If radius <= 0"); - pw.println(" both it and R.dimen.config_letterboxBackgroundWallpaperBlurRadius are "); - pw.println(" ignored and 0 is used."); - pw.println(" set-letterbox-background-wallpaper-dark-scrim-alpha [reset|alpha]"); - pw.println(" get-letterbox-background-wallpaper-dark-scrim-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 and "); - pw.println(" 0.0 (transparent) is used instead."); 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); + pw.println(" reset [-d DISPLAY_ID]"); pw.println(" Reset all override settings."); if (!IS_USER) { @@ -967,4 +923,47 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" Logging settings."); } } + + 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."); + } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 2e37fee1fb1c..9382b8eed0b8 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -36,6 +36,7 @@ import android.annotation.Nullable; import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.GraphicBuffer; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Binder; @@ -45,7 +46,6 @@ import android.os.Parcel; import android.os.RemoteException; import android.util.ArraySet; import android.util.Slog; -import android.view.Surface; import android.view.SurfaceControl; import android.window.IDisplayAreaOrganizerController; import android.window.ITaskOrganizerController; @@ -766,18 +766,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer( + buffer.getHardwareBuffer()); SurfaceControl screenshot = mService.mWindowManager.mSurfaceControlFactory.apply(null) .setName(wc.getName() + " - Organizer Screenshot") - .setBufferSize(bounds.width(), bounds.height()) .setFormat(PixelFormat.TRANSLUCENT) .setParent(wc.getParentSurfaceControl()) + .setSecure(buffer.containsSecureLayers()) .setCallsite("WindowOrganizerController.takeScreenshot") + .setBLASTLayer() .build(); - Surface surface = new Surface(); - surface.copyFrom(screenshot); - surface.attachAndQueueBufferWithColorSpace(buffer.getHardwareBuffer(), null); - surface.release(); + SurfaceControl.Transaction transaction = mService.mWindowManager.mTransactionFactory.get(); + transaction.setBuffer(screenshot, graphicBuffer); + transaction.setColorSpace(screenshot, buffer.getColorSpace()); + transaction.apply(); outSurfaceControl.copyFrom(screenshot, "WindowOrganizerController.takeScreenshot"); return true; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 46d923b0cfb0..1a5042ffd56d 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1140,7 +1140,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void attach() { if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken); - mSession.windowAddedLocked(mAttrs.packageName); + mSession.windowAddedLocked(); } /** diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index ddcb2bf7be22..6283b4e56402 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -96,6 +96,7 @@ import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_REMOVE_N import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED; import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED; import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED; +import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; @@ -8517,20 +8518,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { + " as profile owner for user " + userHandle); return false; } - if (who == null - || !isPackageInstalledForUser(who.getPackageName(), userHandle)) { - throw new IllegalArgumentException("Component " + who - + " not installed for userId:" + userHandle); - } + Preconditions.checkArgument(who != null); final CallerIdentity caller = getCallerIdentity(); synchronized (getLockObject()) { enforceCanSetProfileOwnerLocked(caller, who, userHandle); - + Preconditions.checkArgument(isPackageInstalledForUser(who.getPackageName(), userHandle), + "Component " + who + " not installed for userId:" + userHandle); final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle); - if (admin == null || getUserData(userHandle).mRemovingAdmins.contains(who)) { - throw new IllegalArgumentException("Not active admin: " + who); - } + Preconditions.checkArgument(admin != null && !getUserData( + userHandle).mRemovingAdmins.contains(who), "Not active admin: " + who); final int parentUserId = getProfileParentId(userHandle); // When trying to set a profile owner on a new user, it may be that this user is @@ -8739,7 +8736,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle) - && getManagedUserId(userHandle) == -1) { + && getManagedUserId(userHandle) == -1 + && newState != STATE_USER_UNMANAGED) { // No managed device, user or profile, so setting provisioning state makes no sense. throw new IllegalStateException("Not allowed to change provisioning state unless a " + "device or profile owner is set."); @@ -8802,6 +8800,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { case DevicePolicyManager.STATE_USER_SETUP_FINALIZED: // Cannot transition out of finalized. break; + case DevicePolicyManager.STATE_USER_PROFILE_FINALIZED: + // Should only move to an unmanaged state after removing the work profile. + if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) { + return; + } + break; } // Didn't meet any of the accepted state transition checks above, throw appropriate error. diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp index b136d00fccac..83677c2a2242 100644 --- a/services/tests/PackageManagerServiceTests/host/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/Android.bp @@ -36,6 +36,10 @@ java_test_host { ], test_suites: ["general-tests"], java_resources: [ + ":PackageManagerTestOverlayActor", + ":PackageManagerTestOverlay", + ":PackageManagerTestOverlayTarget", + ":PackageManagerTestOverlayTargetNoOverlayable", ":PackageManagerTestAppDeclaresStaticLibrary", ":PackageManagerTestAppStub", ":PackageManagerTestAppUsesStaticLibrary", diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt new file mode 100644 index 000000000000..558d01ed203d --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt @@ -0,0 +1,117 @@ +/* + * 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.pm.test + +import com.android.internal.util.test.SystemPreparer +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import org.junit.After +import org.junit.Before +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import java.io.File + +@RunWith(DeviceJUnit4ClassRunner::class) +class OverlayActorVisibilityTest : BaseHostJUnit4Test() { + + companion object { + private const val ACTOR_PKG_NAME = "com.android.server.pm.test.overlay.actor" + private const val ACTOR_APK = "PackageManagerTestOverlayActor.apk" + private const val TARGET_APK = "PackageManagerTestOverlayTarget.apk" + private const val OVERLAY_APK = "PackageManagerTestOverlay.apk" + private const val TARGET_NO_OVERLAYABLE_APK = + "PackageManagerTestOverlayTargetNoOverlayable.apk" + + @get:ClassRule + val deviceRebootRule = SystemPreparer.TestRuleDelegate(true) + } + + @get:Rule + val tempFolder = TemporaryFolder() + + private val preparer: SystemPreparer = SystemPreparer( + tempFolder, + SystemPreparer.RebootStrategy.FULL, + deviceRebootRule + ) { this.device } + + private val namedActorFile = File( + "/system/etc/sysconfig/com.android.server.pm.test.OverlayActorVisibilityTest.xml" + ) + + @Before + @After + fun uninstallPackages() { + device.uninstallPackages(ACTOR_APK, TARGET_APK, OVERLAY_APK) + } + + @Before + fun pushSysConfigFile() { + // In order for the test app to be the verification agent, it needs a permission file + // which can be pushed onto the system and removed afterwards. + // language=XML + val file = tempFolder.newFile().apply { + """ + <config> + <named-actor + namespace="androidTest" + name="OverlayActorVisibilityTest" + package="$ACTOR_PKG_NAME" + /> + </config> + """ + .trimIndent() + .let { writeText(it) } + } + + preparer.pushFile(file, namedActorFile.toString()) + .reboot() + } + + @After + fun deleteSysConfigFile() { + preparer.deleteFile(namedActorFile.toString()) + .reboot() + } + + @Test + fun testVisibilityByOverlayable() { + assertThat(device.installJavaResourceApk(tempFolder, ACTOR_APK, false)).isNull() + assertThat(device.installJavaResourceApk(tempFolder, OVERLAY_APK, false)).isNull() + assertThat(device.installJavaResourceApk(tempFolder, TARGET_NO_OVERLAYABLE_APK, false)) + .isNull() + + runDeviceTests( + ACTOR_PKG_NAME, "$ACTOR_PKG_NAME.OverlayableVisibilityTest", + "verifyNotVisible" + ) + + assertThat(device.installJavaResourceApk(tempFolder, TARGET_APK, true)).isNull() + + assertWithMessage(device.executeShellCommand("dumpsys package $OVERLAY_APK")) + + runDeviceTests( + ACTOR_PKG_NAME, "$ACTOR_PKG_NAME.OverlayableVisibilityTest", + "verifyVisible" + ) + } +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp new file mode 100644 index 000000000000..92dcd348a8e2 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp @@ -0,0 +1,26 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "PackageManagerTestOverlay", + srcs: [ + "src/**/*.kt", + ], +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml new file mode 100644 index 000000000000..21e4432b299f --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.overlay"> + <application/> + <overlay android:targetPackage="com.android.server.pm.test.overlay.target" + android:targetName="Testing"/> +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml new file mode 100644 index 000000000000..f0b85864ab6b --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <string name="policy_public">You have been overlaid</string> +</resources> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp new file mode 100644 index 000000000000..57184748d074 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp @@ -0,0 +1,33 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "PackageManagerTestOverlayActor", + srcs: ["src/**/*.kt"], + static_libs: [ + "androidx.test.runner", + "junit", + "kotlin-test", + "truth-prebuilt", + ], +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml new file mode 100644 index 000000000000..a92a14f68200 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.overlay.actor" + > + + <application/> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.pm.test.overlay.actor" + /> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt new file mode 100644 index 000000000000..7537247f4e8f --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test.overlay.actor + +import android.content.Context +import android.content.pm.PackageManager +import androidx.test.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import kotlin.test.assertFailsWith + +class OverlayableVisibilityTest { + + companion object { + private const val TARGET_PACKAGE = "com.android.server.pm.test.overlay.target" + private const val OVERLAY_PACKAGE = "com.android.server.pm.test.overlay" + } + + private val context: Context = InstrumentationRegistry.getContext() + private val packageManager = context.packageManager + + @Test + fun verifyVisible() { + assertThat(packageManager.getApplicationInfo(TARGET_PACKAGE, 0)).isNotNull() + assertThat(packageManager.getApplicationInfo(OVERLAY_PACKAGE, 0)).isNotNull() + } + + @Test + fun verifyNotVisible() { + assertFailsWith(PackageManager.NameNotFoundException::class) { + packageManager.getApplicationInfo(TARGET_PACKAGE, 0) + } + assertFailsWith(PackageManager.NameNotFoundException::class) { + packageManager.getApplicationInfo(OVERLAY_PACKAGE, 0) + } + } +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp new file mode 100644 index 000000000000..2bb6b82a6bbc --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp @@ -0,0 +1,38 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "PackageManagerTestOverlayTarget", + defaults: ["cts_support_defaults"], + sdk_version: "current", + resource_dirs: [ + "res", + "res_overlayable", + ], +} + +android_test_helper_app { + name: "PackageManagerTestOverlayTargetNoOverlayable", + defaults: ["cts_support_defaults"], + sdk_version: "current", + resource_dirs: [ + "res", + ], +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml new file mode 100644 index 000000000000..6038cb1ec20c --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.overlay.target"> +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt new file mode 100644 index 000000000000..4625e3bcd041 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt @@ -0,0 +1 @@ +Not overlaid
\ No newline at end of file diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml new file mode 100644 index 000000000000..283f5c13256e --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <public-group type="string" first-id="0x7f010000"> + <public name="policy_public" /> + </public-group> +</resources> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml new file mode 100644 index 000000000000..822194fa7ebb --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <string name="policy_public">Not overlaid</string> +</resources> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml new file mode 100644 index 000000000000..0100389f690a --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <overlayable name="Testing" actor="overlay://androidTest/OverlayActorVisibilityTest"> + <policy type="public"> + <item type="string" name="policy_public" /> + </policy> + </overlayable> +</resources> diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt index dce853afc763..4de8d52a8c2e 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt @@ -235,9 +235,23 @@ class DomainVerificationCollectorTest { <category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.DEFAULT"/> <data android:scheme="https"/> - <data android:path="/sub5"/> - <data android:host="example5.com"/> - <data android:host="invalid5"/> + <data android:path="/sub6"/> + <data android:host="example6.com"/> + <data android:host="invalid6"/> + </intent-filter> + <intent-filter android:autoVerify="$autoVerify"> + <category android:name="android.intent.category.BROWSABLE"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:scheme="example7.com"/> + <intent-filter android:autoVerify="$autoVerify"> + <category android:name="android.intent.category.BROWSABLE"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:scheme="https"/> + </intent-filter> + <intent-filter android:autoVerify="$autoVerify"> + <category android:name="android.intent.category.BROWSABLE"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:path="/sub7"/> </intent-filter> </xml> """.trimIndent() @@ -324,6 +338,30 @@ class DomainVerificationCollectorTest { addDataAuthority("invalid6", null) } ) + addIntent( + ParsedIntentInfo().apply { + setAutoVerify(autoVerify) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataAuthority("example7.com", null) + } + ) + addIntent( + ParsedIntentInfo().apply { + setAutoVerify(autoVerify) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("https") + } + ) + addIntent( + ParsedIntentInfo().apply { + setAutoVerify(autoVerify) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataPath("/sub7", PatternMatcher.PATTERN_LITERAL) + } + ) }, ) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt new file mode 100644 index 000000000000..98634b28ea9d --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt @@ -0,0 +1,126 @@ +/* + * 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.pm.test.verify.domain + +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import com.android.server.pm.verify.domain.DomainVerificationUtils +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class DomainVerificationValidIntentTest { + + companion object { + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun parameters(): Array<Params> { + val succeeding = mutableListOf<Params>() + val failing = mutableListOf<Params>() + + // Start with the base intent + val base = Params(categorySet = emptySet()).also { succeeding += it } + + // Add all explicit supported categorySet + succeeding += base.copy( + categorySet = setOf(Intent.CATEGORY_BROWSABLE), + matchDefaultOnly = true + ) + + failing += base.copy( + categorySet = setOf(Intent.CATEGORY_BROWSABLE), + matchDefaultOnly = false + ) + + succeeding += listOf(true, false).map { + base.copy( + categorySet = setOf(Intent.CATEGORY_DEFAULT), + matchDefaultOnly = it + ) + } + + succeeding += listOf(true, false).map { + base.copy( + categorySet = setOf(Intent.CATEGORY_BROWSABLE, Intent.CATEGORY_DEFAULT), + matchDefaultOnly = it + ) + } + + // Fail on unsupported category + failing += listOf( + emptySet(), + setOf(Intent.CATEGORY_BROWSABLE), + setOf(Intent.CATEGORY_DEFAULT), + setOf(Intent.CATEGORY_BROWSABLE, Intent.CATEGORY_DEFAULT) + ).map { base.copy(categorySet = it + "invalid.CATEGORY") } + + // Fail on unsupported action + failing += base.copy(action = Intent.ACTION_SEND) + + // Fail on unsupported domain + failing += base.copy(domain = "invalid") + + // Fail on empty domains + failing += base.copy(domain = "") + + // Fail on missing scheme + failing += base.copy( + uriFunction = { Uri.Builder().authority("test.com").build() } + ) + + // Fail on missing host + failing += base.copy( + domain = "", + uriFunction = { Uri.Builder().scheme("https").build() } + ) + + succeeding.forEach { it.expected = true } + failing.forEach { it.expected = false } + return (succeeding + failing).toTypedArray() + } + + data class Params( + val action: String = Intent.ACTION_VIEW, + val categorySet: Set<String> = mutableSetOf(), + val domain: String = "test.com", + val matchDefaultOnly: Boolean = true, + var expected: Boolean? = null, + val uriFunction: (domain: String) -> Uri = { Uri.parse("https://$it") } + ) { + val intent = Intent(action, uriFunction(domain)).apply { + categorySet.forEach(::addCategory) + } + + override fun toString() = intent.toShortString(false, false, false, false) + + ", matchDefaultOnly = $matchDefaultOnly, expected = $expected" + } + } + + @Parameterized.Parameter(0) + lateinit var params: Params + + @Test + fun verify() { + val flags = if (params.matchDefaultOnly) PackageManager.MATCH_DEFAULT_ONLY else 0 + assertThat(DomainVerificationUtils.isDomainVerificationIntent(params.intent, flags)) + .isEqualTo(params.expected) + } +} diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index e4b650cad055..17a5dccb57da 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -28,6 +28,8 @@ <uses-permission android:name="android.permission.MANAGE_APPOPS"/> <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/> + <uses-permission + android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/> <!-- needed by MasterClearReceiverTest to display a system dialog --> <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/> 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 7654093f9e7e..d55bbd1d8e45 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -468,8 +468,9 @@ public class AlarmManagerServiceTest { TEST_CALLING_UID); } - private void setPrioritizedAlarm(int type, long triggerTime, IAlarmListener listener) { - mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test", + private void setPrioritizedAlarm(int type, long triggerTime, long windowLength, + IAlarmListener listener) { + mService.setImpl(type, triggerTime, windowLength, 0, null, listener, "test", FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null); } @@ -1685,7 +1686,7 @@ public class AlarmManagerServiceTest { final int numAlarms = 10; for (int i = 0; i < numAlarms; i++) { setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - new IAlarmListener.Stub() { + 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { @@ -1720,7 +1721,7 @@ public class AlarmManagerServiceTest { final int numAlarms = 10; for (int i = 0; i < numAlarms; i++) { setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - new IAlarmListener.Stub() { + 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { @@ -1738,12 +1739,12 @@ public class AlarmManagerServiceTest { } assertEquals(numAlarms, alarmsFired.get()); - setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, new IAlarmListener.Stub() { + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { } }); - setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, new IAlarmListener.Stub() { + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { } @@ -2263,6 +2264,28 @@ public class AlarmManagerServiceTest { } @Test + public void minWindowPriorityAlarm() { + doReturn(true).when( + () -> CompatChanges.isChangeEnabled( + eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS), + anyString(), any(UserHandle.class))); + final long minWindow = 73; + 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 { + } + }); + assertEquals(1, mService.mAlarmStore.size()); + final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); + assertEquals(window, a.windowLength); + } + } + + @Test public void denyListPackagesAdded() { mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"}); setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5"); diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index edc0d468dbe1..a8d8a90e8935 100644 --- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -16,51 +16,66 @@ package com.android.server.app; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; import android.Manifest; import android.app.GameManager; import android.content.Context; import android.content.ContextWrapper; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.platform.test.annotations.Presubmit; +import android.provider.DeviceConfig; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.function.Supplier; @RunWith(AndroidJUnit4.class) @SmallTest @Presubmit public class GameManagerServiceTests { - + @Mock MockContext mMockContext; private static final String TAG = "GameServiceTests"; private static final String PACKAGE_NAME_INVALID = "com.android.app"; private static final int USER_ID_1 = 1001; private static final int USER_ID_2 = 1002; + private MockitoSession mMockingSession; + private String mPackageName; + @Mock + private PackageManager mMockPackageManager; + // Stolen from ConnectivityServiceTest.MockContext - static class MockContext extends ContextWrapper { + class MockContext extends ContextWrapper { private static final String TAG = "MockContext"; // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); - @Mock - private final MockPackageManager mMockPackageManager; - MockContext(Context base) { super(base); - mMockPackageManager = new MockPackageManager(); } /** @@ -112,15 +127,31 @@ public class GameManagerServiceTests { } } - @Mock - private MockContext mMockContext; - - private String mPackageName; - @Before public void setUp() throws Exception { + mMockingSession = mockitoSession() + .initMocks(this) + .mockStatic(DeviceConfig.class) + .strictness(Strictness.WARN) + .startMocking(); mMockContext = new MockContext(InstrumentationRegistry.getContext()); mPackageName = mMockContext.getPackageName(); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.category = ApplicationInfo.CATEGORY_GAME; + final PackageInfo pi = new PackageInfo(); + pi.packageName = mPackageName; + final List<PackageInfo> packages = new ArrayList<>(); + packages.add(pi); + when(mMockPackageManager.getInstalledPackages(anyInt())).thenReturn(packages); + when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); + } + + @After + public void tearDown() throws Exception { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } } private void mockModifyGameModeGranted() { @@ -133,6 +164,60 @@ public class GameManagerServiceTests { PackageManager.PERMISSION_DENIED); } + private void mockDeviceConfigDefault() { + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, "").build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigNone() { + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigPerformance() { + String configString = "mode=2,downscaleFactor=0.5"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigBattery() { + String configString = "mode=3,downscaleFactor=0.7"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigAll() { + String configString = "mode=3,downscaleFactor=0.7:mode=2,downscaleFactor=0.5"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigInvalid() { + String configString = "mode=2,downscaleFactor=0.55"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigMalformed() { + String configString = "adsljckv=nin3rn9hn1231245:8795tq=21ewuydg"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + /** * By default game mode is not supported. */ @@ -190,7 +275,6 @@ public class GameManagerServiceTests { public void testGetGameModeInvalidPackageName() { GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onUserStarting(USER_ID_1); - try { assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameManagerService.getGameMode(PACKAGE_NAME_INVALID, @@ -268,4 +352,137 @@ public class GameManagerServiceTests { assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameManagerService.getGameMode(mPackageName, USER_ID_2)); } + + /** + * Phonesky device config exists, but is only propagating the default value. + */ + @Test + public void testDeviceConfigDefault() { + mockDeviceConfigDefault(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Phonesky device config does not exists. + */ + @Test + public void testDeviceConfigNone() { + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Phonesky device config for performance mode exists and is valid. + */ + @Test + public void testDeviceConfigPerformance() { + mockDeviceConfigPerformance(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + boolean perfModeExists = false; + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_PERFORMANCE) { + perfModeExists = true; + } + } + assertEquals(modes.length, 1); + assertTrue(perfModeExists); + } + + /** + * Phonesky device config for battery mode exists and is valid. + */ + @Test + public void testDeviceConfigBattery() { + mockDeviceConfigBattery(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + boolean batteryModeExists = false; + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_BATTERY) { + batteryModeExists = true; + } + } + assertEquals(modes.length, 1); + assertTrue(batteryModeExists); + } + + /** + * Phonesky device configs for both battery and performance modes exists and are valid. + */ + @Test + public void testDeviceConfigAll() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + boolean batteryModeExists = false; + boolean perfModeExists = false; + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_BATTERY) { + batteryModeExists = true; + } else if (mode == GameManager.GAME_MODE_PERFORMANCE) { + perfModeExists = true; + } + } + assertTrue(batteryModeExists); + assertTrue(perfModeExists); + } + + /** + * Phonesky device config contains values that parse correctly but are not valid in game mode. + */ + @Test + public void testDeviceConfigInvalid() { + mockDeviceConfigInvalid(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Phonesky device config is garbage. + */ + @Test + public void testDeviceConfigMalformed() { + mockDeviceConfigMalformed(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } } 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 73ec5b8d3522..1b42dfa0712e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -31,7 +31,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; -import static android.app.admin.PasswordMetrics.computeForPassword; +import static android.app.admin.PasswordMetrics.computeForPasswordOrPin; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE; import static android.net.InetAddresses.parseNumericAddress; @@ -1551,6 +1551,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetProfileOwner_failures() throws Exception { // TODO Test more failure cases. Basically test all chacks in enforceCanSetProfileOwner(). + // Package doesn't exist and caller is not system + assertExpectException(SecurityException.class, + /* messageRegex= */ "Calling identity is not authorized", + () -> dpm.setProfileOwner(admin1, "owner-name", UserHandle.USER_SYSTEM)); + + // Package exists, but caller is not system + setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID); + assertExpectException(SecurityException.class, + /* messageRegex= */ "Calling identity is not authorized", + () -> dpm.setProfileOwner(admin1, "owner-name", UserHandle.USER_SYSTEM)); } @Test @@ -3169,6 +3179,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + public void testSetUserProvisioningState_profileFinalized_canTransitionToUserUnmanaged() + throws Exception { + setupProfileOwner(); + + exerciseUserProvisioningTransitions(CALLER_USER_HANDLE, + DevicePolicyManager.STATE_USER_PROFILE_FINALIZED, + DevicePolicyManager.STATE_USER_UNMANAGED); + } + + @Test public void testSetUserProvisioningState_illegalTransitionToAnotherInProgressState() throws Exception { setupProfileOwner(); @@ -5156,7 +5176,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { reset(mContext.spiedContext); - PasswordMetrics passwordMetricsNoSymbols = computeForPassword("abcdXYZ5".getBytes()); + PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin( + "abcdXYZ5".getBytes(), /* isPin */ false); setActivePasswordState(passwordMetricsNoSymbols); assertThat(dpm.isActivePasswordSufficient()).isTrue(); @@ -5183,7 +5204,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { reset(mContext.spiedContext); assertThat(dpm.isActivePasswordSufficient()).isFalse(); - PasswordMetrics passwordMetricsWithSymbols = computeForPassword("abcd.XY5".getBytes()); + PasswordMetrics passwordMetricsWithSymbols = computeForPasswordOrPin( + "abcd.XY5".getBytes(), /* isPin */ false); setActivePasswordState(passwordMetricsWithSymbols); assertThat(dpm.isActivePasswordSufficient()).isTrue(); @@ -5237,7 +5259,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM); when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM)) - .thenReturn(computeForPassword("184342".getBytes())); + .thenReturn(computeForPasswordOrPin("184342".getBytes(), /* isPin */ true)); // Numeric password is compliant with current requirement (QUALITY_NUMERIC set explicitly // on the parent admin) @@ -6360,7 +6382,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { .thenReturn(CALLER_USER_HANDLE); when(getServices().lockSettingsInternal .getUserPasswordMetrics(CALLER_USER_HANDLE)) - .thenReturn(computeForPassword("asdf".getBytes())); + .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false)); assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_MEDIUM); } @@ -6380,10 +6402,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().lockSettingsInternal .getUserPasswordMetrics(CALLER_USER_HANDLE)) - .thenReturn(computeForPassword("asdf".getBytes())); + .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false)); when(getServices().lockSettingsInternal .getUserPasswordMetrics(parentUser.id)) - .thenReturn(computeForPassword("parentUser".getBytes())); + .thenReturn(computeForPasswordOrPin("parentUser".getBytes(), /* isPin */ false)); assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH); } @@ -7059,13 +7081,15 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_NONE); reset(mContext.spiedContext); - PasswordMetrics passwordMetricsNoSymbols = computeForPassword("1234".getBytes()); + PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin( + "1234".getBytes(), /* isPin */ true); setActivePasswordState(passwordMetricsNoSymbols); assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_LOW); assertThat(dpm.isActivePasswordSufficient()).isFalse(); reset(mContext.spiedContext); - passwordMetricsNoSymbols = computeForPassword("84125312943a".getBytes()); + passwordMetricsNoSymbols = computeForPasswordOrPin( + "84125312943a".getBytes(), /* isPin */ false); setActivePasswordState(passwordMetricsNoSymbols); assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH); // using isActivePasswordSufficient diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java index 4a42940832c4..5d60a897b9a6 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java @@ -208,4 +208,9 @@ public class LockSettingsServiceTestable extends LockSettingsService { parcel.recycle(); } } -} + + @Override + void setKeystorePassword(byte[] password, int userHandle) { + + } +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java index 2b9a05c3ef63..efa1b044f8f9 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java @@ -20,11 +20,16 @@ import android.app.KeyguardManager; import android.app.NotificationManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; +import android.os.Handler; +import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; @@ -94,4 +99,11 @@ public class MockLockSettingsContext extends ContextWrapper { public int checkCallingOrSelfPermission(String permission) { return PackageManager.PERMISSION_GRANTED; } + + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, + UserHandle user, IntentFilter filter, String broadcastPermission, + Handler scheduler) { + return null; + } } diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt index d888b9258d33..876c84547cae 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt +++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt @@ -37,7 +37,7 @@ class OverlayReferenceMapperTests { @JvmStatic @Parameterized.Parameters(name = "deferRebuild {0}") - fun parameters() = arrayOf(true, false) + fun parameters() = arrayOf(/*true, */false) } private lateinit var mapper: OverlayReferenceMapper @@ -55,11 +55,17 @@ class OverlayReferenceMapperTests { fun targetWithOverlay() { val target = mockTarget() val overlay = mockOverlay() - val existing = mapper.addInOrder(overlay) + val existing = mapper.addInOrder(overlay) { + assertThat(it).isEmpty() + } assertEmpty() - mapper.addInOrder(target, existing = existing) + mapper.addInOrder(target, existing = existing) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay)) - mapper.remove(target) + mapper.remove(target) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertEmpty() } @@ -78,22 +84,34 @@ class OverlayReferenceMapperTests { ) ) ) - val existing = mapper.addInOrder(overlay0, overlay1) + val existing = mapper.addInOrder(overlay0, overlay1) { + assertThat(it).isEmpty() + } assertEmpty() - mapper.addInOrder(target, existing = existing) + mapper.addInOrder(target, existing = existing) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay0, overlay1)) - mapper.remove(overlay0) + mapper.remove(overlay0) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay1)) - mapper.remove(target) + mapper.remove(target) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertEmpty() } @Test fun targetWithoutOverlay() { val target = mockTarget() - mapper.addInOrder(target) + mapper.addInOrder(target) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertMapping(ACTOR_PACKAGE_NAME to setOf(target)) - mapper.remove(target) + mapper.remove(target) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertEmpty() } @@ -101,11 +119,17 @@ class OverlayReferenceMapperTests { fun overlayWithTarget() { val target = mockTarget() val overlay = mockOverlay() - val existing = mapper.addInOrder(target) + val existing = mapper.addInOrder(target) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertMapping(ACTOR_PACKAGE_NAME to setOf(target)) - mapper.addInOrder(overlay, existing = existing) + mapper.addInOrder(overlay, existing = existing) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay)) - mapper.remove(overlay) + mapper.remove(overlay) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertMapping(ACTOR_PACKAGE_NAME to setOf(target)) } @@ -122,34 +146,52 @@ class OverlayReferenceMapperTests { ) ) ) - mapper.addInOrder(target0, target1, overlay) + mapper.addInOrder(target0, target1, overlay) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay)) - mapper.remove(target0) + mapper.remove(target0) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay)) - mapper.remove(target1) + mapper.remove(target1) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } assertEmpty() } @Test fun overlayWithoutTarget() { val overlay = mockOverlay() - mapper.addInOrder(overlay) + mapper.addInOrder(overlay) { + assertThat(it).isEmpty() + } // An overlay can only have visibility exposed through its target assertEmpty() - mapper.remove(overlay) + mapper.remove(overlay) { + assertThat(it).isEmpty() + } assertEmpty() } private fun OverlayReferenceMapper.addInOrder( vararg pkgs: AndroidPackage, - existing: MutableMap<String, AndroidPackage> = mutableMapOf() - ) = pkgs.fold(existing) { map, pkg -> - addPkg(pkg, map) - map[pkg.packageName] = pkg - return@fold map + existing: MutableMap<String, AndroidPackage> = mutableMapOf(), + assertion: (changedPackages: Set<String>) -> Unit + ): MutableMap<String, AndroidPackage> { + val changedPackages = mutableSetOf<String>() + pkgs.forEach { + changedPackages += addPkg(it, existing) + existing[it.packageName] = it + } + assertion(changedPackages) + return existing } - private fun OverlayReferenceMapper.remove(pkg: AndroidPackage) = removePkg(pkg.packageName) + private fun OverlayReferenceMapper.remove( + pkg: AndroidPackage, + assertion: (changedPackages: Set<String>) -> Unit + ) = assertion(removePkg(pkg.packageName)) private fun assertMapping(vararg pairs: Pair<String, Set<AndroidPackage>>) { val expected = pairs.associate { it } diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING index 8070bd1f06a1..558e2591161c 100644 --- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING @@ -7,6 +7,14 @@ "include-filter": "com.android.server.om." } ] + }, + { + "name": "PackageManagerServiceHostTests", + "options": [ + { + "include-filter": "com.android.server.pm.test.OverlayActorVisibilityTest" + } + ] } ] -}
\ No newline at end of file +} diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 9f428c7cbded..67dd0556e098 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -799,10 +799,18 @@ public class AppsFilterTest { simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); - PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID); + // Packages must be added in actor -> overlay -> target order so that the implicit + // visibility of the actor into the overlay can be tested + + PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID); PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID); - PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID); + + // Actor can not see overlay (yet) + assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting, + overlaySetting, SYSTEM_USER)); + + PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID); // Actor can see both target and overlay assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting, @@ -821,6 +829,12 @@ public class AppsFilterTest { actorSetting, SYSTEM_USER)); assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting, actorSetting, SYSTEM_USER)); + + appsFilter.removePackage(targetSetting); + + // Actor loses visibility to the overlay via removal of the target + assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting, + overlaySetting, SYSTEM_USER)); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java index 809b6d561362..182848b4f628 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java @@ -123,6 +123,7 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { for (long i = cal.getTimeInMillis(); i >= 5; i--) { File file = mock(File.class); when(file.getName()).thenReturn(String.valueOf(i)); + when(file.getAbsolutePath()).thenReturn(String.valueOf(i)); AtomicFile af = new AtomicFile(file); expectedFiles.add(af); mDataBase.mHistoryFiles.addLast(af); @@ -133,6 +134,7 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { for (int i = 5; i >= 0; i--) { File file = mock(File.class); when(file.getName()).thenReturn(String.valueOf(cal.getTimeInMillis() - i)); + when(file.getAbsolutePath()).thenReturn(String.valueOf(cal.getTimeInMillis() - i)); AtomicFile af = new AtomicFile(file); mDataBase.mHistoryFiles.addLast(af); } @@ -158,6 +160,7 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { for (long i = cal.getTimeInMillis(); i >= 5; i--) { File file = mock(File.class); when(file.getName()).thenReturn(i + ".bak"); + when(file.getAbsolutePath()).thenReturn(i + ".bak"); AtomicFile af = new AtomicFile(file); mDataBase.mHistoryFiles.addLast(af); } @@ -415,4 +418,36 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { assertThat(mDataBase.mBuffer).isNotEqualTo(nh); verify(mAlarmManager, times(1)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any()); } + + @Test + public void testRemoveFilePathFromHistory_hasMatch() throws Exception { + for (int i = 0; i < 5; i++) { + AtomicFile af = mock(AtomicFile.class); + when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i)); + mDataBase.mHistoryFiles.addLast(af); + } + // Baseline size of history files + assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5); + + // Remove only file number 3 + String filePathToRemove = new File(mRootDir, "af3").getAbsolutePath(); + mDataBase.removeFilePathFromHistory(filePathToRemove); + assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(4); + } + + @Test + public void testRemoveFilePathFromHistory_noMatch() throws Exception { + for (int i = 0; i < 5; i++) { + AtomicFile af = mock(AtomicFile.class); + when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i)); + mDataBase.mHistoryFiles.addLast(af); + } + // Baseline size of history files + assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5); + + // Attempt to remove a filename that doesn't exist, expect nothing to break or change + String filePathToRemove = new File(mRootDir, "af.thisfileisfake").getAbsolutePath(); + mDataBase.removeFilePathFromHistory(filePathToRemove); + assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5); + } } 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 87efaa270ec4..37d7198ef150 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5057,7 +5057,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testToastRateLimiterCanPreventShowCallForCustomToast() throws Exception { + public void testToastRateLimiterWontPreventShowCallForCustomToastWhenInForeground() + throws Exception { final String testPackage = "testPackageName"; assertEquals(0, mService.mToastQueue.size()); mService.isSystemUid = false; @@ -5075,30 +5076,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { INotificationManager nmService = (INotificationManager) mService.mService; nmService.enqueueToast(testPackage, token, callback, 2000, 0); - verify(callback, times(0)).show(any()); - } - - @Test - public void testCustomToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception { - final String testPackage = "testPackageName"; - assertEquals(0, mService.mToastQueue.size()); - mService.isSystemUid = false; - setToastRateIsWithinQuota(false); // rate limit reached - // Avoids rate limiting. - setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true); - - // package is not suspended - when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) - .thenReturn(false); - - setAppInForegroundForToasts(mUid, true); - - Binder token = new Binder(); - ITransientNotification callback = mock(ITransientNotification.class); - INotificationManager nmService = (INotificationManager) mService.mService; - - nmService.enqueueToast(testPackage, token, callback, 2000, 0); - verify(callback).show(any()); + verify(callback, times(1)).show(any()); } @Test @@ -5206,12 +5184,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testToastRateLimiterCanPreventShowCallForTextToast() throws Exception { + public void testToastRateLimiterCanPreventShowCallForTextToast_whenInBackground() + throws Exception { final String testPackage = "testPackageName"; assertEquals(0, mService.mToastQueue.size()); mService.isSystemUid = false; setToastRateIsWithinQuota(false); // rate limit reached setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false); + setAppInForegroundForToasts(mUid, false); // package is not suspended when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) @@ -5226,12 +5206,35 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testToastRateLimiterWontPreventShowCallForTextToast_whenInForeground() + throws Exception { + final String testPackage = "testPackageName"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = false; + setToastRateIsWithinQuota(false); // rate limit reached + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false); + setAppInForegroundForToasts(mUid, true); + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) + .thenReturn(false); + + Binder token = new Binder(); + INotificationManager nmService = (INotificationManager) mService.mService; + + nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null); + verify(mStatusBar, times(1)) + .showToast(anyInt(), any(), any(), any(), any(), anyInt(), any()); + } + + @Test public void testTextToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception { final String testPackage = "testPackageName"; assertEquals(0, mService.mToastQueue.size()); mService.isSystemUid = false; setToastRateIsWithinQuota(false); // rate limit reached setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true); + setAppInForegroundForToasts(mUid, false); // package is not suspended when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 0afd39f1ed01..2f5235249168 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1971,7 +1971,8 @@ public class DisplayContentTests extends WindowTestsBase { // test misc display overrides assertEquals(ignoreOrientationRequests, testDisplayContent.mIgnoreOrientationRequest); - assertEquals(fixedOrientationLetterboxRatio, mWm.getFixedOrientationLetterboxAspectRatio(), + assertEquals(fixedOrientationLetterboxRatio, + mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(), 0 /* delta */); } @@ -2011,7 +2012,8 @@ public class DisplayContentTests extends WindowTestsBase { // test misc display overrides assertEquals(ignoreOrientationRequests, testDisplayContent.mIgnoreOrientationRequest); - assertEquals(fixedOrientationLetterboxRatio, mWm.getFixedOrientationLetterboxAspectRatio(), + assertEquals(fixedOrientationLetterboxRatio, + mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(), 0 /* delta */); } 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 7c2cfab50821..95b74430e4ab 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -335,11 +335,11 @@ public class SizeCompatTests extends WindowTestsBase { final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "window"); assertEquals(window, mActivity.findMainWindow()); - assertTrue(mActivity.isLetterboxed(mActivity.findMainWindow())); + assertTrue(mActivity.mLetterboxUiController.isLetterboxed(mActivity.findMainWindow())); window.mAttrs.flags |= FLAG_SHOW_WALLPAPER; - assertFalse(mActivity.isLetterboxed(mActivity.findMainWindow())); + assertFalse(mActivity.mLetterboxUiController.isLetterboxed(mActivity.findMainWindow())); } @Test @@ -1023,7 +1023,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed // orientation letterbox. - mActivity.mWmService.setFixedOrientationLetterboxAspectRatio(1.1f); + mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); mActivity.info.setMinAspectRatio(3); prepareUnresizable(mActivity, /* maxAspect= */ 0, SCREEN_ORIENTATION_PORTRAIT); @@ -1054,7 +1054,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app with max aspect ratio lower that aspect ratio override for fixed // orientation letterbox. - mActivity.mWmService.setFixedOrientationLetterboxAspectRatio(3); + mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(3); prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT); final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); @@ -1085,7 +1085,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed // orientation letterbox. final float fixedOrientationLetterboxAspectRatio = 1.1f; - mActivity.mWmService.setFixedOrientationLetterboxAspectRatio( + mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio( fixedOrientationLetterboxAspectRatio); prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); @@ -1515,6 +1515,129 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(primarySplitBounds, letterboxedBounds); } + @Test + public void testUpdateResolvedBoundsHorizontalPosition_left() { + // Display configured as (2800, 1400). + assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + /* letterboxHorizontalPositionMultiplier */ 0.0f, + // At launch. + /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400), + // After 90 degree rotation. + /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400), + // After the display is resized to (700, 1400). + /* sizeCompatScaled */ new Rect(0, 0, 350, 700)); + } + + @Test + public void testUpdateResolvedBoundsHorizontalPosition_center() { + // Display configured as (2800, 1400). + assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + /* letterboxHorizontalPositionMultiplier */ 0.5f, + // At launch. + /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), + // After 90 degree rotation. + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + // After the display is resized to (700, 1400). + /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); + } + + @Test + public void testUpdateResolvedBoundsHorizontalPosition_invalidMultiplier_defaultToCenter() { + // Display configured as (2800, 1400). + + // Below 0.0. + assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + /* letterboxHorizontalPositionMultiplier */ -1.0f, + // At launch. + /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), + // After 90 degree rotation. + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + // After the display is resized to (700, 1400). + /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); + + // Above 1.0 + assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + /* letterboxHorizontalPositionMultiplier */ 2.0f, + // At launch. + /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), + // After 90 degree rotation. + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + // After the display is resized to (700, 1400). + /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); + } + + @Test + public void testUpdateResolvedBoundsHorizontalPosition_right() { + // Display configured as (2800, 1400). + assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + /* letterboxHorizontalPositionMultiplier */ 1.0f, + // At launch. + /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400), + // After 90 degree rotation. + /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400), + // After the display is resized to (700, 1400). + /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700)); + } + + private void assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + float letterboxHorizontalPositionMultiplier, Rect fixedOrientationLetterbox, + Rect sizeCompatUnscaled, Rect sizeCompatScaled) { + // Set up a display in landscape and ignoring orientation request. + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( + letterboxHorizontalPositionMultiplier); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + assertEquals(fixedOrientationLetterbox, mActivity.getBounds()); + + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + assertTrue(mActivity.inSizeCompatMode()); + // Activity is in size compat mode but not scaled. + assertEquals(sizeCompatUnscaled, mActivity.getBounds()); + + // Force activity to scaled down for size compat mode. + resizeDisplay(mTask.mDisplayContent, 700, 1400); + + assertTrue(mActivity.inSizeCompatMode()); + assertScaled(); + assertEquals(sizeCompatScaled, mActivity.getBounds()); + } + + @Test + public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() { + // When activity width equals parent width, multiplier shouldn't have any effect. + assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( + /* letterboxHorizontalPositionMultiplier */ 0.0f); + assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( + /* letterboxHorizontalPositionMultiplier */ 0.5f); + assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( + /* letterboxHorizontalPositionMultiplier */ 1.0f); + } + + private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( + float letterboxHorizontalPositionMultiplier) { + // Set up a display in landscape and ignoring orientation request. + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( + letterboxHorizontalPositionMultiplier); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + + assertFitted(); + + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + assertTrue(mActivity.inSizeCompatMode()); + // Activity is in size compat mode but not scaled. + assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds()); + } + private static WindowState addWindowToActivity(ActivityRecord activity) { final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java index f3616da6c102..619aee6eb919 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java +++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java @@ -17,6 +17,8 @@ package com.android.server.wm; import android.annotation.NonNull; +import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.Region; @@ -253,4 +255,14 @@ public class StubTransaction extends SurfaceControl.Transaction { public SurfaceControl.Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) { return this; } + + @Override + public SurfaceControl.Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) { + return this; + } + + @Override + public SurfaceControl.Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) { + return this; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 5bafbbd2bdf7..2d4e4efd74ea 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -101,6 +101,7 @@ import android.window.TransitionRequestInfo; import com.android.internal.policy.AttributeCache; import com.android.internal.util.ArrayUtils; +import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.runner.Description; @@ -207,11 +208,26 @@ class WindowTestsBase extends SystemServiceTestsBase { // Ensure letterbox aspect ratio is not overridden on any device target. // {@link com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}, is set // on some device form factors. - mAtm.mWindowManager.setFixedOrientationLetterboxAspectRatio(0); + mAtm.mWindowManager.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(0); + // Ensure letterbox position multiplier is not overridden on any device target. + // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}, + // may be set on some device form factors. + mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f); checkDeviceSpecificOverridesNotApplied(); } + @After + public void tearDown() throws Exception { + // Revert back to device overrides. + mAtm.mWindowManager.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio( + mContext.getResources().getFloat( + com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio)); + mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( + mContext.getResources().getFloat( + com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier)); + } + /** * Check that device-specific overrides are not applied. Only need to check once during entire * test run for each case: global overrides, default display, and test display. @@ -219,7 +235,8 @@ class WindowTestsBase extends SystemServiceTestsBase { private void checkDeviceSpecificOverridesNotApplied() { // Check global overrides if (!sGlobalOverridesChecked) { - assertEquals(0, mWm.getFixedOrientationLetterboxAspectRatio(), 0 /* delta */); + assertEquals(0, mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(), + 0 /* delta */); sGlobalOverridesChecked = true; } // Check display-specific overrides diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 2e692e6e68f7..7f24c365237d 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -2139,6 +2139,12 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", " + "forceRestart=" + forceRestart); } + if (mCurrentGadgetHalVersion < UsbManager.GADGET_HAL_V1_2) { + if ((functions & UsbManager.FUNCTION_NCM) != 0) { + Slog.e(TAG, "Could not set unsupported function for the GadgetHal"); + return; + } + } if (mCurrentFunctions != functions || !mCurrentFunctionsApplied || forceRestart) { diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index 32533cbf97fb..0d6cd5a30e8c 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -760,6 +760,7 @@ public final class SmsApplication { private static void assignExclusiveSmsPermissionsToSystemApp(Context context, PackageManager packageManager, AppOpsManager appOps, String packageName, boolean sigatureMatch) { + if (packageName == null) return; // First check package signature matches the caller's package signature. // Since this class is only used internally by the system, this check makes sure // the package signature matches system signature. diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java index bde62fb2977c..ac01afa51729 100644 --- a/telephony/java/android/telephony/CellSignalStrengthNr.java +++ b/telephony/java/android/telephony/CellSignalStrengthNr.java @@ -134,7 +134,7 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa * * Range [0, 15] for each CQI. */ - private List<Integer> mCsiCqiReport;; + private List<Integer> mCsiCqiReport; private int mSsRsrp; private int mSsRsrq; private int mSsSinr; @@ -172,13 +172,13 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa * @hide */ public CellSignalStrengthNr(int csiRsrp, int csiRsrq, int csiSinr, int csiCqiTableIndex, - List<Integer> csiCqiReport, int ssRsrp, int ssRsrq, int ssSinr) { + List<Byte> csiCqiReport, int ssRsrp, int ssRsrq, int ssSinr) { mCsiRsrp = inRangeOrUnavailable(csiRsrp, -140, -44); mCsiRsrq = inRangeOrUnavailable(csiRsrq, -20, -3); mCsiSinr = inRangeOrUnavailable(csiSinr, -23, 23); mCsiCqiTableIndex = inRangeOrUnavailable(csiCqiTableIndex, 1, 3); - mCsiCqiReport = csiCqiReport.stream() - .map(cqi -> new Integer(inRangeOrUnavailable(cqi.intValue(), 1, 3))) + mCsiCqiReport = csiCqiReport.stream() + .map(cqi -> new Integer(inRangeOrUnavailable(Byte.toUnsignedInt(cqi), 1, 3))) .collect(Collectors.toList()); mSsRsrp = inRangeOrUnavailable(ssRsrp, -140, -44); mSsRsrq = inRangeOrUnavailable(ssRsrq, -43, 20); diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index c8ed82cd2a3f..0539897de33a 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -1055,6 +1055,20 @@ public final class DataFailCause { */ public static final int HANDOVER_FAILED = 0x10006; + /** + * Enterprise setup failure: duplicate CID in DataCallResponse. + * + * @hide + */ + public static final int DUPLICATE_CID = 0x10007; + + /** + * Enterprise setup failure: no default data connection set up yet. + * + * @hide + */ + public static final int NO_DEFAULT_DATA = 0x10008; + private static final Map<Integer, String> sFailCauseMap; static { sFailCauseMap = new HashMap<>(); @@ -1481,6 +1495,9 @@ public final class DataFailCause { sFailCauseMap.put(UNACCEPTABLE_NETWORK_PARAMETER, "UNACCEPTABLE_NETWORK_PARAMETER"); sFailCauseMap.put(LOST_CONNECTION, "LOST_CONNECTION"); + sFailCauseMap.put(HANDOVER_FAILED, "HANDOVER_FAILED"); + sFailCauseMap.put(DUPLICATE_CID, "DUPLICATE_CID"); + sFailCauseMap.put(NO_DEFAULT_DATA, "NO_DEFAULT_DATA"); } private DataFailCause() { @@ -1580,6 +1597,7 @@ public final class DataFailCause { add(RADIO_NOT_AVAILABLE); add(UNACCEPTABLE_NETWORK_PARAMETER); add(SIGNAL_LOST); + add(DUPLICATE_CID); } }; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f0771bedc838..c8e5e56031bc 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4885,8 +4885,9 @@ public class TelephonyManager { * Return the set of IMSIs that should be considered "merged together" for data usage * purposes. This API merges IMSIs based on subscription grouping: IMSI of those in the same * group will all be returned. - * Return the current IMSI if there is no subscription group. See - * {@link SubscriptionManager#createSubscriptionGroup(List)} for the definition of a group. + * Return the current IMSI if there is no subscription group, see + * {@link SubscriptionManager#createSubscriptionGroup(List)} for the definition of a group, + * otherwise return an empty array if there is a failure. * * @hide */ diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java index 6c1c653f13d0..5642549d7313 100644 --- a/telephony/java/android/telephony/data/QosBearerFilter.java +++ b/telephony/java/android/telephony/data/QosBearerFilter.java @@ -31,7 +31,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; - /** * Class that stores QOS filter parameters as defined in * 3gpp 24.008 10.5.6.12 and 3gpp 24.501 9.11.4.13. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 059361525733..a540dffb3c9c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -23,7 +23,7 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_LAYER_NAME import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_WINDOW_NAME -val LAUNCHER_TITLE = arrayOf("Wallpaper", "Launcher", "com.google.android.googlequicksearchbox") +val HOME_WINDOW_TITLE = arrayOf("Wallpaper", "Launcher") fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() { assertWm { @@ -41,23 +41,23 @@ fun FlickerTestParameter.launcherReplacesAppWindowAsTopWindow(testApp: IAppHelpe assertWm { this.showsAppWindowOnTop(testApp.getPackage()) .then() - .showsAppWindowOnTop(*LAUNCHER_TITLE) + .showsAppWindowOnTop(*HOME_WINDOW_TITLE) } } fun FlickerTestParameter.launcherWindowBecomesVisible() { assertWm { - this.hidesBelowAppWindow(*LAUNCHER_TITLE) + this.hidesBelowAppWindow(*HOME_WINDOW_TITLE) .then() - .showsBelowAppWindow(*LAUNCHER_TITLE) + .showsBelowAppWindow(*HOME_WINDOW_TITLE) } } fun FlickerTestParameter.launcherWindowBecomesInvisible() { assertWm { - this.showsBelowAppWindow(*LAUNCHER_TITLE) + this.showsBelowAppWindow(*HOME_WINDOW_TITLE) .then() - .hidesBelowAppWindow(*LAUNCHER_TITLE) + .hidesBelowAppWindow(*HOME_WINDOW_TITLE) } } @@ -179,7 +179,7 @@ fun FlickerTestParameter.statusBarLayerRotatesScales( fun FlickerTestParameter.appLayerReplacesLauncher(appName: String) { assertLayers { - this.isVisible(*LAUNCHER_TITLE) + this.isVisible(*HOME_WINDOW_TITLE) .then() .isVisible(appName) } @@ -190,7 +190,7 @@ fun FlickerTestParameter.launcherLayerReplacesApp(testApp: IAppHelper) { this.isVisible(testApp.getPackage()) .then() .isInvisible(testApp.getPackage()) - .isVisible(*LAUNCHER_TITLE) + .isVisible(*HOME_WINDOW_TITLE) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt index 6a7309c4c120..01e34d9f8f97 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt @@ -18,11 +18,11 @@ package com.android.server.wm.flicker.launch import android.platform.helpers.IAppHelper import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.LAUNCHER_TITLE +import com.android.server.wm.flicker.HOME_WINDOW_TITLE fun FlickerTestParameter.appWindowReplacesLauncherAsTopWindow(testApp: IAppHelper) { assertWm { - this.showsAppWindowOnTop(*LAUNCHER_TITLE) + this.showsAppWindowOnTop(*HOME_WINDOW_TITLE) .then() .showsAppWindowOnTop("Snapshot", testApp.getPackage()) } diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java index f6d9a7301949..487c8566ce37 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java @@ -56,6 +56,7 @@ public class RippleActivity extends Activity { CanvasProperty<Float> mY; CanvasProperty<Float> mRadius; CanvasProperty<Float> mProgress; + CanvasProperty<Float> mNoisePhase; CanvasProperty<Paint> mPaint; RuntimeShader mRuntimeShader; @@ -99,6 +100,7 @@ public class RippleActivity extends Activity { mY = CanvasProperty.createFloat(200.0f); mRadius = CanvasProperty.createFloat(150.0f); mProgress = CanvasProperty.createFloat(0.0f); + mNoisePhase = CanvasProperty.createFloat(0.0f); Paint p = new Paint(); p.setAntiAlias(true); @@ -115,7 +117,8 @@ public class RippleActivity extends Activity { if (canvas.isHardwareAccelerated()) { RecordingCanvas recordingCanvas = (RecordingCanvas) canvas; - recordingCanvas.drawRipple(mX, mY, mRadius, mPaint, mProgress, mRuntimeShader); + recordingCanvas.drawRipple(mX, mY, mRadius, mPaint, mProgress, mNoisePhase, + mRuntimeShader); } } @@ -141,6 +144,9 @@ public class RippleActivity extends Activity { mProgress, mToggle ? 1.0f : 0.0f)); mRunningAnimations.add(new RenderNodeAnimator( + mNoisePhase, DURATION)); + + mRunningAnimations.add(new RenderNodeAnimator( mPaint, RenderNodeAnimator.PAINT_ALPHA, 64.0f)); // Will be "chained" to run after the above diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index 6d852bf0387a..9b7458397cf5 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -312,7 +312,7 @@ public class NetworkCapabilitiesTest { .addCapability(NET_CAPABILITY_EIMS) .addCapability(NET_CAPABILITY_NOT_METERED); if (isAtLeastS()) { - netCap.setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2)); + netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2)); netCap.setUids(uids); } if (isAtLeastR()) { @@ -642,16 +642,16 @@ public class NetworkCapabilitiesTest { assertTrue(nc2.appliesToUid(22)); // Verify the subscription id list can be combined only when they are equal. - nc1.setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2)); - nc2.setSubIds(Set.of(TEST_SUBID2)); + nc1.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2)); + nc2.setSubscriptionIds(Set.of(TEST_SUBID2)); assertThrows(IllegalStateException.class, () -> nc2.combineCapabilities(nc1)); - nc2.setSubIds(Set.of()); + nc2.setSubscriptionIds(Set.of()); assertThrows(IllegalStateException.class, () -> nc2.combineCapabilities(nc1)); - nc2.setSubIds(Set.of(TEST_SUBID2, TEST_SUBID1)); + nc2.setSubscriptionIds(Set.of(TEST_SUBID2, TEST_SUBID1)); nc2.combineCapabilities(nc1); - assertEquals(Set.of(TEST_SUBID2, TEST_SUBID1), nc2.getSubIds()); + assertEquals(Set.of(TEST_SUBID2, TEST_SUBID1), nc2.getSubscriptionIds()); } } @@ -806,20 +806,20 @@ public class NetworkCapabilitiesTest { assertEquals(nc1, nc2); if (isAtLeastS()) { - assertThrows(NullPointerException.class, () -> nc1.setSubIds(null)); - nc1.setSubIds(Set.of()); + assertThrows(NullPointerException.class, () -> nc1.setSubscriptionIds(null)); + nc1.setSubscriptionIds(Set.of()); nc2.set(nc1); assertEquals(nc1, nc2); - nc1.setSubIds(Set.of(TEST_SUBID1)); + nc1.setSubscriptionIds(Set.of(TEST_SUBID1)); nc2.set(nc1); assertEquals(nc1, nc2); - nc2.setSubIds(Set.of(TEST_SUBID2, TEST_SUBID1)); + nc2.setSubscriptionIds(Set.of(TEST_SUBID2, TEST_SUBID1)); nc2.set(nc1); assertEquals(nc1, nc2); - nc2.setSubIds(Set.of(TEST_SUBID3, TEST_SUBID2)); + nc2.setSubscriptionIds(Set.of(TEST_SUBID3, TEST_SUBID2)); assertNotEquals(nc1, nc2); } } @@ -908,8 +908,8 @@ public class NetworkCapabilitiesTest { // satisfy these requests. final NetworkCapabilities nc = new NetworkCapabilities.Builder() .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) - .setSubIds(new ArraySet<>(subIds)).build(); - assertEquals(new ArraySet<>(subIds), nc.getSubIds()); + .setSubscriptionIds(new ArraySet<>(subIds)).build(); + assertEquals(new ArraySet<>(subIds), nc.getSubscriptionIds()); return nc; } @@ -921,11 +921,11 @@ public class NetworkCapabilitiesTest { final NetworkCapabilities ncWithoutRequestedIds = capsWithSubIds(TEST_SUBID3); final NetworkRequest requestWithoutId = new NetworkRequest.Builder().build(); - assertEmpty(requestWithoutId.networkCapabilities.getSubIds()); + assertEmpty(requestWithoutId.networkCapabilities.getSubscriptionIds()); final NetworkRequest requestWithIds = new NetworkRequest.Builder() - .setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2)).build(); + .setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2)).build(); assertEquals(Set.of(TEST_SUBID1, TEST_SUBID2), - requestWithIds.networkCapabilities.getSubIds()); + requestWithIds.networkCapabilities.getSubscriptionIds()); assertFalse(requestWithIds.canBeSatisfiedBy(ncWithoutId)); assertTrue(requestWithIds.canBeSatisfiedBy(ncWithOtherIds)); @@ -1138,8 +1138,8 @@ public class NetworkCapabilitiesTest { if (isAtLeastS()) { final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() - .setSubIds(Set.of(TEST_SUBID1)).build(); - assertEquals(Set.of(TEST_SUBID1), nc2.getSubIds()); + .setSubscriptionIds(Set.of(TEST_SUBID1)).build(); + assertEquals(Set.of(TEST_SUBID1), nc2.getSubscriptionIds()); } } } diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java index 6cbdd258c00a..19f884346e6f 100644 --- a/tests/net/java/android/net/ConnectivityManagerTest.java +++ b/tests/net/java/android/net/ConnectivityManagerTest.java @@ -384,7 +384,7 @@ public class ConnectivityManagerTest { eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); - manager.requestBackgroundNetwork(request, handler, callback); + manager.requestBackgroundNetwork(request, callback, handler); verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities), eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 1b0a500c6ac6..b71be596d9c5 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -2668,25 +2668,6 @@ public class ConnectivityServiceTest { assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - // Bring up wifi with a score of 70. - // Cell is lingered because it would not satisfy any request, even if it validated. - mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - mWiFiNetworkAgent.adjustScore(50); - mWiFiNetworkAgent.connect(false); // Score: 70 - callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); - defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - - // Tear down wifi. - mWiFiNetworkAgent.disconnect(); - callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); - defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); - assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but // it's arguably correct to linger it, since it was the default network before it validated. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); @@ -2991,11 +2972,17 @@ public class ConnectivityServiceTest { callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - // BUG: the network will no longer linger, even though it's validated and outscored. - // TODO: fix this. mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); mEthernetNetworkAgent.connect(true); + // BUG: with the legacy int-based scoring code, the network will no longer linger, even + // though it's validated and outscored. + // The new policy-based scoring code fixes this. + // TODO: remove the line below and replace with the three commented lines when + // the policy-based scoring code is turned on. callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); + // callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); + // callback.expectCallback(CallbackEntry.LOSING, mWiFiNetworkAgent); + // callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); callback.assertNoCallback(); @@ -4313,7 +4300,7 @@ public class ConnectivityServiceTest { final TestNetworkCallback cellBgCallback = new TestNetworkCallback(); mCm.requestBackgroundNetwork(new NetworkRequest.Builder() .addTransportType(TRANSPORT_CELLULAR).build(), - mCsHandlerThread.getThreadHandler(), cellBgCallback); + cellBgCallback, mCsHandlerThread.getThreadHandler()); // Make callbacks for monitoring. final NetworkRequest request = new NetworkRequest.Builder().build(); @@ -12602,12 +12589,12 @@ public class ConnectivityServiceTest { public void testSubIdsClearedWithoutNetworkFactoryPermission() throws Exception { mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED); final NetworkCapabilities nc = new NetworkCapabilities(); - nc.setSubIds(Collections.singleton(Process.myUid())); + nc.setSubscriptionIds(Collections.singleton(Process.myUid())); final NetworkCapabilities result = mService.networkCapabilitiesRestrictedForCallerPermissions( nc, Process.myPid(), Process.myUid()); - assertTrue(result.getSubIds().isEmpty()); + assertTrue(result.getSubscriptionIds().isEmpty()); } @Test @@ -12616,17 +12603,17 @@ public class ConnectivityServiceTest { final Set<Integer> subIds = Collections.singleton(Process.myUid()); final NetworkCapabilities nc = new NetworkCapabilities(); - nc.setSubIds(subIds); + nc.setSubscriptionIds(subIds); final NetworkCapabilities result = mService.networkCapabilitiesRestrictedForCallerPermissions( nc, Process.myPid(), Process.myUid()); - assertEquals(subIds, result.getSubIds()); + assertEquals(subIds, result.getSubscriptionIds()); } private NetworkRequest getRequestWithSubIds() { return new NetworkRequest.Builder() - .setSubIds(Collections.singleton(Process.myUid())) + .setSubscriptionIds(Collections.singleton(Process.myUid())) .build(); } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 9a663436f983..907cb46d2929 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -696,7 +696,7 @@ public class VcnManagementServiceTest { .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .addTransportType(transport); if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - ncBuilder.setSubIds(Collections.singleton(subId)); + ncBuilder.setSubscriptionIds(Collections.singleton(subId)); } return ncBuilder; diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java index b592000e38f9..8289e85dadf9 100644 --- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java @@ -153,21 +153,19 @@ public class UnderlyingNetworkTrackerTest { verify(mConnectivityManager) .requestBackgroundNetwork( eq(getWifiRequest(expectedSubIds)), - any(), - any(NetworkBringupCallback.class)); + any(NetworkBringupCallback.class), + any()); for (final int subId : expectedSubIds) { verify(mConnectivityManager) .requestBackgroundNetwork( eq(getCellRequestForSubId(subId)), - any(), - any(NetworkBringupCallback.class)); + any(NetworkBringupCallback.class), any()); } verify(mConnectivityManager) .requestBackgroundNetwork( eq(getRouteSelectionRequest(expectedSubIds)), - any(), - any(RouteSelectionCallback.class)); + any(RouteSelectionCallback.class), any()); } @Test @@ -191,7 +189,7 @@ public class UnderlyingNetworkTrackerTest { private NetworkRequest getWifiRequest(Set<Integer> netCapsSubIds) { return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubIds(netCapsSubIds) + .setSubscriptionIds(netCapsSubIds) .build(); } @@ -203,7 +201,7 @@ public class UnderlyingNetworkTrackerTest { } private NetworkRequest getRouteSelectionRequest(Set<Integer> netCapsSubIds) { - return getExpectedRequestBase().setSubIds(netCapsSubIds).build(); + return getExpectedRequestBase().setSubscriptionIds(netCapsSubIds).build(); } private NetworkRequest.Builder getExpectedRequestBase() { @@ -267,8 +265,8 @@ public class UnderlyingNetworkTrackerTest { verify(mConnectivityManager) .requestBackgroundNetwork( eq(getRouteSelectionRequest(INITIAL_SUB_IDS)), - any(), - mRouteSelectionCallbackCaptor.capture()); + mRouteSelectionCallbackCaptor.capture(), + any()); RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue(); cb.onAvailable(mNetwork); diff --git a/tools/hiddenapi/checksorted_sha.sh b/tools/hiddenapi/checksorted_sha.sh deleted file mode 100755 index 72fb86737488..000000000000 --- a/tools/hiddenapi/checksorted_sha.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -e -LOCAL_DIR="$( dirname ${BASH_SOURCE} )" -git show --name-only --pretty=format: $1 | grep "hiddenapi/hiddenapi-.*txt" | while read file; do - diff <(git show $1:$file) <(git show $1:$file | $LOCAL_DIR/sort_api.sh ) || { - echo -e "\e[1m\e[31m$file $1 is not sorted or contains duplicates. To sort it correctly:\e[0m" - echo -e "\e[33m${LOCAL_DIR}/sort_api.sh $PWD/$file\e[0m" - exit 1 - } -done diff --git a/tools/hiddenapi/sort_api.sh b/tools/hiddenapi/sort_api.sh deleted file mode 100755 index 710da40585ac..000000000000 --- a/tools/hiddenapi/sort_api.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -set -e -if [ -z "$1" ]; then - source_list=/dev/stdin - dest_list=/dev/stdout -else - source_list="$1" - dest_list="$1" -fi -# Load the file -readarray A < "$source_list" -# Sort -IFS=$'\n' -# Stash away comments -C=( $(grep -E '^#' <<< "${A[*]}" || :) ) -A=( $(grep -v -E '^#' <<< "${A[*]}" || :) ) -# Sort entries -A=( $(LC_COLLATE=C sort -f <<< "${A[*]}") ) -A=( $(uniq <<< "${A[*]}") ) -# Concatenate comments and entries -A=( ${C[*]} ${A[*]} ) -unset IFS -# Dump array back into the file -if [ ${#A[@]} -ne 0 ]; then - printf '%s\n' "${A[@]}" > "$dest_list" -fi diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java index da0571ba88e1..3b7566051fae 100644 --- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -117,13 +117,13 @@ public class WifiNl80211Manager { /** * Interface used to listen country code event */ - public interface CountryCodeChangeListener { + public interface CountryCodeChangedListener { /** * Called when country code changed. * - * @param countryCode A new country code which is 2-Character alphanumeric. + * @param countryCode An ISO-3166-alpha2 country code which is 2-Character alphanumeric. */ - void onChanged(@NonNull String countryCode); + void onCountryCodeChanged(@NonNull String countryCode); } /** @@ -163,27 +163,27 @@ public class WifiNl80211Manager { /** @hide */ @VisibleForTesting public class WificondEventHandler extends IWificondEventCallback.Stub { - private Map<CountryCodeChangeListener, Executor> mCountryCodeChangeListenerHolder = + private Map<CountryCodeChangedListener, Executor> mCountryCodeChangedListenerHolder = new HashMap<>(); /** - * Register CountryCodeChangeListener with pid. + * Register CountryCodeChangedListener with pid. * * @param executor The Executor on which to execute the callbacks. * @param listener listener for country code changed events. */ - public void registerCountryCodeChangeListener(Executor executor, - CountryCodeChangeListener listener) { - mCountryCodeChangeListenerHolder.put(listener, executor); + public void registerCountryCodeChangedListener(Executor executor, + CountryCodeChangedListener listener) { + mCountryCodeChangedListenerHolder.put(listener, executor); } /** - * Unregister CountryCodeChangeListener with pid. + * Unregister CountryCodeChangedListener with pid. * * @param listener listener which registered country code changed events. */ - public void unregisterCountryCodeChangeListener(CountryCodeChangeListener listener) { - mCountryCodeChangeListenerHolder.remove(listener); + public void unregisterCountryCodeChangedListener(CountryCodeChangedListener listener) { + mCountryCodeChangedListenerHolder.remove(listener); } @Override @@ -191,8 +191,8 @@ public class WifiNl80211Manager { Log.d(TAG, "OnRegDomainChanged " + countryCode); final long token = Binder.clearCallingIdentity(); try { - mCountryCodeChangeListenerHolder.forEach((listener, executor) -> { - executor.execute(() -> listener.onChanged(countryCode)); + mCountryCodeChangedListenerHolder.forEach((listener, executor) -> { + executor.execute(() -> listener.onCountryCodeChanged(countryCode)); }); } finally { Binder.restoreCallingIdentity(token); @@ -1240,25 +1240,25 @@ public class WifiNl80211Manager { * @param listener listener for country code changed events. * @return true on success, false on failure. */ - public boolean registerCountryCodeChangeListener(@NonNull @CallbackExecutor Executor executor, - @NonNull CountryCodeChangeListener listener) { + public boolean registerCountryCodeChangedListener(@NonNull @CallbackExecutor Executor executor, + @NonNull CountryCodeChangedListener listener) { if (!retrieveWificondAndRegisterForDeath()) { return false; } Log.d(TAG, "registerCountryCodeEventListener called"); - mWificondEventHandler.registerCountryCodeChangeListener(executor, listener); + mWificondEventHandler.registerCountryCodeChangedListener(executor, listener); return true; } /** - * Unregister CountryCodeChangeListener with pid. + * Unregister CountryCodeChangedListener with pid. * * @param listener listener which registered country code changed events. */ - public void unregisterCountryCodeChangeListener(@NonNull CountryCodeChangeListener listener) { + public void unregisterCountryCodeChangedListener(@NonNull CountryCodeChangedListener listener) { Log.d(TAG, "unregisterCountryCodeEventListener called"); - mWificondEventHandler.unregisterCountryCodeChangeListener(listener); + mWificondEventHandler.unregisterCountryCodeChangedListener(listener); } /** diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java index 98a0042a7096..3fb23013bdec 100644 --- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java +++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java @@ -98,9 +98,9 @@ public class WifiNl80211ManagerTest { @Mock private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback; @Mock - private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener; + private WifiNl80211Manager.CountryCodeChangedListener mCountryCodeChangedListener; @Mock - private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener2; + private WifiNl80211Manager.CountryCodeChangedListener mCountryCodeChangedListener2; @Mock private Context mContext; private TestLooper mLooper; @@ -768,25 +768,25 @@ public class WifiNl80211ManagerTest { } /** - * Ensures callback works after register CountryCodeChangeListener. + * Ensures callback works after register CountryCodeChangedListener. */ @Test - public void testCountryCodeChangeListenerInvocation() throws Exception { - assertTrue(mWificondControl.registerCountryCodeChangeListener( - Runnable::run, mCountryCodeChangeListener)); - assertTrue(mWificondControl.registerCountryCodeChangeListener( - Runnable::run, mCountryCodeChangeListener2)); + public void testCountryCodeChangedListenerInvocation() throws Exception { + assertTrue(mWificondControl.registerCountryCodeChangedListener( + Runnable::run, mCountryCodeChangedListener)); + assertTrue(mWificondControl.registerCountryCodeChangedListener( + Runnable::run, mCountryCodeChangedListener2)); mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE); - verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE); - verify(mCountryCodeChangeListener2).onChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangedListener).onCountryCodeChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangedListener2).onCountryCodeChanged(TEST_COUNTRY_CODE); - reset(mCountryCodeChangeListener); - reset(mCountryCodeChangeListener2); - mWificondControl.unregisterCountryCodeChangeListener(mCountryCodeChangeListener2); + reset(mCountryCodeChangedListener); + reset(mCountryCodeChangedListener2); + mWificondControl.unregisterCountryCodeChangedListener(mCountryCodeChangedListener2); mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE); - verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE); - verify(mCountryCodeChangeListener2, never()).onChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangedListener).onCountryCodeChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangedListener2, never()).onCountryCodeChanged(TEST_COUNTRY_CODE); } /** |