Merge "Fix notigication group background color" into sc-dev
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 175fb38..65b2511 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -22,7 +22,7 @@
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 23dc720..bc3f131 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -96,6 +96,8 @@
],
api_levels_annotations_enabled: false,
filter_packages: packages_to_document,
+ defaults_visibility: ["//visibility:private"],
+ visibility: ["//frameworks/base/api"],
}
/////////////////////////////////////////////////////////////////////
@@ -352,6 +354,8 @@
tag: ".jar",
dest: "android-non-updatable.jar",
},
+ defaults_visibility: ["//visibility:private"],
+ visibility: ["//visibility:private"],
}
java_library_static {
@@ -405,6 +409,8 @@
system_modules: "none",
java_version: "1.8",
compile_dex: true,
+ defaults_visibility: ["//visibility:private"],
+ visibility: ["//visibility:public"],
}
java_defaults {
@@ -417,6 +423,7 @@
tag: ".jar",
dest: "android.jar",
},
+ defaults_visibility: ["//frameworks/base/services"],
}
java_library_static {
@@ -516,6 +523,7 @@
"metalava-manual",
],
args: priv_apps,
+ visibility: ["//visibility:private"],
}
java_library_static {
@@ -525,4 +533,5 @@
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 35cea3e..6c62426 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 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 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 @@
@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 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 ac91bdb..c85c4c3 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 @@
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 @@
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 @@
request.getProjectionsInternal(),
mUserId,
new IAppSearchBatchResultCallback.Stub() {
+ @Override
public void onResult(AppSearchBatchResult result) {
executor.execute(() -> {
AppSearchBatchResult.Builder<String, GenericDocument>
@@ -359,8 +362,9 @@
});
}
- 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 @@
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 @@
}
});
}
+
+ /**
+ * 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 49049b6..28f8a7a 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 @@
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 b1bbd18..64b331e 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 27729a5..299c9957 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 c3f1978..f6f5c98 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.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.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.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 @@
// 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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
}
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 @@
/**
* 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 c1b8294..4de52fb 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.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.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -333,9 +333,9 @@
@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 @@
/** 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 1c04d99..731ab35 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 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.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
+import java.util.Objects;
import java.util.Random;
/**
@@ -162,15 +162,15 @@
* 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 @@
/** 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 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 f0de496..6193367 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 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 @@
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 5042ce0..c35849d 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 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 @@
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 5f26e8c..72078f8 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.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 @@
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 31da201..03d9a96 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 @@
}
}
- 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 @@
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 @@
+ " does not belong to the calling uid " + callingUid);
}
- final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
- final boolean exact = (windowLength == AlarmManager.WINDOW_EXACT);
+ // Repeating alarms must use PendingIntent, not direct listener
+ if (interval != 0 && directReceiver != null) {
+ throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers");
+ }
- // make sure the caller is allowed to use the requested kind of alarm, and also
+ 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 == 0);
+
+ // 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 @@
}
}
- // 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 c37d2c3..9b1b066 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 d94d638..131783f 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
}
}
mWorkCountTracker.resetStagingCount();
+ mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
noteConcurrency();
}
@@ -702,18 +753,66 @@
}
@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 @@
@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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 d72f565b..1815661 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 @@
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 @@
}
/** 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 999c53f..12d9c7f 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.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 @@
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 @@
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 @@
}
@Override
+ @GuardedBy("mLock")
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
updateComponentEnabledStateLocked(jobStatus);
}
@@ -108,30 +126,53 @@
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) {
- Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName());
- return null;
- }
- mServiceInfoCache.add(userId, service, si);
+ 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());
+ }
+ // 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 @@
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 @@
}
}
+ @GuardedBy("mLock")
private void updateComponentStatesLocked(@NonNull Predicate<JobStatus> filter) {
mComponentStateUpdateFunctor.reset();
mService.getJobStore().forEachJob(filter, mComponentStateUpdateFunctor);
@@ -178,24 +226,40 @@
}
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 bebf019..b7d7ed8 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -10,7 +10,6 @@
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 3f30d3e..97fa0ec 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -242,7 +242,7 @@
}
};
- /*
+ /**
* 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 @@
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 @@
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 @@
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 @@
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 3cd2494..0420714 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -25218,7 +25218,6 @@
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 2be8c9a..b653410 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -161,6 +161,10 @@
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 e956dee..5b978e5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2321,7 +2321,7 @@
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.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 @@
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 @@
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 @@
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 @@
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 0541934..e449728 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -161,6 +161,7 @@
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 @@
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 @@
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 1d89e31..303ab41 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.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 @@
*
* @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 627017c..28d6fbb 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -469,7 +469,8 @@
}
}
- /** @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 df9530f..1cb46b1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -6475,7 +6475,7 @@
historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++));
}
}
- mDiscreteAccesses = historicalDiscreteAccesses;
+ mDiscreteAccesses = deduplicateDiscreteEvents(historicalDiscreteAccesses);
}
private void increaseAccessCount(@UidState int uidState, @OpFlags int flags,
@@ -6996,7 +6996,7 @@
}
result.add(entry);
}
- return result;
+ return deduplicateDiscreteEvents(result);
}
/**
@@ -9819,4 +9819,52 @@
}
}
}
+
+ 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 dfc105a..8574678 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -614,7 +614,7 @@
* 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 e83557c..4f7c684 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -190,4 +190,18 @@
* 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 4cf3a80..ca08683 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -53,7 +53,6 @@
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 @@
"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 6a71c92..8d332ab 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -190,6 +190,30 @@
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 e93138b..759597c 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -28,6 +28,7 @@
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 @@
// 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 @@
/**
* 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 @@
}
/**
- * 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 @@
}
}
+ 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 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 @@
@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 @@
@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 @@
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 @@
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 @@
|| !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 13f11ad..0544a36 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_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 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 @@
} 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 a72877e..fe81df0 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -320,6 +320,15 @@
}
/**
+ * 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 318913f..a88c9ed 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4705,10 +4705,9 @@
* @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 5ff1124..86a8a9d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8923,6 +8923,8 @@
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 @@
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 void close() throws Exception {
IoUtils.closeQuietly(mCachedAssetManager);
}
+
+ @Override
+ public ApkAssets getBaseApkAssets() {
+ return mBaseApkAssets;
+ }
}
/**
@@ -9085,5 +9094,10 @@
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 98a20f7..52ee4de 100644
--- a/core/java/android/content/pm/PackagePartitions.java
+++ b/core/java/android/content/pm/PackagePartitions.java
@@ -47,7 +47,7 @@
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 a1ffc0c..0fc6b2b 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -383,10 +383,9 @@
}
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 @@
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 @@
}
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 @@
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 @@
}
}
- 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 @@
}
}
+ @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 4ec2e73..3a4aae1 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 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 @@
- // 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 @@
};
@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 f3caf60..c1a8396 100644
--- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -43,6 +43,8 @@
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 @@
// 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 @@
}
@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 523ca40..e5c2158 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -128,6 +128,11 @@
}
@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 108fb95..7584e15f 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 @@
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 4ee5383..f03da7c 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -718,7 +718,7 @@
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 1597322..0000000
--- 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 8f6510e..da01dcb 100644
--- a/core/java/android/net/NetworkWatchlistManager.java
+++ b/core/java/android/net/NetworkWatchlistManager.java
@@ -19,6 +19,7 @@
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 @@
* 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 d5cc01a..cb9a3e4 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -24,7 +24,7 @@
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 @@
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
similarity index 87%
rename from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
rename to core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
index 1e4fdd7..1bb7680 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
@@ -19,11 +19,11 @@
/**
* @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 bbf802c..64c1211 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -67,7 +67,7 @@
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 @@
}
/**
- * 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 @@
mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>();
mTagRemovedListener = null;
mLock = new Object();
- mControllerAlwaysOnStateListener = new NfcControllerAlwaysOnStateListener(getService());
+ mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
}
/**
@@ -2246,12 +2246,12 @@
* <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 @@
}
/**
- * 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/NfcControllerAlwaysOnListener.java b/core/java/android/nfc/NfcControllerAlwaysOnListener.java
new file mode 100644
index 0000000..96707bb
--- /dev/null
+++ b/core/java/android/nfc/NfcControllerAlwaysOnListener.java
@@ -0,0 +1,120 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcControllerAlwaysOnListener extends INfcControllerAlwaysOnListener.Stub {
+ private static final String TAG = NfcControllerAlwaysOnListener.class.getSimpleName();
+
+ private final INfcAdapter mAdapter;
+
+ private final Map<ControllerAlwaysOnListener, Executor> mListenerMap = new HashMap<>();
+
+ private boolean mCurrentState = false;
+ private boolean mIsRegistered = false;
+
+ public NfcControllerAlwaysOnListener(@NonNull INfcAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Register a {@link ControllerAlwaysOnListener} with this
+ * {@link NfcControllerAlwaysOnListener}
+ *
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+ */
+ public void register(@NonNull Executor executor,
+ @NonNull ControllerAlwaysOnListener listener) {
+ synchronized (this) {
+ if (mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.put(listener, executor);
+ if (!mIsRegistered) {
+ try {
+ mAdapter.registerControllerAlwaysOnListener(this);
+ mIsRegistered = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register");
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregister the specified {@link ControllerAlwaysOnListener}
+ *
+ * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+ */
+ public void unregister(@NonNull ControllerAlwaysOnListener listener) {
+ synchronized (this) {
+ if (!mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.remove(listener);
+
+ if (mListenerMap.isEmpty() && mIsRegistered) {
+ try {
+ mAdapter.unregisterControllerAlwaysOnListener(this);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to unregister");
+ }
+ mIsRegistered = false;
+ }
+ }
+ }
+
+ private void sendCurrentState(@NonNull ControllerAlwaysOnListener listener) {
+ synchronized (this) {
+ Executor executor = mListenerMap.get(listener);
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onControllerAlwaysOnChanged(
+ mCurrentState));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void onControllerAlwaysOnChanged(boolean isEnabled) {
+ synchronized (this) {
+ mCurrentState = isEnabled;
+ for (ControllerAlwaysOnListener cb : mListenerMap.keySet()) {
+ sendCurrentState(cb);
+ }
+ }
+ }
+}
+
diff --git a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java b/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java
deleted file mode 100644
index 69a9ec7..0000000
--- a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-import android.annotation.NonNull;
-import android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- */
-public class NfcControllerAlwaysOnStateListener extends INfcControllerAlwaysOnStateCallback.Stub {
- private static final String TAG = "NfcControllerAlwaysOnStateListener";
-
- private final INfcAdapter mAdapter;
-
- private final Map<ControllerAlwaysOnStateCallback, Executor> mCallbackMap = new HashMap<>();
-
- private boolean mCurrentState = false;
- private boolean mIsRegistered = false;
-
- public NfcControllerAlwaysOnStateListener(@NonNull INfcAdapter adapter) {
- mAdapter = adapter;
- }
-
- /**
- * Register a {@link ControllerAlwaysOnStateCallback} with this
- * {@link NfcControllerAlwaysOnStateListener}
- *
- * @param executor an {@link Executor} to execute given callback
- * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
- */
- public void register(@NonNull Executor executor,
- @NonNull ControllerAlwaysOnStateCallback callback) {
- synchronized (this) {
- if (mCallbackMap.containsKey(callback)) {
- return;
- }
-
- mCallbackMap.put(callback, executor);
- if (!mIsRegistered) {
- try {
- mAdapter.registerControllerAlwaysOnStateCallback(this);
- mIsRegistered = true;
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to register ControllerAlwaysOnStateListener");
- }
- }
- }
- }
-
- /**
- * Unregister the specified {@link ControllerAlwaysOnStateCallback}
- *
- * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
- */
- public void unregister(@NonNull ControllerAlwaysOnStateCallback callback) {
- synchronized (this) {
- if (!mCallbackMap.containsKey(callback)) {
- return;
- }
-
- mCallbackMap.remove(callback);
-
- if (mCallbackMap.isEmpty() && mIsRegistered) {
- try {
- mAdapter.unregisterControllerAlwaysOnStateCallback(this);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to unregister ControllerAlwaysOnStateListener");
- }
- mIsRegistered = false;
- }
- }
- }
-
- private void sendCurrentState(@NonNull ControllerAlwaysOnStateCallback callback) {
- synchronized (this) {
- Executor executor = mCallbackMap.get(callback);
-
- final long identity = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> callback.onStateChanged(
- mCurrentState));
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- @Override
- public void onControllerAlwaysOnStateChanged(boolean isEnabled) {
- synchronized (this) {
- mCurrentState = isEnabled;
- for (ControllerAlwaysOnStateCallback cb : mCallbackMap.keySet()) {
- sendCurrentState(cb);
- }
- }
- }
-}
-
diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING
new file mode 100644
index 0000000..71ad687
--- /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 16d041a..d026e95 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -34,6 +34,9 @@
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 @@
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 1a40f06..17c90d6 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -886,6 +886,24 @@
}
/**
+ * @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 d89c29a..87fb611 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -37,6 +37,7 @@
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 @@
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 @@
private int mDisplayState;
SurfaceControl mSurfaceControl = new SurfaceControl();
+ BLASTBufferQueue mBlastBufferQueue;
final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
{
@@ -974,7 +976,14 @@
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 @@
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 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 340fa40..4b18c5a 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -796,13 +796,14 @@
/**
* 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 0a3963d..be172f7 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -693,71 +693,79 @@
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");
- }
-
- 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;
- }
-
- 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.");
- }
- scheduleVsyncLocked();
- return;
- }
-
- if (mFPSDivisor > 1) {
- long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
- if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
- scheduleVsyncLocked();
- return;
- }
- }
-
- mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
- vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
- mFrameScheduled = false;
- mLastFrameTimeNanos = frameTimeNanos;
- mLastFrameIntervalNanos = frameIntervalNanos;
- mLastVsyncEventData = vsyncEventData;
- }
-
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#doFrame " + vsyncEventData.id);
}
+ synchronized (mLock) {
+ if (!mFrameScheduled) {
+ traceMessage("Frame not scheduled");
+ return; // no work to do
+ }
+
+ if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
+ mDebugPrintNextFrameTimeDelta = false;
+ Log.d(TAG, "Frame time delta: "
+ + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ if (mFPSDivisor > 1) {
+ long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
+ if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
+ traceMessage("Frame skipped due to FPSDivisor");
+ scheduleVsyncLocked();
+ return;
+ }
+ }
+
+ 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 @@
@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 @@
@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 a6d786e..5fcb011 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -185,7 +185,8 @@
}
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 21f75d4..2c81e89 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -35,6 +35,7 @@
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 @@
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 @@
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 2b96a14..7bdf5cf 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -30,6 +30,7 @@
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 @@
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 @@
* 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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
* 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 76eb882..be8e519 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9309,11 +9309,11 @@
* 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 616910a..d6292ca 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -581,7 +581,7 @@
*/
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 20e520e..4365966 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -30,7 +30,7 @@
*/
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 1000914..555488d 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -123,21 +123,18 @@
}
/**
- * 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 e438d39..a0a0f32 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -92,7 +92,7 @@
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 fd13c26b..93cd4e9 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -85,7 +85,7 @@
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 a41511b..8aa2d57 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.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 @@
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 @@
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 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 ffba628..4194acb 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 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 @@
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 @@
(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 0000000..24e24c2
--- /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 a7127ad..b157146 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -308,6 +308,7 @@
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 419f142..f24d663 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 d69c7f8..5715fab 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 fb0dd46..b2b9ab3 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 @@
@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 @@
@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 e951054..f1be173 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 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 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 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 56c685a..3d18337 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 org.testng.Assert.expectThrows;
+import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -89,8 +90,12 @@
});
// 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 22c71b52..e7b88c8 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.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 @@
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 c393d68..8225afc 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 @@
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 0000000..335cea1
--- /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 0000000..99e2c34c
--- /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 0000000..490d6f5
--- /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 0000000..34b095c
--- /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 0000000..43f9b6f
--- /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 6c03ddc..e9e58c6 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -226,10 +226,12 @@
*/
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 @@
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 c317831..1cd4cf1 100644
--- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -40,6 +40,8 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 0865332..c972a24 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -858,15 +858,6 @@
}
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 @@
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 @@
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 4608d02..c1c6afc 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -167,6 +167,9 @@
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 f3baad7..602cd5d 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 fed3ea9..a17f543 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 a969386..b2c0055 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 8ca54e0..ef98a9c 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 530d40a..0c64c76 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 882ac37..dfa364a 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 6698a01..a138fee 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 125e322..25dd3ca 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.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 @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 OneHandedAnimationController(Context context) {
mSurfaceTransactionHelper = new OneHandedSurfaceTransactionHelper(context);
- mOvershootInterpolator = new OvershootInterpolator();
+ mInterpolator = new OneHandedInterpolator();
}
@SuppressWarnings("unchecked")
@@ -102,7 +101,7 @@
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 @@
*
* @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 @@
};
}
}
+
+ /**
+ * 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 19098fd..0a86ad8 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 @@
mOneHandedAccessibilityUtil.getOneHandedStartDescription());
mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
mTimeoutHandler.resetTimer();
-
mOneHandedUiEventLogger.writeEvent(
OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_IN);
}
@@ -351,12 +350,7 @@
@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 d4f229c..3af0ff0 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 @@
}
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 e50bde2..6494f89 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 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 @@
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 4c4a152..3056e97 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -820,10 +820,11 @@
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 e0a0be5..995f00c 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -153,6 +153,7 @@
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 855cd0d..173f394 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -152,32 +152,66 @@
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};
- }
+ setUniform2f(runtimeEffectBuilder, "in_origin", x->value, y->value);
+ setUniform(runtimeEffectBuilder, "in_radius", radius);
+ setUniform(runtimeEffectBuilder, "in_progress", progress);
+ setUniform(runtimeEffectBuilder, "in_turbulencePhase", turbulencePhase);
- 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;
- }
+ //
+ // 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 c1feb76..837b055 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -146,6 +146,7 @@
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 855d56e..eb5a88a 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -141,20 +141,22 @@
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 @@
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 7859145..7d65be1 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 @@
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 @@
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};
- }
+ setUniform2f("in_origin", mX->value, mY->value);
+ setUniform("in_radius", mRadius);
+ setUniform("in_progress", mProgress);
+ setUniform("in_turbulencePhase", mTurbulencePhase);
- 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;
- }
+ //
+ // 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 @@
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 71f533c..ab00dd5 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 @@
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 82814de..9e73f04 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -60,12 +60,11 @@
// 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 @@
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 06f2a27..4deb3b9 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -75,6 +75,7 @@
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 6bfbb0d..a6e4c4c 100644
--- a/libs/hwui/pipeline/skia/TransformCanvas.cpp
+++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp
@@ -22,7 +22,7 @@
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 9eacc74..e7d30ebb 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -19,6 +19,7 @@
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 @@
/**
* 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 83cc263..07d04aa 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 c62402d..bb93af9 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -100,6 +100,7 @@
"//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 4719772..9e2cd3e 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -13,7 +13,7 @@
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 2194575..3ca7475 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -277,7 +277,7 @@
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 @@
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 @@
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 92a792b..a2e218d 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
@@ -68,5 +68,11 @@
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 c4a0d69..96f2de6 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -5362,10 +5362,10 @@
* {@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 @@
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 0000000..79009e8
--- /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 c4277c3..775c88f 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -1749,7 +1749,7 @@
combineSSIDs(nc);
combineRequestor(nc);
combineAdministratorUids(nc);
- combineSubIds(nc);
+ combineSubscriptionIds(nc);
}
/**
@@ -1771,7 +1771,7 @@
&& (onlyImmutable || satisfiedByUids(nc))
&& (onlyImmutable || satisfiedBySSID(nc))
&& (onlyImmutable || satisfiedByRequestor(nc))
- && (onlyImmutable || satisfiedBySubIds(nc)));
+ && (onlyImmutable || satisfiedBySubscriptionIds(nc)));
}
/**
@@ -1868,7 +1868,7 @@
&& equalsPrivateDnsBroken(that)
&& equalsRequestor(that)
&& equalsAdministratorUids(that)
- && equalsSubIds(that);
+ && equalsSubscriptionIds(that);
}
@Override
@@ -2346,7 +2346,7 @@
* @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 @@
*/
@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 @@
* 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 @@
* <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 @@
*/
@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 78e1011..90aac0e 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -508,8 +508,8 @@
*/
@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 a24456e..f57061a 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 199a2d6..bddf43c 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 a834784..173e959 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 3363f8e..03d844a 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 @@
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 @@
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 01ec447..3da4521 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.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 @@
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 @@
}
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 @@
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 00bea8d..47a373e 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 @@
*/
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 f443332..842af26 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
similarity index 86%
rename from packages/SystemUI/res/drawable/ic_brightness_thumb.xml
rename to packages/SystemUI/res/drawable/ic_brightness_full.xml
index d721988..f443332 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 @@
<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 0000000..b463556
--- /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 0000000..80acc4d
--- /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 6022206..77b9871 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 0000000..4e9d380
--- /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 7d9cbb9..4236493 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 c9e4945..7070660 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 34aa8e4..7c28fc1 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 ae3adb8..4fcce24 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 db892d7..04fe918 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 dde1248..720e47b 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 813c97d..6abe406 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 0000000..c92dec9
--- /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 62ac75e..5d9c909 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 983b1bb..62d5a45 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -91,6 +91,13 @@
}
@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 a686fc0..cbfdce5 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.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 @@
@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 @@
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 5fa98bc..d07723e 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.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 @@
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 @@
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 @@
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 @@
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 @@
}
mHandler = startHandlerThread();
mHandler.post(this::startOnScreenDecorationsThread);
+ mDotViewController.setUiExecutor(mHandler::post);
}
@VisibleForTesting
@@ -286,6 +298,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ 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 2661d89..b544599 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 @@
/**
* 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 994401d..534f93e 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 @@
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 @@
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 @@
* @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 @@
* @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 @@
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 @@
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 @@
});
}
+ @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 de00d50..1a94473 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 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 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 @@
}
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 @@
}
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 @@
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 a0b5521..38a6186 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.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 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 @@
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 @@
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 @@
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 0fbe1f7..a160379 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 @@
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.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.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.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 @@
/** 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 @@
}
}
- /** 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 @@
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 @@
}
/** 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 @@
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 51af47d..6b917c5 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 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.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 @@
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 @@
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 @@
public static final String EMPTY_STRING = "";
+ private int mMediumVerticalPadding;
+
private Context mContext;
private PeopleSpaceTile mTile;
private float mDensity;
@@ -205,7 +213,8 @@
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 @@
}
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 c01a52d..c416b5e 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.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 @@
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 @@
}
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 d63dc4a..6cb7c31 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.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.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.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 @@
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 @@
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 @@
mPackageManager = packageManager;
mIsForTesting = isForTesting;
mPeopleSpaceWidgetProvider = peopleSpaceWidgetProvider;
+ mUserManager = userManager;
+ mINotificationManager = notificationManager;
}
/**
@@ -783,4 +796,52 @@
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 f87ea7c..feb27d80 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 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 @@
return PrivacyDialog(context, list, starter)
}
}
-
/**
* Controller for [PrivacyDialog].
*
@@ -66,6 +66,7 @@
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 @@
@Background backgroundExecutor: Executor,
@Main uiExecutor: Executor,
privacyLogger: PrivacyLogger,
- keyguardStateController: KeyguardStateController
+ keyguardStateController: KeyguardStateController,
+ appOpsController: AppOpsController
) : this(
permissionManager,
packageManager,
@@ -90,6 +92,7 @@
uiExecutor,
privacyLogger,
keyguardStateController,
+ appOpsController,
defaultDialogProvider
)
@@ -127,7 +130,9 @@
}
@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 @@
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 4c617ed..63ec6e5 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 @@
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 f7e2a31..8b27b6e 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 @@
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 @@
// 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 @@
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 @@
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 3467838..74ae3a6 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.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 @@
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 @@
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 @@
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 b728b43..6f19276 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 @@
@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 dbd6758..b60fd13 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 @@
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 f57fd21..f4266a2 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.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 @@
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 @@
}
/** 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
/**
* @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 @@
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 @@
&& !mNoAnimationsInThisFrame;
}
iconState.clampedAppearAmount = clampedAmount;
- setIconTransformationAmount(view, transitionAmount, isLastChild);
+ setIconTransformationAmount(view, transitionAmount);
}
private boolean isTargetClipped(ExpandableView view) {
@@ -585,12 +630,10 @@
+ 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 @@
return;
}
iconState.alpha = transitionAmount;
-
boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
iconState.hidden = isAppearing
|| (view instanceof ExpandableNotificationRow
@@ -610,8 +652,8 @@
|| (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 @@
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 @@
}
super.applyToView(view);
+ setIndexOfFirstViewInShelf(firstViewInShelf);
+ setFirstViewInOverflowingSection(firstViewInOverflowSection);
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
@@ -812,6 +867,8 @@
}
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 1e935c1..4f70fdb 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.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 @@
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 0000000..5ab71bc
--- /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 0000000..6380dc0
--- /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 0000000..6209630
--- /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 0000000..b481823
--- /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 0000000..b85d031
--- /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 5748c4a..2537b19 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 @@
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 @@
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 4ed5056..3bf0ddb 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 @@
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 8446b4e6..caf4720 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 @@
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 @@
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 @@
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 @@
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 fb72ac3..751573a 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 @@
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 @@
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 @@
mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
updateScrollability();
clampScrollPosition();
- mAmbientState.setLayoutMaxHeight(mContentHeight);
+ updateStackPosition();
+ mAmbientState.setContentHeight(mContentHeight);
}
/**
@@ -4849,7 +4877,7 @@
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 7776e69..4fc49ed 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 @@
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 bbdbe80..3e1a781 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.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 @@
// 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 @@
}
- 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 @@
&& ((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 @@
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 @@
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 @@
}
}
}
- 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 @@
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 @@
}
}
+ 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 @@
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;
- }
- int childHeight = getMaxAllowedChildHeight(child);
- childViewState.yTranslation = currentYPosition;
- childViewState.alpha = 1f - ambientState.getHideAmount();
-
- boolean isFooterView = child instanceof FooterView;
- boolean isEmptyShadeView = child instanceof EmptyShadeView;
-
- 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);
+ currentYPosition += sectionFraction * mGapHeight;
}
- 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);
+ viewState.yTranslation = currentYPosition;
+
+ if (view instanceof SectionHeaderView) {
+ // Add padding before sections for overscroll effect.
+ viewState.yTranslation += ambientState.getSectionPadding();
}
- childViewState.yTranslation += inset;
+ 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);
+ }
+
+ currentYPosition += viewState.height + sectionFraction * mPaddingBetweenElements;
+ setLocation(view.getViewState(), currentYPosition, i);
+ viewState.yTranslation += ambientState.getStackY();
return currentYPosition;
}
@@ -393,10 +525,10 @@
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 @@
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 @@
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 int scrollY;
/**
+ * First view in shelf.
+ */
+ public ExpandableView firstViewInShelf;
+
+ /**
+ * First view in section overflowing into shelf while shade expansion changes.
+ */
+ public ExpandableView firstViewInOverflowSection;
+
+ /**
+ * 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<ExpandableView>();
-
- private int indexOfExpandingNotification;
-
- public int getIndexOfExpandingNotification() {
- return indexOfExpandingNotification;
- }
+ 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 40e6b40..4e57e44 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_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.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.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 @@
* 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 @@
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 @@
};
@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 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 @@
showClock(false);
initEmergencyCryptkeeperText();
initOperatorName();
+ mAnimationScheduler.addCallback(this);
}
@Override
@@ -208,6 +232,7 @@
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 @@
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 @@
}
@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 c3325b1..b83039b 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.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 @@
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 @@
}
private void updateWalletVisibility() {
- if (mDozing || !mWalletEnabled) {
+ if (mDozing || !mWalletEnabled || !mHasCard) {
mWalletButton.setVisibility(GONE);
} else {
mWalletButton.setVisibility(VISIBLE);
@@ -659,6 +661,13 @@
updateCameraVisibility();
}
+ @Override
+ public void onKeyguardShowingChanged() {
+ if (mKeyguardStateController.isShowing()) {
+ queryWalletCards();
+ }
+ }
+
private void inflateCameraPreview() {
View previewBefore = mCameraPreview;
boolean visibleBefore = false;
@@ -897,18 +906,20 @@
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 0694737..c22fec9 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 @@
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.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 @@
/**
* 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 @@
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 @@
loadDimens();
loadBlockList();
mBatteryController = Dependency.get(BatteryController.class);
+ mAnimationScheduler = Dependency.get(SystemStatusAnimationScheduler.class);
}
@Override
@@ -349,6 +360,7 @@
mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
mIconManager.setBlockList(mBlockedIcons);
Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
+ mAnimationScheduler.addCallback(this);
onThemeChanged();
}
@@ -358,6 +370,7 @@
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 @@
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 1c8bda7..c4d8840 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 @@
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 5aecb72..0c8122c 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 @@
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 d386ebd..43d525d 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.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 @@
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 @@
BrightnessSlider.Factory brightnessSliderFactory,
WiredChargingRippleController chargingRippleAnimationController,
OngoingCallController ongoingCallController,
+ SystemStatusAnimationScheduler animationScheduler,
+ PrivacyDotViewController dotViewController,
TunerService tunerService,
FeatureFlags featureFlags) {
super(context);
@@ -875,6 +881,8 @@
mBrightnessSliderFactory = brightnessSliderFactory;
mChargingRippleAnimationController = chargingRippleAnimationController;
mOngoingCallController = ongoingCallController;
+ mAnimationScheduler = animationScheduler;
+ mDotViewController = dotViewController;
mFeatureFlags = featureFlags;
tunerService.addTunable(
@@ -1171,7 +1179,10 @@
}).getFragmentManager()
.beginTransaction()
.replace(R.id.status_bar_container,
- new CollapsedStatusBarFragment(mOngoingCallController),
+ new CollapsedStatusBarFragment(
+ mOngoingCallController,
+ mAnimationScheduler,
+ mDotViewController),
CollapsedStatusBarFragment.TAG)
.commit();
@@ -1788,7 +1799,15 @@
@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 3404528..4356b52 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.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 @@
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 @@
}
@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 @@
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 2c2779e..24e6db8 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.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 @@
BrightnessSlider.Factory brightnessSliderFactory,
WiredChargingRippleController chargingRippleAnimationController,
OngoingCallController ongoingCallController,
+ SystemStatusAnimationScheduler animationScheduler,
+ PrivacyDotViewController dotViewController,
TunerService tunerService,
FeatureFlags featureFlags) {
return new StatusBar(
@@ -293,6 +297,8 @@
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 d195062..d1a2c8a 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 @@
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 @@
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 74b79d5..81bb819 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 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 59262cf..3252750 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 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 @@
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
private UserTracker mUserTracker;
+ @Mock
+ private PrivacyDotViewController mDotViewController;
@Before
public void setup() {
@@ -116,7 +119,7 @@
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 3f0831c..78c6717 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 @@
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 @@
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 @@
}
@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 @@
}
@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 @@
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 42e88b0..63ce98a 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.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import android.app.IWallpaperManager;
import android.os.PowerManager;
import android.testing.AndroidTestingRunner;
@@ -43,9 +44,12 @@
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 0ce03ad..81ca4c8 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.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 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.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.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.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 @@
@Mock
private IPeopleManager mPeopleManager;
@Mock
- private LauncherApps mLauncherApps;
- @Mock
private IAppWidgetService mIAppWidgetService;
@Mock
private AppWidgetManager mAppWidgetManager;
@@ -239,84 +228,6 @@
}
@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 @@
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 107ac83..3cc55f2 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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 0ef3ca2..ccb40e1 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 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 @@
private BubblesManager mBubblesManager;
@Mock
private NotificationListenerService.Ranking mRanking;
+ @Mock
+ private UserManager mUserManager;
@Captor
private ArgumentCaptor<NotificationVisibility> mNotificationVisibilityCaptor;
@@ -93,7 +96,7 @@
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 @@
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 @@
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 7125500..e9be8d8 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.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 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.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.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -169,6 +177,10 @@
private NotificationEntryManager mNotificationEntryManager;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private INotificationManager mNotificationManager;
+ @Mock
+ private UserManager mUserManager;
@Captor
private ArgumentCaptor<NotificationHandler> mListenerCaptor;
@@ -189,7 +201,8 @@
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 @@
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 @@
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 791dd12..05a1e4f 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.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.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 @@
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 @@
uiExecutor,
privacyLogger,
keyguardStateController,
+ appOpsController,
dialogProvider
)
}
@@ -143,18 +148,27 @@
}
@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 @@
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 @@
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 @@
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 @@
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 @@
isActive = false,
lastAccess = 2L
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_recent, usage_mostRecent, usage_moreRecent)
)
@@ -342,7 +356,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 bba1c6a..e4d7b1b 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.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 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
@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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
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 d236023..4bba0d0 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 @@
}
@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 67fd5eb..929a7e1 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 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 @@
private View mCenteredNotificationAreaView;
private StatusBarStateController mStatusBarStateController;
private OngoingCallController mOngoingCallController;
+ private SystemStatusAnimationScheduler mAnimationScheduler;
+ private PrivacyDotViewController mDotViewController;
public CollapsedStatusBarFragmentTest() {
super(CollapsedStatusBarFragment.class);
@@ -212,6 +216,11 @@
@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 08d6d2d..11f96c8 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.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.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 @@
@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 @@
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 @@
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 4471778..40439a2 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 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 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 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 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 ed87a40..c38a547 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 @@
@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 653946e..6f6ef72 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 @@
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 @@
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 @@
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 4d96162..16e6671 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -630,7 +630,9 @@
}
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 @@
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 @@
// 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 @@
}
mAppOpsManager.checkPackage(callerUid, callerPackageName);
- if (!nc.getSubIds().isEmpty()) {
+ if (!nc.getSubscriptionIds().isEmpty()) {
enforceNetworkFactoryPermission();
}
}
@@ -6148,6 +6149,7 @@
@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 @@
@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 @@
@Override
public void unregisterConnectivityDiagnosticsCallback(
@NonNull IConnectivityDiagnosticsCallback callback) {
+ Objects.requireNonNull(callback, "callback must be non-null");
mConnectivityDiagnosticsHandler.sendMessage(
mConnectivityDiagnosticsHandler.obtainMessage(
ConnectivityDiagnosticsHandler
@@ -9556,6 +9559,7 @@
*/
@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 9f91dd6..483250a 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -174,9 +174,9 @@
# 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 df6ab5d..3ba4c34 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -19,9 +19,10 @@
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 @@
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 @@
}
}
+ 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 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 @@
mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled);
SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
mIndividualSensorListeners.get(userId);
+
+ setUserRestriction(userId, sensor, enabled);
+
if (listenersForUser == null) {
return;
}
@@ -1152,6 +1170,18 @@
}
}
+ 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 98bfa28..2d486c4 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2551,11 +2551,12 @@
* 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 @@
}
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 039f4d9..4b52057 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -729,7 +729,7 @@
// 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 @@
}
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 b103def..b7ef10a 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 @@
@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 @@
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 @@
}
}
- 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 @@
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 @@
break;
case ACTION_PACKAGE_REMOVED:
disableCompatScale(packageName);
- mConfigs.remove(packageName);
+ synchronized (mDeviceConfigLock) {
+ mConfigs.remove(packageName);
+ }
break;
default:
// do nothing
@@ -710,23 +710,6 @@
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 d56fd12..b795b54 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 @@
}
@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 c63af7e..f1bfd53 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 @@
*/
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 @@
}
@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 52da566..14cec09 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 @@
}
/**
+ * 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 584174e..18becd4 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 @@
}
/**
+ * 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 2b345e5..346af44 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 @@
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 @@
*
* @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 @@
// 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 7994fcc..94a5099 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 @@
@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 @@
@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 6244743..885093d 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 @@
@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 045e06d0..2ffc62a 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 @@
}
/** 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 @@
(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 @@
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 ab7e526..22a675a 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 @@
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 @@
}
} 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 59f00a2..6cded50 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 @@
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 abcf4fb..b10d56b 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 @@
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 8bd3b1e..42b7c9d3 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 @@
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 @@
}
}
+ /**
+ * 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 @@
}
}
- 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 @@
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 3ddfe6c..1e6e5cb 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 @@
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 @@
}
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 @@
}
}
+ 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 @@
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 @@
/** 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 @@
* 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 a9dbb2f..94a1f3b 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 @@
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.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 @@
* 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 @@
*
* @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 @@
* 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 @@
*
* 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 @@
}
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 @@
*
* 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 @@
if (TextUtils.isEmpty(actor)) {
continue;
}
- addOverlayToMap(actor, targetPkgName, overlay);
+ addOverlayToMap(actor, targetPkgName, overlay, changedPackages);
}
}
}
@@ -312,7 +359,8 @@
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 @@
}
}
- 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 ca8202f..4f527f2 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 @@
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 @@
}
}
- 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 @@
final AndroidPackage newPkg = newPkgSetting.pkg;
if (newPkg == null) {
- // nothing to add
- return;
+ return null;
}
if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) {
@@ -765,8 +785,13 @@
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 @@
}
}
- 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 @@
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 6875b8a..ec79483 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 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 85c5a5e..59039ba 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 @@
*/
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 @@
*/
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 @@
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 @@
*/
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 @@
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 @@
// 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 @@
c = mSnapshotComputer;
}
}
+ c.use();
return c;
}
}
@@ -5065,16 +4984,16 @@
* 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 @@
mSnapshotEnabled = false;
mLiveComputer = createLiveComputer();
mSnapshotComputer = null;
+ mSnapshotStatistics = null;
mPackages.putAll(testParams.packages);
mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
@@ -6479,17 +6399,20 @@
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 @@
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 @@
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 @@
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 0000000..c425bad5
--- /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 e913829..81cfbf7 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 bf2b3c7..a8a6a72 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.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.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 @@
* 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 44ff3eb..246810f 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.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.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 @@
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 20c1c3c..14cab38 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 @@
@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 a59b368..8818023 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 @@
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 @@
* 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 @@
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 89b7bbd..9acbdcc 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 @@
}
}
+ /**
+ * 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 e4dc8c2..f021072 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.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 @@
*/
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 @@
}
}
+ @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 @@
}
stopLaunchTrace(info);
+ final Boolean isHibernating =
+ mLastHibernationStates.remove(info.mLastLaunchedActivity.packageName);
if (abort) {
mSupervisor.stopWaitingForActivityVisible(info.mLastLaunchedActivity);
launchObserverNotifyActivityLaunchCancelled(info);
@@ -830,7 +858,7 @@
if (info.isInterestingToLoggerAndObserver()) {
launchObserverNotifyActivityLaunchFinished(info, timestampNs);
}
- logAppTransitionFinished(info);
+ logAppTransitionFinished(info, isHibernating != null ? isHibernating : false);
}
info.mPendingDrawActivities.clear();
mTransitionInfoList.remove(info);
@@ -859,7 +887,7 @@
}
}
- 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 @@
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 @@
// 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 @@
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 f5cfc9d..aa9727a 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.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_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.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.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 @@
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 @@
*/
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 @@
@Nullable
private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
- private boolean mShowWallpaperForLetterboxBackground;
-
// activity is not displayed?
// TODO: rename to mNoDisplay
@VisibleForTesting
@@ -1096,60 +1091,11 @@
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 @@
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 @@
? 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 @@
}
}
+ /**
+ * 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 @@
}
}
- 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 @@
* when the current activity is displayed.
*/
boolean isFullyTransparentBarAllowed(Rect rect) {
- return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect);
+ return mLetterboxUiController.isFullyTransparentBarAllowed(rect);
}
/**
@@ -1607,7 +1422,7 @@
* 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 @@
mPersistentState = persistentState;
taskDescription = _taskDescription;
+
+ mLetterboxUiController = new LetterboxUiController(mWmService, this);
+
if (_createTime > 0) {
createTime = _createTime;
}
@@ -2140,7 +1958,6 @@
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 @@
*/
void cleanUp(boolean cleanServices, boolean setState) {
task.cleanUpActivityReferences(this);
+ clearLastParentBeforePip();
deferRelaunchUntilPaused = false;
frozenBeforeDestroy = false;
@@ -3687,10 +3505,8 @@
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 @@
// 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 @@
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 @@
}
}
+
+ /**
+ * 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 @@
// 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 @@
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 @@
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 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 @@
// 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 @@
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 @@
/**
* 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 0000000..eb7087c
--- /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 0000000..130f680
--- /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 6631a3e..4bb48b0b 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 @@
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 52551ec..b9fc8b1 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 @@
.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 95a4f69e..fb66c04 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 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 @@
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 @@
.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 b82a308..c6cd560 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 @@
}
}
- 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 140ae3e..603bfd1 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 @@
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 @@
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 @@
@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 6f434e0..e0a791e 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 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 @@
*/
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 8690499..858d9f3 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.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 @@
// 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 @@
/** 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 @@
}
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 @@
return;
}
final int currentMode = getWindowingMode();
- final int currentOverrideMode = getRequestedOverrideWindowingMode();
final Task topTask = getTopMostTask();
int windowingMode = preferredWindowingMode;
@@ -5397,9 +5410,26 @@
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 @@
// 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 @@
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 @@
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 dff621c..625cff3 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 @@
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 5ffab48..a467d82 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 @@
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 c80a38f..d14a773 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.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 @@
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 @@
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 @@
}
}
- /**
- * 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 5942f34..1b578d1 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 @@
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.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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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) {
- 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);
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
+ if (peekNextArg() == null) {
+ getErrPrintWriter().println("Error: No arguments provided.");
+ }
+ while (peekNextArg() != null) {
+ String arg = getNextArg();
+ switch (arg) {
+ case "--aspectRatio":
+ runSetFixedOrientationLetterboxAspectRatio(pw);
+ break;
+ case "--cornerRadius":
+ runSetLetterboxActivityCornersRadius(pw);
+ break;
+ case "--backgroundType":
+ runSetLetterboxBackgroundType(pw);
+ break;
+ case "--backgroundColor":
+ runSetLetterboxBackgroundColor(pw);
+ break;
+ case "--wallpaperBlurRadius":
+ runSetLetterboxBackgroundWallpaperBlurRadius(pw);
+ break;
+ case "--wallpaperDarkScrimAlpha":
+ runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
+ break;
+ case "--horizontalPositionMultiplier":
+ runSeLetterboxHorizontalPositionMultiplier(pw);
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: Unrecognized letterbox style option: " + arg);
+ return -1;
}
}
return 0;
}
+ private int runResetLetterboxStyle(PrintWriter pw) throws RemoteException {
+ if (peekNextArg() == null) {
+ resetLetterboxStyle();
+ }
+ synchronized (mInternal.mGlobalLock) {
+ while (peekNextArg() != null) {
+ String arg = getNextArg();
+ switch (arg) {
+ case "aspectRatio":
+ mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+ break;
+ case "cornerRadius":
+ mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
+ break;
+ case "backgroundType":
+ mLetterboxConfiguration.resetLetterboxBackgroundType();
+ break;
+ case "backgroundColor":
+ mLetterboxConfiguration.resetLetterboxBackgroundColor();
+ break;
+ case "wallpaperBlurRadius":
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+ break;
+ case "wallpaperDarkScrimAlpha":
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+ break;
+ case "horizontalPositionMultiplier":
+ mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: Unrecognized letterbox style option: " + arg);
+ return -1;
+ }
+ }
+ }
+ return 0;
+ }
+
+ private 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 @@
// 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 @@
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 @@
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 2e37fee..9382b8e 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.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.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 @@
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 46d923b..1a5042f 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 @@
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 ddcb2bf..6283b4e 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_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 @@
+ " 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 @@
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 @@
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 b136d00..83677c2 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -36,6 +36,10 @@
],
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 0000000..558d01ed
--- /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 0000000..92dcd34
--- /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 0000000..21e4432
--- /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 0000000..f0b8586
--- /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 0000000..5718474
--- /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 0000000..a92a14f
--- /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 0000000..7537247f
--- /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 0000000..2bb6b82
--- /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 0000000..6038cb1
--- /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 0000000..4625e3b
--- /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 0000000..283f5c1
--- /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 0000000..822194f
--- /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 0000000..0100389
--- /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 dce853a..4de8d52 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 @@
<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 @@
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 0000000..98634b2
--- /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 e4b650c..17a5dcc 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 7654093..d55bbd1 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 @@
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 @@
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 @@
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 @@
}
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 @@
}
@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/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
new file mode 100644
index 0000000..a8d8a90
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -0,0 +1,488 @@
+/*
+ * 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.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
+ 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<>();
+
+ MockContext(Context base) {
+ super(base);
+ }
+
+ /**
+ * Mock checks for the specified permission, and have them behave as per {@code granted}.
+ *
+ * <p>Passing null reverts to default behavior, which does a real permission check on the
+ * test package.
+ *
+ * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+ * {@link PackageManager#PERMISSION_DENIED}.
+ */
+ public void setPermission(String permission, Integer granted) {
+ mMockedPermissions.put(permission, granted);
+ }
+
+ private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
+ final Integer granted = mMockedPermissions.get(permission);
+ return granted != null ? granted : ifAbsent.get();
+ }
+
+ @Override
+ public int checkPermission(String permission, int pid, int uid) {
+ return checkMockedPermission(
+ permission, () -> super.checkPermission(permission, pid, uid));
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ return checkMockedPermission(
+ permission, () -> super.checkCallingOrSelfPermission(permission));
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ final Integer granted = mMockedPermissions.get(permission);
+ if (granted == null) {
+ super.enforceCallingOrSelfPermission(permission, message);
+ return;
+ }
+
+ if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("[Test] permission denied: " + permission);
+ }
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mMockPackageManager;
+ }
+ }
+
+ @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() {
+ mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+ PackageManager.PERMISSION_GRANTED);
+ }
+
+ private void mockModifyGameModeDenied() {
+ mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+ 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.
+ */
+ @Test
+ public void testGameModeDefaultValue() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+
+ mockModifyGameModeGranted();
+
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ }
+
+ /**
+ * Test the default behaviour for a nonexistent user.
+ */
+ @Test
+ public void testDefaultValueForNonexistentUser() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+
+ mockModifyGameModeGranted();
+
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(mPackageName, USER_ID_2));
+ }
+
+ /**
+ * Test getter and setter of game modes.
+ */
+ @Test
+ public void testGameMode() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+
+ mockModifyGameModeGranted();
+
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+ USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ }
+
+ /**
+ * Test permission.MANAGE_GAME_MODE is checked
+ */
+ @Test
+ public void testGetGameModeInvalidPackageName() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ try {
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(PACKAGE_NAME_INVALID,
+ USER_ID_1));
+
+ fail("GameManagerService failed to generate SecurityException when "
+ + "permission.MANAGE_GAME_MODE is not granted.");
+ } catch (SecurityException ignored) {
+ }
+
+ // The test should throw an exception, so the test is passing if we get here.
+ }
+
+ /**
+ * Test permission.MANAGE_GAME_MODE is checked
+ */
+ @Test
+ public void testSetGameModePermissionDenied() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+
+ // Update the game mode so we can read back something valid.
+ mockModifyGameModeGranted();
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+ // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
+ mockModifyGameModeDenied();
+ try {
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+ USER_ID_1);
+
+ fail("GameManagerService failed to generate SecurityException when "
+ + "permission.MANAGE_GAME_MODE is denied.");
+ } catch (SecurityException ignored) {
+ }
+
+ // The test should throw an exception, so the test is passing if we get here.
+ mockModifyGameModeGranted();
+ // Verify that the Game Mode value wasn't updated.
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ }
+
+ /**
+ * Test game modes are user-specific.
+ */
+ @Test
+ public void testGameModeMultipleUsers() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ gameManagerService.onUserStarting(USER_ID_2);
+
+ mockModifyGameModeGranted();
+
+ // Set User 1 to Standard
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+ // Set User 2 to Performance and verify User 1 is still Standard
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+ USER_ID_2);
+ assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+ gameManagerService.getGameMode(mPackageName, USER_ID_2));
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+ // Set User 1 to Battery and verify User 2 is still Performance
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY,
+ USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_BATTERY,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ 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/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
deleted file mode 100644
index edc0d46..0000000
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.Manifest;
-import android.app.GameManager;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManager;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-import java.util.HashMap;
-import java.util.function.Supplier;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class GameManagerServiceTests {
-
- 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;
-
- // Stolen from ConnectivityServiceTest.MockContext
- static 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();
- }
-
- /**
- * Mock checks for the specified permission, and have them behave as per {@code granted}.
- *
- * <p>Passing null reverts to default behavior, which does a real permission check on the
- * test package.
- *
- * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
- * {@link PackageManager#PERMISSION_DENIED}.
- */
- public void setPermission(String permission, Integer granted) {
- mMockedPermissions.put(permission, granted);
- }
-
- private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
- final Integer granted = mMockedPermissions.get(permission);
- return granted != null ? granted : ifAbsent.get();
- }
-
- @Override
- public int checkPermission(String permission, int pid, int uid) {
- return checkMockedPermission(
- permission, () -> super.checkPermission(permission, pid, uid));
- }
-
- @Override
- public int checkCallingOrSelfPermission(String permission) {
- return checkMockedPermission(
- permission, () -> super.checkCallingOrSelfPermission(permission));
- }
-
- @Override
- public void enforceCallingOrSelfPermission(String permission, String message) {
- final Integer granted = mMockedPermissions.get(permission);
- if (granted == null) {
- super.enforceCallingOrSelfPermission(permission, message);
- return;
- }
-
- if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
- throw new SecurityException("[Test] permission denied: " + permission);
- }
- }
-
- @Override
- public PackageManager getPackageManager() {
- return mMockPackageManager;
- }
- }
-
- @Mock
- private MockContext mMockContext;
-
- private String mPackageName;
-
- @Before
- public void setUp() throws Exception {
- mMockContext = new MockContext(InstrumentationRegistry.getContext());
- mPackageName = mMockContext.getPackageName();
- }
-
- private void mockModifyGameModeGranted() {
- mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
- PackageManager.PERMISSION_GRANTED);
- }
-
- private void mockModifyGameModeDenied() {
- mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
- PackageManager.PERMISSION_DENIED);
- }
-
- /**
- * By default game mode is not supported.
- */
- @Test
- public void testGameModeDefaultValue() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
-
- mockModifyGameModeGranted();
-
- assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- }
-
- /**
- * Test the default behaviour for a nonexistent user.
- */
- @Test
- public void testDefaultValueForNonexistentUser() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
-
- mockModifyGameModeGranted();
-
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
- assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
- gameManagerService.getGameMode(mPackageName, USER_ID_2));
- }
-
- /**
- * Test getter and setter of game modes.
- */
- @Test
- public void testGameMode() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
-
- mockModifyGameModeGranted();
-
- assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
- assertEquals(GameManager.GAME_MODE_STANDARD,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
- USER_ID_1);
- assertEquals(GameManager.GAME_MODE_PERFORMANCE,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- }
-
- /**
- * Test permission.MANAGE_GAME_MODE is checked
- */
- @Test
- public void testGetGameModeInvalidPackageName() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
-
- try {
- assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
- gameManagerService.getGameMode(PACKAGE_NAME_INVALID,
- USER_ID_1));
-
- fail("GameManagerService failed to generate SecurityException when "
- + "permission.MANAGE_GAME_MODE is not granted.");
- } catch (SecurityException ignored) {
- }
-
- // The test should throw an exception, so the test is passing if we get here.
- }
-
- /**
- * Test permission.MANAGE_GAME_MODE is checked
- */
- @Test
- public void testSetGameModePermissionDenied() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
-
- // Update the game mode so we can read back something valid.
- mockModifyGameModeGranted();
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
- assertEquals(GameManager.GAME_MODE_STANDARD,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
- // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
- mockModifyGameModeDenied();
- try {
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
- USER_ID_1);
-
- fail("GameManagerService failed to generate SecurityException when "
- + "permission.MANAGE_GAME_MODE is denied.");
- } catch (SecurityException ignored) {
- }
-
- // The test should throw an exception, so the test is passing if we get here.
- mockModifyGameModeGranted();
- // Verify that the Game Mode value wasn't updated.
- assertEquals(GameManager.GAME_MODE_STANDARD,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- }
-
- /**
- * Test game modes are user-specific.
- */
- @Test
- public void testGameModeMultipleUsers() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
- gameManagerService.onUserStarting(USER_ID_2);
-
- mockModifyGameModeGranted();
-
- // Set User 1 to Standard
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
- assertEquals(GameManager.GAME_MODE_STANDARD,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
- // Set User 2 to Performance and verify User 1 is still Standard
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
- USER_ID_2);
- assertEquals(GameManager.GAME_MODE_PERFORMANCE,
- gameManagerService.getGameMode(mPackageName, USER_ID_2));
- assertEquals(GameManager.GAME_MODE_STANDARD,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
- // Set User 1 to Battery and verify User 2 is still Performance
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY,
- USER_ID_1);
- assertEquals(GameManager.GAME_MODE_BATTERY,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- assertEquals(GameManager.GAME_MODE_PERFORMANCE,
- gameManagerService.getGameMode(mPackageName, USER_ID_2));
- }
-}
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 73ec5b8..1b42dfa 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_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 @@
@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 @@
}
@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 @@
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 @@
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 @@
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 @@
.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 @@
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 @@
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 4a42940..5d60a89 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 @@
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 2b9a05c..efa1b04 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.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 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 d888b92..876c845 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 @@
@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 @@
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 @@
)
)
)
- 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 @@
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 @@
)
)
)
- 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 8070bd1..558e259 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 9f428c7..67dd055 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 @@
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 @@
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 809b6d5..182848b4 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 @@
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 @@
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 @@
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 @@
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 87efaa2..37d7198 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 @@
}
@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 @@
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 @@
}
@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 @@
}
@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 0afd39f..2f52352 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 @@
// test misc display overrides
assertEquals(ignoreOrientationRequests, testDisplayContent.mIgnoreOrientationRequest);
- assertEquals(fixedOrientationLetterboxRatio, mWm.getFixedOrientationLetterboxAspectRatio(),
+ assertEquals(fixedOrientationLetterboxRatio,
+ mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(),
0 /* delta */);
}
@@ -2011,7 +2012,8 @@
// 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 7c2cfab..95b7443 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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
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 f3616da6c..619aee6 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 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 5bafbbd..2d4e4ef 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 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 @@
// 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 @@
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 2e692e6..7f24c36 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 @@
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 32533cb..0d6cd5a 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -760,6 +760,7 @@
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 bde62fb..ac01afa 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -134,7 +134,7 @@
*
* 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 @@
* @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 c8ed82c..0539897 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1055,6 +1055,20 @@
*/
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 @@
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 @@
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 f0771be..c8e5e56 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4885,8 +4885,9 @@
* 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 6c1c653..5642549 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -31,7 +31,6 @@
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 0593615..a540dff 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.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 @@
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.appLayerReplacesLauncher(appName: String) {
assertLayers {
- this.isVisible(*LAUNCHER_TITLE)
+ this.isVisible(*HOME_WINDOW_TITLE)
.then()
.isVisible(appName)
}
@@ -190,7 +190,7 @@
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 6a7309c..01e34d9 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 @@
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 f6d9a73..487c856 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 @@
CanvasProperty<Float> mY;
CanvasProperty<Float> mRadius;
CanvasProperty<Float> mProgress;
+ CanvasProperty<Float> mNoisePhase;
CanvasProperty<Paint> mPaint;
RuntimeShader mRuntimeShader;
@@ -99,6 +100,7 @@
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 @@
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 @@
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 6d852bf..9b74583 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -312,7 +312,7 @@
.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 @@
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 @@
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 @@
// 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 @@
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 @@
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 6cbdd25..19f8843 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -384,7 +384,7 @@
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 1b0a500..b71be59 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -2668,25 +2668,6 @@
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 @@
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 @@
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 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 @@
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 9a66343..907cb46 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -696,7 +696,7 @@
.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 b592000..8289e85 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 @@
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 @@
private NetworkRequest getWifiRequest(Set<Integer> netCapsSubIds) {
return getExpectedRequestBase()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
- .setSubIds(netCapsSubIds)
+ .setSubscriptionIds(netCapsSubIds)
.build();
}
@@ -203,7 +201,7 @@
}
private NetworkRequest getRouteSelectionRequest(Set<Integer> netCapsSubIds) {
- return getExpectedRequestBase().setSubIds(netCapsSubIds).build();
+ return getExpectedRequestBase().setSubscriptionIds(netCapsSubIds).build();
}
private NetworkRequest.Builder getExpectedRequestBase() {
@@ -267,8 +265,8 @@
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 72fb867..0000000
--- 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 710da40..0000000
--- 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 da0571ba..3b75660 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 @@
/**
* 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 @@
/** @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 @@
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 @@
* @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 98a0042..3fb2301 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 @@
@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 @@
}
/**
- * 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);
}
/**