Merge "Revert implict min update interval to 1/6 of interval" into sc-dev
diff --git a/StubLibraries.bp b/StubLibraries.bp
index e82a322..941a1fa 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -205,6 +205,7 @@
api_lint: {
enabled: true,
new_since: ":android.api.module-lib.latest",
+ baseline_file: "core/api/module-lib-lint-baseline.txt",
},
},
dists: [
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 0c07fa0..0709ff5 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -17,11 +17,10 @@
import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
import static android.os.Process.INVALID_UID;
-import static android.os.UserHandle.USER_NULL;
+import android.Manifest;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchMigrationHelper;
import android.app.appsearch.AppSearchResult;
@@ -63,6 +62,7 @@
import com.android.server.appsearch.external.localstorage.stats.CallStats;
import com.android.server.appsearch.stats.LoggerInstanceManager;
import com.android.server.appsearch.stats.PlatformLogger;
+import com.android.server.appsearch.util.PackageUtil;
import com.android.server.usage.StorageStatsManagerLocal;
import com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter;
@@ -124,8 +124,10 @@
}
private void registerReceivers() {
- mContext.registerReceiverAsUser(new UserActionReceiver(), UserHandle.ALL,
- new IntentFilter(Intent.ACTION_USER_REMOVED), /*broadcastPermission=*/ null,
+ mContext.registerReceiverForAllUsers(
+ new UserActionReceiver(),
+ new IntentFilter(Intent.ACTION_USER_REMOVED),
+ /*broadcastPermission=*/ null,
/*scheduler=*/ null);
//TODO(b/145759910) Add a direct callback when user clears the data instead of relying on
@@ -135,8 +137,10 @@
packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
packageChangedFilter.addDataScheme("package");
packageChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL,
- packageChangedFilter, /*broadcastPermission=*/ null,
+ mContext.registerReceiverForAllUsers(
+ new PackageChangedReceiver(),
+ packageChangedFilter,
+ /*broadcastPermission=*/ null,
/*scheduler=*/ null);
}
@@ -148,12 +152,13 @@
switch (intent.getAction()) {
case Intent.ACTION_USER_REMOVED:
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
- if (userId == USER_NULL) {
- Log.e(TAG, "userId is missing in the intent: " + intent);
+ UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
+ if (userHandle == null) {
+ Log.e(TAG, "Extra "
+ + Intent.EXTRA_USER + " is missing in the intent: " + intent);
return;
}
- handleUserRemoved(UserHandle.of(userId));
+ handleUserRemoved(userHandle);
break;
default:
Log.e(TAG, "Received unknown intent: " + intent);
@@ -1183,13 +1188,9 @@
Objects.requireNonNull(actualCallingUser);
Objects.requireNonNull(claimedCallingPackage);
- int claimedCallingUid;
- try {
- Context claimedCallingContext =
- mContext.createContextAsUser(actualCallingUser, /*flags=*/ 0);
- claimedCallingUid = claimedCallingContext.getPackageManager().getPackageUid(
- claimedCallingPackage, /*flags=*/ 0);
- } catch (PackageManager.NameNotFoundException e) {
+ int claimedCallingUid = PackageUtil.getPackageUidAsUser(
+ mContext, claimedCallingPackage, actualCallingUser);
+ if (claimedCallingUid == INVALID_UID) {
throw new SecurityException(
"Specified calling package [" + claimedCallingPackage + "] not found");
}
@@ -1257,23 +1258,44 @@
*
* <p>Takes care of checking permissions and converting USER_CURRENT to the actual current user.
*
+ * @param requestedUser The user which the caller is requesting to execute as.
+ * @param callingUid The actual uid of the caller as determined by Binder.
* @return the user handle that the call should run as. Will always be a concrete user.
*/
// TODO(b/173553485) verifying that the caller has permission to access target user's data
// TODO(b/173553485) Handle ACTION_USER_REMOVED broadcast
// TODO(b/173553485) Implement SystemService.onUserStopping()
@NonNull
- private static UserHandle handleIncomingUser(@NonNull UserHandle userHandle, int callingUid) {
+ private UserHandle handleIncomingUser(@NonNull UserHandle requestedUser, int callingUid) {
int callingPid = Binder.getCallingPid();
- int finalUserId = ActivityManager.handleIncomingUser(
+ UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+ if (callingUser.equals(requestedUser)) {
+ return requestedUser;
+ }
+ // Duplicates UserController#ensureNotSpecialUser
+ if (requestedUser.getIdentifier() < 0) {
+ throw new IllegalArgumentException(
+ "Call does not support special user " + requestedUser);
+ }
+ boolean canInteractAcrossUsers = mContext.checkPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
callingPid,
- callingUid,
- userHandle.getIdentifier(),
- /*allowAll=*/ false,
- /*requireFull=*/ false,
- /*name=*/ null,
- /*callerPackage=*/ null);
- return UserHandle.of(finalUserId);
+ callingUid) == PackageManager.PERMISSION_GRANTED;
+ if (!canInteractAcrossUsers) {
+ canInteractAcrossUsers = mContext.checkPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ callingPid,
+ callingUid) == PackageManager.PERMISSION_GRANTED;
+ }
+ if (canInteractAcrossUsers) {
+ return requestedUser;
+ }
+ throw new SecurityException(
+ "Permission denied while calling from uid " + callingUid
+ + " with " + requestedUser + "; Need to run as either the calling user ("
+ + callingUser + "), or with one of the following permissions: "
+ + Manifest.permission.INTERACT_ACROSS_USERS + " or "
+ + Manifest.permission.INTERACT_ACROSS_USERS_FULL);
}
// TODO(b/179160886): Cache the previous storage stats.
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index beb4d24..0775272 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -172,11 +172,6 @@
@Nullable AppSearchLogger logger)
throws AppSearchException {
File appSearchDir = getAppSearchDir(userHandle);
- // TODO(b/181787682): Swap AppSearchImpl and VisibilityStore to accept a UserHandle too
- return AppSearchImpl.create(
- appSearchDir,
- userContext,
- userHandle.getIdentifier(),
- /*logger=*/ null);
+ return AppSearchImpl.create(appSearchDir, userContext, /*logger=*/ null);
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index a940dde..29cb57c 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -201,7 +201,6 @@
public static AppSearchImpl create(
@NonNull File icingDir,
@NonNull Context userContext,
- int userId,
@Nullable AppSearchLogger logger)
throws AppSearchException {
Objects.requireNonNull(icingDir);
@@ -213,8 +212,7 @@
initStatsBuilder = new InitializeStats.Builder();
}
- AppSearchImpl appSearchImpl =
- new AppSearchImpl(icingDir, userContext, userId, initStatsBuilder);
+ AppSearchImpl appSearchImpl = new AppSearchImpl(icingDir, userContext, initStatsBuilder);
long prepareVisibilityStoreLatencyStartMillis = SystemClock.elapsedRealtime();
appSearchImpl.initializeVisibilityStore();
@@ -238,7 +236,6 @@
private AppSearchImpl(
@NonNull File icingDir,
@NonNull Context userContext,
- int userId,
@Nullable InitializeStats.Builder initStatsBuilder)
throws AppSearchException {
mReadWriteLock.writeLock().lock();
@@ -256,7 +253,7 @@
"Constructing IcingSearchEngine, response",
Objects.hashCode(mIcingSearchEngineLocked));
- mVisibilityStoreLocked = new VisibilityStore(this, userContext, userId);
+ mVisibilityStoreLocked = new VisibilityStore(this, userContext);
// The core initialization procedure. If any part of this fails, we bail into
// resetLocked(), deleting all data (but hopefully allowing AppSearchImpl to come up).
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 7d0ce41..c857fb6 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
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -36,6 +35,7 @@
import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
import com.android.server.appsearch.external.localstorage.stats.RemoveStats;
import com.android.server.appsearch.external.localstorage.stats.SearchStats;
+import com.android.server.appsearch.util.PackageUtil;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
@@ -493,21 +493,13 @@
@GuardedBy("mLock")
private int getPackageUidAsUserLocked(@NonNull String packageName) {
Integer packageUid = mPackageUidCacheLocked.get(packageName);
- if (packageUid != null) {
- return packageUid;
+ if (packageUid == null) {
+ packageUid = PackageUtil.getPackageUidAsUser(mContext, packageName, mUserHandle);
+ if (packageUid != Process.INVALID_UID) {
+ mPackageUidCacheLocked.put(packageName, packageUid);
+ }
}
-
- // TODO(b/173532925) since VisibilityStore has the same method, we can make this a
- // utility function
- try {
- packageUid = mContext.getPackageManager().getPackageUidAsUser(
- packageName, mUserHandle.getIdentifier());
- mPackageUidCacheLocked.put(packageName, packageUid);
- return packageUid;
- } catch (PackageManager.NameNotFoundException e) {
- // Package doesn't exist, continue
- }
- return Process.INVALID_UID;
+ return packageUid;
}
//
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/util/PackageUtil.java b/apex/appsearch/service/java/com/android/server/appsearch/util/PackageUtil.java
new file mode 100644
index 0000000..53a1bed
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/util/PackageUtil.java
@@ -0,0 +1,55 @@
+/*
+ * 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.appsearch.util;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+
+/**
+ * Utilities for interacting with {@link android.content.pm.PackageManager},
+ * {@link android.os.UserHandle}, and other parts of dealing with apps and binder.
+ *
+ * @hide
+ */
+public class PackageUtil {
+ private PackageUtil() {}
+
+ /**
+ * Finds the UID of the {@code packageName}. Returns {@link Process#INVALID_UID} if unable to
+ * find the UID.
+ */
+ public static int getPackageUidAsUser(
+ @NonNull Context context, @NonNull String packageName, @NonNull UserHandle user) {
+ Context userContext = context.createContextAsUser(user, /*flags=*/ 0);
+ return getPackageUid(userContext, packageName);
+ }
+
+ /**
+ * Finds the UID of the {@code packageName} in the given {@code context}. Returns
+ * {@link Process#INVALID_UID} if unable to find the UID.
+ */
+ public static int getPackageUid(@NonNull Context context, @NonNull String packageName) {
+ try {
+ return context.getPackageManager().getPackageUid(packageName, /*flags=*/ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return Process.INVALID_UID;
+ }
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java
index acff792..95ed368 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
import android.annotation.NonNull;
-import android.annotation.UserIdInt;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
@@ -27,7 +26,6 @@
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Process;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -35,6 +33,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+import com.android.server.appsearch.util.PackageUtil;
import com.google.android.icing.proto.PersistType;
@@ -67,9 +66,6 @@
* @hide
*/
public class VisibilityStore {
-
- private static final String TAG = "AppSearchVisibilityStore";
-
/** No-op user id that won't have any visibility settings. */
public static final int NO_OP_USER_ID = -1;
@@ -95,9 +91,6 @@
// Context of the user that the call is being made as.
private final Context mUserContext;
- // User ID of the caller who we're checking visibility settings for.
- private final int mUserId;
-
/** Stores the schemas that are platform-hidden. All values are prefixed. */
private final NotPlatformSurfaceableMap mNotPlatformSurfaceableMap =
new NotPlatformSurfaceableMap();
@@ -112,13 +105,9 @@
* @param appSearchImpl AppSearchImpl instance
* @param userContext Context of the user that the call is being made as
*/
- public VisibilityStore(
- @NonNull AppSearchImpl appSearchImpl,
- @NonNull Context userContext,
- @UserIdInt int userId) {
+ public VisibilityStore(@NonNull AppSearchImpl appSearchImpl, @NonNull Context userContext) {
mAppSearchImpl = appSearchImpl;
- mUserContext = userContext;
- mUserId = userId;
+ mUserContext = Objects.requireNonNull(userContext);
}
/**
@@ -348,6 +337,9 @@
Set<PackageIdentifier> packageIdentifiers =
mPackageAccessibleMap.getAccessiblePackages(
packageName, databaseName, prefixedSchema);
+ if (packageIdentifiers.isEmpty()) {
+ return false;
+ }
for (PackageIdentifier packageIdentifier : packageIdentifiers) {
// TODO(b/169883602): Consider caching the UIDs of packages. Looking this up in the
// package manager could be costly. We would also need to update the cache on
@@ -357,9 +349,10 @@
// the callerUid since clients can createContextAsUser with some other user, and then
// make calls to us. So just check if the appId portion of the uid is the same. This is
// essentially UserHandle.isSameApp, but that's not a system API for us to use.
- int callerAppId = UserHandle.getAppId((callerUid));
- int userAppId =
- UserHandle.getAppId(getPackageUidAsUser(packageIdentifier.getPackageName()));
+ int callerAppId = UserHandle.getAppId(callerUid);
+ int packageUid =
+ PackageUtil.getPackageUid(mUserContext, packageIdentifier.getPackageName());
+ int userAppId = UserHandle.getAppId(packageUid);
if (callerAppId != userAppId) {
continue;
}
@@ -401,17 +394,4 @@
@NonNull String packageName, @NonNull String databaseName) {
return ID_PREFIX + PrefixUtil.createPrefix(packageName, databaseName);
}
-
- /**
- * Finds the UID of the {@code packageName}. Returns {@link Process#INVALID_UID} if unable to
- * find the UID.
- */
- private int getPackageUidAsUser(@NonNull String packageName) {
- try {
- return mUserContext.getPackageManager().getPackageUidAsUser(packageName, mUserId);
- } catch (PackageManager.NameNotFoundException e) {
- // Package doesn't exist, continue
- }
- return Process.INVALID_UID;
- }
}
diff --git a/apex/media/Android.bp b/apex/media/Android.bp
index a75f1ae..2b4b3f0 100644
--- a/apex/media/Android.bp
+++ b/apex/media/Android.bp
@@ -27,7 +27,6 @@
sdk {
name: "media-module-sdk",
- java_sdk_libs: [
- "framework-media",
- ],
+ bootclasspath_fragments: ["com.android.media-bootclasspath-fragment"],
+ java_sdk_libs: ["service-media-s"],
}
diff --git a/core/api/module-lib-lint-baseline.txt b/core/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..0c1ebb3
--- /dev/null
+++ b/core/api/module-lib-lint-baseline.txt
@@ -0,0 +1,39 @@
+// Baseline format: 1.0
+SamShouldBeLast: android.app.ActivityManager#addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.app.ActivityManager.addOnUidImportanceListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.PendingIntent#send(android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished, android.os.Handler):
+ SAM-compatible parameters (such as parameter 4, "onFinished", in android.app.PendingIntent.send) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.PendingIntent#send(android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished, android.os.Handler, String):
+ SAM-compatible parameters (such as parameter 4, "onFinished", in android.app.PendingIntent.send) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.PendingIntent#send(android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished, android.os.Handler, String, android.os.Bundle):
+ SAM-compatible parameters (such as parameter 4, "onFinished", in android.app.PendingIntent.send) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.PendingIntent#send(int, android.app.PendingIntent.OnFinished, android.os.Handler):
+ SAM-compatible parameters (such as parameter 2, "onFinished", in android.app.PendingIntent.send) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioManager#abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes):
+ SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.abandonAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int):
+ SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.requestAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy):
+ SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.requestAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
+ SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.requestAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName):
+ SAM-compatible parameters (such as parameter 1, "sessionListener", in android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "sessionListener", in android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnSession2TokensChangedListener(android.media.session.MediaSessionManager.OnSession2TokensChangedListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.media.session.MediaSessionManager.addOnSession2TokensChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.session.MediaSessionManager#setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.media.session.MediaSessionManager.setOnMediaKeyListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.session.MediaSessionManager#setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.media.session.MediaSessionManager.setOnVolumeKeyLongPressListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.os.Binder#attachInterface(android.os.IInterface, String):
+ SAM-compatible parameters (such as parameter 1, "owner", in android.os.Binder.attachInterface) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.os.Binder#linkToDeath(android.os.IBinder.DeathRecipient, int):
+ SAM-compatible parameters (such as parameter 1, "recipient", in android.os.Binder.linkToDeath) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.os.Binder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
+ SAM-compatible parameters (such as parameter 1, "recipient", in android.os.Binder.unlinkToDeath) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.os.IBinder#linkToDeath(android.os.IBinder.DeathRecipient, int):
+ SAM-compatible parameters (such as parameter 1, "recipient", in android.os.IBinder.linkToDeath) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.os.IBinder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
+ SAM-compatible parameters (such as parameter 1, "recipient", in android.os.IBinder.unlinkToDeath) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 58f6c52..b435acf 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1,6 +1,6 @@
// Baseline format: 1.0
ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions():
-
+
BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
@@ -12,23 +12,23 @@
GenericException: android.app.prediction.AppPredictor#finalize():
-
+
GenericException: android.hardware.location.ContextHubClient#finalize():
-
+
GenericException: android.net.IpSecManager.IpSecTunnelInterface#finalize():
-
+
GenericException: android.service.autofill.augmented.FillWindow#finalize():
-
+
IntentBuilderName: android.app.search.SearchAction#getIntent():
-
+
IntentBuilderName: android.app.smartspace.SmartspaceAction#getIntent():
Methods creating an Intent should be named `create<Foo>Intent()`, was `getIntent`
KotlinKeyword: android.app.Notification#when:
-
+
MissingGetterMatchingBuilder: android.security.keystore.KeyGenParameterSpec.Builder#setUid(int):
@@ -42,49 +42,49 @@
MissingNullability: android.media.soundtrigger.SoundTriggerDetectionService#onUnbind(android.content.Intent) parameter #0:
-
+
MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #0:
-
+
MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1:
-
+
MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2:
-
+
MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0:
-
+
MissingNullability: android.provider.ContactsContract.MetadataSync#CONTENT_URI:
-
+
MissingNullability: android.provider.ContactsContract.MetadataSync#METADATA_AUTHORITY_URI:
-
+
MissingNullability: android.provider.ContactsContract.MetadataSyncState#CONTENT_URI:
-
+
MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #0:
-
+
MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #1:
-
+
MissingNullability: android.service.autofill.augmented.AugmentedAutofillService#onUnbind(android.content.Intent) parameter #0:
-
+
MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
-
+
MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1:
-
+
MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2:
-
+
MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
-
+
MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
-
+
MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringDaily(java.time.ZonedDateTime) parameter #0:
-
+
MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringMonthly(java.time.ZonedDateTime) parameter #0:
-
+
MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringWeekly(java.time.ZonedDateTime) parameter #0:
-
+
MissingNullability: android.telephony.data.DataService#onUnbind(android.content.Intent) parameter #0:
-
+
MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
-
+
MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String) parameter #0:
-
+
OnNameExpected: android.service.smartspace.SmartspaceService#notifySmartspaceEvent(android.app.smartspace.SmartspaceSessionId, android.app.smartspace.SmartspaceTargetEvent):
@@ -92,178 +92,192 @@
ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
-
+
ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
-
+
ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context):
-
+
SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
-
+
SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, String[]):
-
+
SamShouldBeLast: android.accounts.AccountManager#confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#editProperties(String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#getAuthTokenByFeatures(String, String, String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#renameAccount(android.accounts.Account, String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#startAddAccountSession(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#startUpdateCredentialsSession(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#updateCredentials(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.app.AlarmManager#set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
-
+
SamShouldBeLast: android.app.AlarmManager#setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
-
+
SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
-
+
SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.app.WallpaperManager#addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback):
-
+
SamShouldBeLast: android.content.IntentFilter#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.content.pm.ApplicationInfo#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.content.pm.PackageItemInfo#dumpBack(android.util.Printer, String):
-
+
SamShouldBeLast: android.content.pm.PackageItemInfo#dumpFront(android.util.Printer, String):
-
+
SamShouldBeLast: android.content.pm.ResolveInfo#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.location.Location#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
-
+
SamShouldBeLast: android.location.LocationManager#registerGnssMeasurementsCallback(java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback):
-
+
SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageCallback(java.util.concurrent.Executor, android.location.GnssNavigationMessage.Callback):
-
+
SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
-
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener, android.os.Looper):
-
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
-
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
-
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper):
-
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
-
+
SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(String, android.location.LocationListener, android.os.Looper):
-
+
SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper):
-
+
SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
-
+
SamShouldBeLast: android.media.AudioRecord#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.AudioRecord#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
-
+
SamShouldBeLast: android.media.AudioRecordingMonitor#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
-
+
SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-
+
+SamShouldBeLast: android.media.MediaPlayer#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.MediaPlayer#setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.setOnDrmInfoListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.MediaPlayer#setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.setOnDrmPreparedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.MediaPlayer#setOnMediaTimeDiscontinuityListener(android.media.MediaPlayer.OnMediaTimeDiscontinuityListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.setOnMediaTimeDiscontinuityListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.MediaPlayer#setOnSubtitleDataListener(android.media.MediaPlayer.OnSubtitleDataListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.setOnSubtitleDataListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
-
+
SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName):
-
+
SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler):
-
+
SamShouldBeLast: android.media.session.MediaSessionManager#addOnSession2TokensChangedListener(android.media.session.MediaSessionManager.OnSession2TokensChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.session.MediaSessionManager#registerCallback(java.util.concurrent.Executor, android.media.session.MediaSessionManager.Callback):
-
+
SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
-
+
SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
-
+
SamShouldBeLast: android.nfc.NfcAdapter#setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity):
-
+
SamShouldBeLast: android.nfc.NfcAdapter#setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...):
-
+
SamShouldBeLast: android.nfc.NfcAdapter#setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...):
-
+
SamShouldBeLast: android.os.Binder#attachInterface(android.os.IInterface, String):
-
+
SamShouldBeLast: android.os.Binder#linkToDeath(android.os.IBinder.DeathRecipient, int):
-
+
SamShouldBeLast: android.os.Binder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
-
+
SamShouldBeLast: android.os.Handler#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.os.Handler#postAtTime(Runnable, Object, long):
-
+
SamShouldBeLast: android.os.Handler#postAtTime(Runnable, long):
-
+
SamShouldBeLast: android.os.Handler#postDelayed(Runnable, Object, long):
-
+
SamShouldBeLast: android.os.Handler#postDelayed(Runnable, long):
-
+
SamShouldBeLast: android.os.Handler#removeCallbacks(Runnable, Object):
-
+
SamShouldBeLast: android.os.IBinder#linkToDeath(android.os.IBinder.DeathRecipient, int):
-
+
SamShouldBeLast: android.os.IBinder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
-
+
SamShouldBeLast: android.os.RecoverySystem#verifyPackage(java.io.File, android.os.RecoverySystem.ProgressListener, java.io.File):
-
+
+SamShouldBeLast: android.security.KeyChain#choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, String[], java.security.Principal[], String, int, String):
+ SAM-compatible parameters (such as parameter 2, "response", in android.security.KeyChain.choosePrivateKeyAlias) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.security.KeyChain#choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, String[], java.security.Principal[], android.net.Uri, String):
+ SAM-compatible parameters (such as parameter 2, "response", in android.security.KeyChain.choosePrivateKeyAlias) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.view.View#postDelayed(Runnable, long):
-
+
SamShouldBeLast: android.view.View#postOnAnimationDelayed(Runnable, long):
-
+
SamShouldBeLast: android.view.View#scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long):
-
+
SamShouldBeLast: android.view.Window#addOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener, android.os.Handler):
-
+
SamShouldBeLast: android.view.accessibility.AccessibilityManager#addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler):
-
+
SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler):
-
+
SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams):
-
+
UserHandleName: android.app.search.SearchAction.Builder#setUserHandle(android.os.UserHandle):
Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
UserHandleName: android.app.search.SearchTarget.Builder#setUserHandle(android.os.UserHandle):
-
+
UserHandleName: android.app.smartspace.SmartspaceAction.Builder#setUserHandle(android.os.UserHandle):
Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 25fd254..7cb8bc0 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -604,6 +604,14 @@
String ownerPkgName, int ownerUid);
/**
+ * Effectively PendingIntent.getActivityForUser(), but the PendingIntent is
+ * owned by the given uid rather than by the caller (i.e. the system).
+ */
+ public abstract PendingIntent getPendingIntentActivityAsApp(
+ int requestCode, @NonNull Intent[] intents, int flags, Bundle options,
+ String ownerPkgName, int ownerUid);
+
+ /**
* @return mBootTimeTempAllowlistDuration of ActivityManagerConstants.
*/
public abstract long getBootTimeTempAllowListDuration();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 306b54d..1ce598b 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -56,6 +56,7 @@
import android.view.ViewGroup;
import android.view.Window;
import android.window.IRemoteTransition;
+import android.window.SplashScreen;
import android.window.WindowContainerToken;
import java.lang.annotation.Retention;
@@ -327,6 +328,10 @@
private static final String KEY_LAUNCHED_FROM_BUBBLE =
"android.activity.launchTypeBubble";
+ /** See {@link #setSplashscreenStyle(int)}. */
+ private static final String KEY_SPLASH_SCREEN_STYLE =
+ "android.activity.splashScreenStyle";
+
/** See {@link #setTransientLaunch()}. */
private static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch";
@@ -415,6 +420,8 @@
private IRemoteTransition mRemoteTransition;
private boolean mOverrideTaskTransition;
private int mSplashScreenThemeResId;
+ @SplashScreen.SplashScreenStyle
+ private int mSplashScreenStyle;
private boolean mRemoveWithTaskOrganizer;
private boolean mLaunchedFromBubble;
private boolean mTransientLaunch;
@@ -1171,6 +1178,7 @@
mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER);
mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE);
mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH);
+ mSplashScreenStyle = opts.getInt(KEY_SPLASH_SCREEN_STYLE);
}
/**
@@ -1365,6 +1373,23 @@
}
/**
+ * Sets the preferred splash screen style.
+ * @hide
+ */
+ public void setSplashscreenStyle(@SplashScreen.SplashScreenStyle int style) {
+ mSplashScreenStyle = style;
+ }
+
+ /**
+ * Gets the preferred splash screen style from caller
+ * @hide
+ */
+ @SplashScreen.SplashScreenStyle
+ public int getSplashScreenStyle() {
+ return mSplashScreenStyle;
+ }
+
+ /**
* Sets whether the activity is to be launched into LockTask mode.
*
* Use this option to start an activity in LockTask mode. Note that only apps permitted by
@@ -1932,6 +1957,9 @@
if (mTransientLaunch) {
b.putBoolean(KEY_TRANSIENT_LAUNCH, mTransientLaunch);
}
+ if (mSplashScreenStyle != 0) {
+ b.putInt(KEY_SPLASH_SCREEN_STYLE, mSplashScreenStyle);
+ }
return b;
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8d74796..5b65795 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -13112,6 +13112,10 @@
* @see #getCrossProfileCalendarPackages(ComponentName)
* @hide
*/
+ @RequiresPermission(anyOf = {
+ permission.INTERACT_ACROSS_USERS_FULL,
+ permission.INTERACT_ACROSS_USERS
+ })
public @Nullable Set<String> getCrossProfileCalendarPackages() {
throwIfParentInstance("getCrossProfileCalendarPackages");
if (mService != null) {
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index 24ca72c..0e31567 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -119,7 +119,7 @@
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides);
try {
- platformCompat.setOverridesOnReleaseBuilds(config, packageName);
+ platformCompat.putOverridesOnReleaseBuilds(config, packageName);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index dc29c5e..a741f96 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -265,7 +265,7 @@
// Return an empty cursor for all columns.
return new MatrixCursor(cursor.getColumnNames(), 0);
}
- Trace.traceBegin(TRACE_TAG_DATABASE, "query");
+ traceBegin(TRACE_TAG_DATABASE, "query: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -285,7 +285,7 @@
// getCallingPackage() isn't available in getType(), as the javadoc states.
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- Trace.traceBegin(TRACE_TAG_DATABASE, "getType");
+ traceBegin(TRACE_TAG_DATABASE, "getType: ", uri.getAuthority());
try {
return mInterface.getType(uri);
} catch (RemoteException e) {
@@ -323,7 +323,7 @@
setCallingAttributionSource(original);
}
}
- Trace.traceBegin(TRACE_TAG_DATABASE, "insert");
+ traceBegin(TRACE_TAG_DATABASE, "insert: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -345,7 +345,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
- Trace.traceBegin(TRACE_TAG_DATABASE, "bulkInsert");
+ traceBegin(TRACE_TAG_DATABASE, "bulkInsert: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -391,7 +391,7 @@
}
}
}
- Trace.traceBegin(TRACE_TAG_DATABASE, "applyBatch");
+ traceBegin(TRACE_TAG_DATABASE, "applyBatch: ", authority);
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -423,7 +423,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
- Trace.traceBegin(TRACE_TAG_DATABASE, "delete");
+ traceBegin(TRACE_TAG_DATABASE, "delete: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -445,7 +445,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
- Trace.traceBegin(TRACE_TAG_DATABASE, "update");
+ traceBegin(TRACE_TAG_DATABASE, "update: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -465,7 +465,7 @@
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
enforceFilePermission(attributionSource, uri, mode);
- Trace.traceBegin(TRACE_TAG_DATABASE, "openFile");
+ traceBegin(TRACE_TAG_DATABASE, "openFile: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -486,7 +486,7 @@
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
enforceFilePermission(attributionSource, uri, mode);
- Trace.traceBegin(TRACE_TAG_DATABASE, "openAssetFile");
+ traceBegin(TRACE_TAG_DATABASE, "openAssetFile: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -505,7 +505,7 @@
String method, @Nullable String arg, @Nullable Bundle extras) {
validateIncomingAuthority(authority);
Bundle.setDefusable(extras, true);
- Trace.traceBegin(TRACE_TAG_DATABASE, "call");
+ traceBegin(TRACE_TAG_DATABASE, "call: ", authority);
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -523,7 +523,7 @@
// getCallingPackage() isn't available in getType(), as the javadoc states.
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- Trace.traceBegin(TRACE_TAG_DATABASE, "getStreamTypes");
+ traceBegin(TRACE_TAG_DATABASE, "getStreamTypes: ", uri.getAuthority());
try {
return mInterface.getStreamTypes(uri, mimeTypeFilter);
} catch (RemoteException e) {
@@ -541,7 +541,7 @@
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
enforceFilePermission(attributionSource, uri, "r");
- Trace.traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile");
+ traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -569,7 +569,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return null;
}
- Trace.traceBegin(TRACE_TAG_DATABASE, "canonicalize");
+ traceBegin(TRACE_TAG_DATABASE, "canonicalize: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -605,7 +605,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return null;
}
- Trace.traceBegin(TRACE_TAG_DATABASE, "uncanonicalize");
+ traceBegin(TRACE_TAG_DATABASE, "uncanonicalize: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -641,7 +641,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return false;
}
- Trace.traceBegin(TRACE_TAG_DATABASE, "refresh");
+ traceBegin(TRACE_TAG_DATABASE, "refresh: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -658,7 +658,7 @@
int uid, int modeFlags) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- Trace.traceBegin(TRACE_TAG_DATABASE, "checkUriPermission");
+ traceBegin(TRACE_TAG_DATABASE, "checkUriPermission: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -2683,4 +2683,10 @@
}
return uri;
}
+
+ private static void traceBegin(long traceTag, String methodName, String subInfo) {
+ if (Trace.isTagEnabled(traceTag)) {
+ Trace.traceBegin(traceTag, methodName + subInfo);
+ }
+ }
}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 5887047..1e650a8 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -19,6 +19,9 @@
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
import static android.content.pm.parsing.ParsingPackageUtils.validateName;
+import static android.content.pm.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
+import static android.content.pm.parsing.ParsingUtils.DEFAULT_MIN_SDK_VERSION;
+import static android.content.pm.parsing.ParsingUtils.DEFAULT_TARGET_SDK_VERSION;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import android.annotation.NonNull;
@@ -31,12 +34,12 @@
import android.content.res.ApkAssets;
import android.content.res.XmlResourceParser;
import android.os.Trace;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.Slog;
-import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import libcore.io.IoUtils;
@@ -59,10 +62,6 @@
private static final String TAG = ParsingUtils.TAG;
- // TODO(b/135203078): Consolidate constants
- private static final int DEFAULT_MIN_SDK_VERSION = 1;
- private static final int DEFAULT_TARGET_SDK_VERSION = 0;
-
private static final int PARSE_DEFAULT_INSTALL_LOCATION =
PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
@@ -323,8 +322,7 @@
signingDetails = PackageParser.SigningDetails.UNKNOWN;
}
- final AttributeSet attrs = parser;
- return parseApkLite(input, apkPath, parser, attrs, signingDetails);
+ return parseApkLite(input, apkPath, parser, signingDetails);
} catch (XmlPullParserException | IOException | RuntimeException e) {
Slog.w(TAG, "Failed to parse " + apkPath, e);
return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
@@ -342,32 +340,39 @@
}
private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath,
- XmlPullParser parser, AttributeSet attrs, PackageParser.SigningDetails signingDetails)
+ XmlResourceParser parser, PackageParser.SigningDetails signingDetails)
throws IOException, XmlPullParserException {
- ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser, attrs);
+ ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser);
if (result.isError()) {
return input.error(result);
}
Pair<String, String> packageSplit = result.getResult();
- int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
- int versionCode = 0;
- int versionCodeMajor = 0;
+ int installLocation = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
+ "installLocation", PARSE_DEFAULT_INSTALL_LOCATION);
+ int versionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "versionCode", 0);
+ int versionCodeMajor = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
+ "versionCodeMajor",
+ 0);
+ int revisionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "revisionCode", 0);
+ boolean coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
+ boolean isolatedSplits = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "isolatedSplits", false);
+ boolean isFeatureSplit = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "isFeatureSplit", false);
+ boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "isSplitRequired", false);
+ String configForSplit = parser.getAttributeValue(null, "configForSplit");
+
int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
- int revisionCode = 0;
- boolean coreApp = false;
boolean debuggable = false;
boolean profilableByShell = false;
boolean multiArch = false;
boolean use32bitAbi = false;
boolean extractNativeLibs = true;
- boolean isolatedSplits = false;
- boolean isFeatureSplit = false;
- boolean isSplitRequired = false;
boolean useEmbeddedDex = false;
- String configForSplit = null;
String usesSplitName = null;
String targetPackage = null;
boolean overlayIsStatic = false;
@@ -377,40 +382,6 @@
String requiredSystemPropertyName = null;
String requiredSystemPropertyValue = null;
- for (int i = 0; i < attrs.getAttributeCount(); i++) {
- final String attr = attrs.getAttributeName(i);
- switch (attr) {
- case "installLocation":
- installLocation = attrs.getAttributeIntValue(i,
- PARSE_DEFAULT_INSTALL_LOCATION);
- break;
- case "versionCode":
- versionCode = attrs.getAttributeIntValue(i, 0);
- break;
- case "versionCodeMajor":
- versionCodeMajor = attrs.getAttributeIntValue(i, 0);
- break;
- case "revisionCode":
- revisionCode = attrs.getAttributeIntValue(i, 0);
- break;
- case "coreApp":
- coreApp = attrs.getAttributeBooleanValue(i, false);
- break;
- case "isolatedSplits":
- isolatedSplits = attrs.getAttributeBooleanValue(i, false);
- break;
- case "configForSplit":
- configForSplit = attrs.getAttributeValue(i);
- break;
- case "isFeatureSplit":
- isFeatureSplit = attrs.getAttributeBooleanValue(i, false);
- break;
- case "isSplitRequired":
- isSplitRequired = attrs.getAttributeBooleanValue(i, false);
- break;
- }
- }
-
// Only search the tree when the tag is the direct child of <manifest> tag
int type;
final int searchDepth = parser.getDepth() + 1;
@@ -427,34 +398,23 @@
}
if (ParsingPackageUtils.TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
- final VerifierInfo verifier = parseVerifier(attrs);
+ final VerifierInfo verifier = parseVerifier(parser);
if (verifier != null) {
verifiers.add(verifier);
}
} else if (ParsingPackageUtils.TAG_APPLICATION.equals(parser.getName())) {
- for (int i = 0; i < attrs.getAttributeCount(); ++i) {
- final String attr = attrs.getAttributeName(i);
- switch (attr) {
- case "debuggable":
- debuggable = attrs.getAttributeBooleanValue(i, false);
- break;
- case "multiArch":
- multiArch = attrs.getAttributeBooleanValue(i, false);
- break;
- case "use32bitAbi":
- use32bitAbi = attrs.getAttributeBooleanValue(i, false);
- break;
- case "extractNativeLibs":
- extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
- break;
- case "useEmbeddedDex":
- useEmbeddedDex = attrs.getAttributeBooleanValue(i, false);
- break;
- case "rollbackDataPolicy":
- rollbackDataPolicy = attrs.getAttributeIntValue(i, 0);
- break;
- }
- }
+ debuggable = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "debuggable",
+ false);
+ multiArch = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "multiArch",
+ false);
+ use32bitAbi = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "use32bitAbi",
+ false);
+ extractNativeLibs = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "extractNativeLibs", true);
+ useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "useEmbeddedDex", false);
+ rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
+ "rollbackDataPolicy", 0);
final int innerDepth = parser.getDepth();
int innerType;
@@ -470,52 +430,79 @@
}
if (ParsingPackageUtils.TAG_PROFILEABLE.equals(parser.getName())) {
- for (int i = 0; i < attrs.getAttributeCount(); ++i) {
- final String attr = attrs.getAttributeName(i);
- if ("shell".equals(attr)) {
- profilableByShell = attrs.getAttributeBooleanValue(i,
- profilableByShell);
- }
- }
+ profilableByShell = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "shell", profilableByShell);
}
}
} else if (ParsingPackageUtils.TAG_OVERLAY.equals(parser.getName())) {
- for (int i = 0; i < attrs.getAttributeCount(); ++i) {
- final String attr = attrs.getAttributeName(i);
- if ("requiredSystemPropertyName".equals(attr)) {
- requiredSystemPropertyName = attrs.getAttributeValue(i);
- } else if ("requiredSystemPropertyValue".equals(attr)) {
- requiredSystemPropertyValue = attrs.getAttributeValue(i);
- } else if ("targetPackage".equals(attr)) {
- targetPackage = attrs.getAttributeValue(i);;
- } else if ("isStatic".equals(attr)) {
- overlayIsStatic = attrs.getAttributeBooleanValue(i, false);
- } else if ("priority".equals(attr)) {
- overlayPriority = attrs.getAttributeIntValue(i, 0);
- }
- }
+ requiredSystemPropertyName = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
+ "requiredSystemPropertyName");
+ requiredSystemPropertyValue = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
+ "requiredSystemPropertyValue");
+ targetPackage = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "targetPackage");
+ overlayIsStatic = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "isStatic",
+ false);
+ overlayPriority = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "priority", 0);
} else if (ParsingPackageUtils.TAG_USES_SPLIT.equals(parser.getName())) {
if (usesSplitName != null) {
Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
continue;
}
- usesSplitName = attrs.getAttributeValue(PackageParser.ANDROID_RESOURCES, "name");
+ usesSplitName = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
if (usesSplitName == null) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"<uses-split> tag requires 'android:name' attribute");
}
} else if (ParsingPackageUtils.TAG_USES_SDK.equals(parser.getName())) {
- for (int i = 0; i < attrs.getAttributeCount(); ++i) {
- final String attr = attrs.getAttributeName(i);
- if ("targetSdkVersion".equals(attr)) {
- targetSdkVersion = attrs.getAttributeIntValue(i,
- DEFAULT_TARGET_SDK_VERSION);
- }
- if ("minSdkVersion".equals(attr)) {
- minSdkVersion = attrs.getAttributeIntValue(i, DEFAULT_MIN_SDK_VERSION);
+ // Mirrors ParsingPackageUtils#parseUsesSdk until lite and full parsing is combined
+ String minSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
+ "minSdkVersion");
+ String targetSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
+ "targetSdkVersion");
+
+ int minVer = DEFAULT_MIN_SDK_VERSION;
+ String minCode = null;
+ int targetVer = DEFAULT_TARGET_SDK_VERSION;
+ String targetCode = null;
+
+ if (!TextUtils.isEmpty(minSdkVersionString)) {
+ try {
+ minVer = Integer.parseInt(minSdkVersionString);
+ } catch (NumberFormatException ignored) {
+ minCode = minSdkVersionString;
}
}
+
+ if (!TextUtils.isEmpty(targetSdkVersionString)) {
+ try {
+ targetVer = Integer.parseInt(targetSdkVersionString);
+ } catch (NumberFormatException ignored) {
+ targetCode = targetSdkVersionString;
+ if (minCode == null) {
+ minCode = targetCode;
+ }
+ }
+ } else {
+ targetVer = minVer;
+ targetCode = minCode;
+ }
+
+ ParseResult<Integer> targetResult = ParsingPackageUtils.computeTargetSdkVersion(
+ targetVer, targetCode, ParsingPackageUtils.SDK_CODENAMES, input);
+ if (targetResult.isError()) {
+ return input.error(targetResult);
+ }
+
+ ParseResult<Integer> minResult = ParsingPackageUtils.computeMinSdkVersion(
+ minVer, minCode, ParsingPackageUtils.SDK_VERSION,
+ ParsingPackageUtils.SDK_CODENAMES, input);
+ if (minResult.isError()) {
+ return input.error(minResult);
+ }
+
+ targetSdkVersion = targetResult.getResult();
+ minSdkVersion = minResult.getResult();
}
}
@@ -541,7 +528,7 @@
}
public static ParseResult<Pair<String, String>> parsePackageSplitNames(ParseInput input,
- XmlPullParser parser, AttributeSet attrs) throws IOException, XmlPullParserException {
+ XmlResourceParser parser) throws IOException, XmlPullParserException {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
@@ -556,7 +543,7 @@
"No <manifest> tag");
}
- final String packageName = attrs.getAttributeValue(null, "package");
+ final String packageName = parser.getAttributeValue(null, "package");
if (!"android".equals(packageName)) {
final ParseResult<?> nameResult = validateName(input, packageName, true, true);
if (nameResult.isError()) {
@@ -565,7 +552,7 @@
}
}
- String splitName = attrs.getAttributeValue(null, "split");
+ String splitName = parser.getAttributeValue(null, "split");
if (splitName != null) {
if (splitName.length() == 0) {
splitName = null;
@@ -583,22 +570,8 @@
}
public static VerifierInfo parseVerifier(AttributeSet attrs) {
- String packageName = null;
- String encodedPublicKey = null;
-
- final int attrCount = attrs.getAttributeCount();
- for (int i = 0; i < attrCount; i++) {
- final int attrResId = attrs.getAttributeNameResource(i);
- switch (attrResId) {
- case R.attr.name:
- packageName = attrs.getAttributeValue(i);
- break;
-
- case R.attr.publicKey:
- encodedPublicKey = attrs.getAttributeValue(i);
- break;
- }
- }
+ String packageName = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
+ String encodedPublicKey = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "publicKey");
if (packageName == null || packageName.length() == 0) {
Slog.i(TAG, "verifier package name was null; skipping");
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index f0b7649..4c44ba1 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -356,7 +356,7 @@
private float minAspectRatio;
@Nullable
private SparseIntArray minExtensionVersions;
- private int minSdkVersion;
+ private int minSdkVersion = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
private int networkSecurityConfigRes;
@Nullable
private CharSequence nonLocalizedLabel;
@@ -369,7 +369,7 @@
private int requiresSmallestWidthDp;
private int roundIconRes;
private int targetSandboxVersion;
- private int targetSdkVersion;
+ private int targetSdkVersion = ParsingUtils.DEFAULT_TARGET_SDK_VERSION;
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
private String taskAffinity;
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index b74760a..6fd5333 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -582,12 +582,12 @@
*/
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
String codePath, Resources res, XmlResourceParser parser, int flags)
- throws XmlPullParserException, IOException, PackageParserException {
+ throws XmlPullParserException, IOException {
final String splitName;
final String pkgName;
ParseResult<Pair<String, String>> packageSplitResult =
- ApkLiteParseUtils.parsePackageSplitNames(input, parser, parser);
+ ApkLiteParseUtils.parsePackageSplitNames(input, parser);
if (packageSplitResult.isError()) {
return input.error(packageSplitResult);
}
@@ -1460,9 +1460,9 @@
if (SDK_VERSION > 0) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
try {
- int minVers = 1;
+ int minVers = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
String minCode = null;
- int targetVers = 0;
+ int targetVers = ParsingUtils.DEFAULT_TARGET_SDK_VERSION;
String targetCode = null;
TypedValue val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_minSdkVersion);
diff --git a/core/java/android/content/pm/parsing/ParsingUtils.java b/core/java/android/content/pm/parsing/ParsingUtils.java
index 5da5fbf..07ec6a8 100644
--- a/core/java/android/content/pm/parsing/ParsingUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingUtils.java
@@ -35,6 +35,11 @@
public static final String TAG = "PackageParsing";
+ public static final String ANDROID_RES_NAMESPACE = "http://schemas.android.com/apk/res/android";
+
+ public static final int DEFAULT_MIN_SDK_VERSION = 1;
+ public static final int DEFAULT_TARGET_SDK_VERSION = 0;
+
@Nullable
public static String buildClassName(String pkg, CharSequence clsSeq) {
if (clsSeq == null || clsSeq.length() <= 0) {
diff --git a/core/java/android/content/pm/verify/domain/TEST_MAPPING b/core/java/android/content/pm/verify/domain/TEST_MAPPING
index 5fcf411..ba4a62c 100644
--- a/core/java/android/content/pm/verify/domain/TEST_MAPPING
+++ b/core/java/android/content/pm/verify/domain/TEST_MAPPING
@@ -9,7 +9,10 @@
]
},
{
- "name": "CtsDomainVerificationDeviceTestCases"
+ "name": "CtsDomainVerificationDeviceStandaloneTestCases"
+ },
+ {
+ "name": "CtsDomainVerificationDeviceMultiUserTestCases"
},
{
"name": "CtsDomainVerificationHostTestCases"
diff --git a/core/java/android/os/incremental/IncrementalMetrics.java b/core/java/android/os/incremental/IncrementalMetrics.java
index c44b7d9..534525a 100644
--- a/core/java/android/os/incremental/IncrementalMetrics.java
+++ b/core/java/android/os/incremental/IncrementalMetrics.java
@@ -90,7 +90,7 @@
* @return total duration in milliseconds of delayed reads
*/
public long getTotalDelayedReadsDurationMillis() {
- return mData.getInt(IIncrementalService.METRICS_TOTAL_DELAYED_READS_MILLIS, -1);
+ return mData.getLong(IIncrementalService.METRICS_TOTAL_DELAYED_READS_MILLIS, -1);
}
/**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 085136e..603df1e 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -157,7 +157,7 @@
private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
private static final boolean ENABLE_WALLPAPER_DIMMING =
- SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", false);
+ SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", true);
private final ArrayList<Engine> mActiveEngines
= new ArrayList<Engine>();
@@ -1764,6 +1764,7 @@
float finalStep = step;
int finalPageIndx = pageIndx;
Bitmap screenShot = page.getBitmap();
+ if (screenShot == null) screenShot = mLastScreenshot;
if (screenShot == null || screenShot.isRecycled()) {
if (DEBUG) {
Log.d(TAG, "invalid bitmap " + screenShot
diff --git a/core/java/android/view/CrossWindowBlurListeners.java b/core/java/android/view/CrossWindowBlurListeners.java
index 55fc4f4..e307b96 100644
--- a/core/java/android/view/CrossWindowBlurListeners.java
+++ b/core/java/android/view/CrossWindowBlurListeners.java
@@ -42,7 +42,7 @@
// property for background blur support in surface flinger
private static final String BLUR_PROPERTY = "ro.surface_flinger.supports_background_blur";
public static final boolean CROSS_WINDOW_BLUR_SUPPORTED =
- SystemProperties.get(BLUR_PROPERTY, "default").equals("1");
+ SystemProperties.getBoolean(BLUR_PROPERTY, false);
private static volatile CrossWindowBlurListeners sInstance;
private static final Object sLock = new Object();
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index bf2af51..790b93a 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -90,6 +90,8 @@
@NonNull
private final Handler mWorkerHandler;
private int mCurrentState;
+ @NonNull
+ private ArraySet<AutofillId> mLastRequestAutofillIds;
public UiTranslationController(Activity activity, Context context) {
mActivity = activity;
@@ -120,6 +122,9 @@
+ (DEBUG ? (", views: " + views + ", spec: " + uiTranslationSpec) : ""));
synchronized (mLock) {
mCurrentState = state;
+ if (views != null) {
+ setLastRequestAutofillIdsLocked(views);
+ }
}
switch (state) {
case STATE_UI_TRANSLATION_STARTED:
@@ -175,13 +180,25 @@
}
}
+ private void setLastRequestAutofillIdsLocked(List<AutofillId> views) {
+ if (mLastRequestAutofillIds == null) {
+ mLastRequestAutofillIds = new ArraySet<>();
+ }
+ if (mLastRequestAutofillIds.size() > 0) {
+ mLastRequestAutofillIds.clear();
+ }
+ mLastRequestAutofillIds.addAll(views);
+ }
+
/**
* Called to dump the translation information for Activity.
*/
public void dump(String outerPrefix, PrintWriter pw) {
pw.print(outerPrefix); pw.println("UiTranslationController:");
final String pfx = outerPrefix + " ";
- pw.print(pfx); pw.print("activity: "); pw.println(mActivity);
+ pw.print(pfx); pw.print("activity: "); pw.print(mActivity);
+ pw.print(pfx); pw.print("resumed: "); pw.println(mActivity.isResumed());
+ pw.print(pfx); pw.print("current state: "); pw.println(mCurrentState);
final int translatorSize = mTranslators.size();
pw.print(outerPrefix); pw.print("number translator: "); pw.println(translatorSize);
for (int i = 0; i < translatorSize; i++) {
@@ -244,13 +261,18 @@
pw.print(outerPrefix); pw.print("autofillId: "); pw.print(autofillId);
// TODO: print TranslationTransformation
boolean isContainsView = false;
+ boolean isRequestedView = false;
synchronized (mLock) {
+ if (mLastRequestAutofillIds.contains(autofillId)) {
+ isRequestedView = true;
+ }
final WeakReference<View> viewRef = mViews.get(autofillId);
if (viewRef != null && viewRef.get() != null) {
isContainsView = true;
}
}
- pw.print(outerPrefix); pw.print("isContainsView: "); pw.println(isContainsView);
+ pw.print(outerPrefix); pw.print("isContainsView: "); pw.print(isContainsView);
+ pw.print(outerPrefix); pw.print("isRequestedView: "); pw.println(isRequestedView);
}
/**
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
index 42a58fb..3e00758 100644
--- a/core/java/android/window/SplashScreen.java
+++ b/core/java/android/window/SplashScreen.java
@@ -16,6 +16,7 @@
package android.window;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.StyleRes;
import android.annotation.SuppressLint;
@@ -42,6 +43,24 @@
*/
public interface SplashScreen {
/**
+ * Force splash screen to be empty.
+ * @hide
+ */
+ int SPLASH_SCREEN_STYLE_EMPTY = 0;
+ /**
+ * Force splash screen to show icon.
+ * @hide
+ */
+ int SPLASH_SCREEN_STYLE_ICON = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "SPLASH_SCREEN_STYLE_" }, value = {
+ SPLASH_SCREEN_STYLE_EMPTY,
+ SPLASH_SCREEN_STYLE_ICON
+ })
+ @interface SplashScreenStyle {}
+
+ /**
* <p>Specifies whether an {@link Activity} wants to handle the splash screen animation on its
* own. Normally the splash screen will show on screen before the content of the activity has
* been drawn, and disappear when the activity is showing on the screen. With this listener set,
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index f718d40..418d16e 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -168,7 +168,7 @@
* @param packageName the package name of the app whose changes will be overridden
* @throws SecurityException if overriding changes is not permitted
*/
- void setOverridesOnReleaseBuilds(in CompatibilityOverrideConfig overrides, in String packageName);
+ void putOverridesOnReleaseBuilds(in CompatibilityOverrideConfig overrides, in String packageName);
/**
* Adds overrides to compatibility changes.
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index f2ac87e..666ab95 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -65,6 +65,7 @@
# These are highly common-use files
per-file Android.bp = file:/graphics/java/android/graphics/OWNERS
per-file AndroidRuntime.cpp = file:/graphics/java/android/graphics/OWNERS
+per-file AndroidRuntime.cpp = calin@google.com, ngeoffray@google.com, oth@google.com
# Although marked "view" this is mostly graphics stuff
per-file android_view_* = file:/graphics/java/android/graphics/OWNERS
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index bf59f4d..5ac2336 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4980,6 +4980,11 @@
<!-- List containing the allowed install sources for accessibility service. -->
<string-array name="config_accessibility_allowed_install_source" translatable="false"/>
+ <!-- Default value for Settings.ASSIST_LONG_PRESS_HOME_ENABLED -->
+ <bool name="config_assistLongPressHomeEnabledDefault">true</bool>
+ <!-- Default value for Settings.ASSIST_TOUCH_GESTURE_ENABLED -->
+ <bool name="config_assistTouchGestureEnabledDefault">true</bool>
+
<!-- The amount of dimming to apply to wallpapers with mid range luminance. 0 displays
the wallpaper at full brightness. 1 displays the wallpaper as fully black. -->
<item name="config_wallpaperDimAmount" format="float" type="dimen">0.05</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7006d93..b574415 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4394,5 +4394,8 @@
<java-symbol type="dimen" name="starting_surface_icon_size" />
<java-symbol type="dimen" name="starting_surface_default_icon_size" />
+ <java-symbol type="bool" name="config_assistLongPressHomeEnabledDefault" />
+ <java-symbol type="bool" name="config_assistTouchGestureEnabledDefault" />
+
<java-symbol type="dimen" name="config_wallpaperDimAmount" />
</resources>
diff --git a/core/tests/coretests/apks/overlay_config/Android.bp b/core/tests/coretests/apks/overlay_config/Android.bp
index 9c971fd..0777523 100644
--- a/core/tests/coretests/apks/overlay_config/Android.bp
+++ b/core/tests/coretests/apks/overlay_config/Android.bp
@@ -10,4 +10,5 @@
android_test_helper_app {
name: "FrameworksCoreTests_overlay_config",
defaults: ["FrameworksCoreTests_apks_defaults"],
+ min_sdk_version: "20",
}
diff --git a/core/tests/coretests/apks/overlay_config/AndroidManifest.xml b/core/tests/coretests/apks/overlay_config/AndroidManifest.xml
index b15338e..092a575 100644
--- a/core/tests/coretests/apks/overlay_config/AndroidManifest.xml
+++ b/core/tests/coretests/apks/overlay_config/AndroidManifest.xml
@@ -19,7 +19,7 @@
<application android:hasCode="false" />
- <uses-sdk android:targetSdkVersion="21"/>
+ <uses-sdk android:minSdkVersion="20" android:targetSdkVersion="21"/>
<overlay android:targetPackage="android"
android:targetName="TestResources" />
diff --git a/core/tests/coretests/apks/overlay_config/OWNERS b/core/tests/coretests/apks/overlay_config/OWNERS
new file mode 100644
index 0000000..3e79d8f
--- /dev/null
+++ b/core/tests/coretests/apks/overlay_config/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/content/res/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OWNERS b/core/tests/coretests/src/com/android/internal/content/res/OWNERS
new file mode 100644
index 0000000..3e79d8f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/res/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/content/res/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/content/OverlayConfigIterationRule.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java
similarity index 98%
rename from core/tests/coretests/src/com/android/internal/content/OverlayConfigIterationRule.java
rename to core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java
index a01459f..c50c818 100644
--- a/core/tests/coretests/src/com/android/internal/content/OverlayConfigIterationRule.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.content;
+package com.android.internal.content.res;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
diff --git a/core/tests/coretests/src/com/android/internal/content/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
similarity index 99%
rename from core/tests/coretests/src/com/android/internal/content/OverlayConfigTest.java
rename to core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index dee118f..178c2dd 100644
--- a/core/tests/coretests/src/com/android/internal/content/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.content;
+package com.android.internal.content.res;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 99d7167..240d056 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -497,6 +497,8 @@
<privapp-permissions package="com.android.statementservice">
<permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
+ <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
</privapp-permissions>
<privapp-permissions package="com.android.traceur">
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 1163536..d757a8c 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -39,11 +39,7 @@
<axis tag="wdth" stylevalue="100" />
<axis tag="wght" stylevalue="300" />
</font>
- <font weight="400" style="normal">Roboto-Regular.ttf
- <axis tag="ital" stylevalue="0" />
- <axis tag="wdth" stylevalue="100" />
- <axis tag="wght" stylevalue="400" />
- </font>
+ <font weight="400" style="normal">RobotoStatic-Regular.ttf</font>
<font weight="500" style="normal">Roboto-Regular.ttf
<axis tag="ital" stylevalue="0" />
<axis tag="wdth" stylevalue="100" />
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index b88751a..61f7fac 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1387,6 +1387,7 @@
static {
// Preload Roboto-Regular.ttf in Zygote for improving app launch performance.
preloadFontFile("/system/fonts/Roboto-Regular.ttf");
+ preloadFontFile("/system/fonts/RobotoStatic-Regular.ttf");
String locale = SystemProperties.get("persist.sys.locale", "en-US");
String script = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale)).getScript();
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 73e65c2..0d8715b 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -182,6 +182,7 @@
private Canvas mMaskCanvas;
private Matrix mMaskMatrix;
private PorterDuffColorFilter mMaskColorFilter;
+ private PorterDuffColorFilter mFocusColorFilter;
private boolean mHasValidMask;
private int mComputedRadius = -1;
@@ -938,7 +939,7 @@
final int alpha = Math.min((int) (origAlpha * newOpacity + 0.5f), 255);
if (alpha > 0) {
ColorFilter origFilter = p.getColorFilter();
- p.setColorFilter(mMaskColorFilter);
+ p.setColorFilter(mFocusColorFilter);
p.setAlpha(alpha);
c.drawCircle(cx, cy, getComputedRadius(), p);
p.setAlpha(origAlpha);
@@ -1091,6 +1092,7 @@
if (mMaskColorFilter == null) {
mMaskColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_IN);
+ mFocusColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_IN);
}
// Draw the appropriate mask anchored to (0,0).
@@ -1219,6 +1221,8 @@
int maskColor = mState.mRippleStyle == STYLE_PATTERNED ? color : color | 0xFF000000;
if (mMaskColorFilter.getColor() != maskColor) {
mMaskColorFilter = new PorterDuffColorFilter(maskColor, mMaskColorFilter.getMode());
+ mFocusColorFilter = new PorterDuffColorFilter(color | 0xFF000000,
+ mFocusColorFilter.getMode());
}
p.setColor(color & 0xFF000000);
p.setColorFilter(mMaskColorFilter);
diff --git a/keystore/java/android/security/GenerateRkpKey.java b/keystore/java/android/security/GenerateRkpKey.java
index a1a7aa8..cc1ec1b 100644
--- a/keystore/java/android/security/GenerateRkpKey.java
+++ b/keystore/java/android/security/GenerateRkpKey.java
@@ -22,6 +22,10 @@
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner
@@ -41,14 +45,25 @@
* @hide
*/
public class GenerateRkpKey {
+ private static final String TAG = "GenerateRkpKey";
+
+ private static final int NOTIFY_EMPTY = 0;
+ private static final int NOTIFY_KEY_GENERATED = 1;
+ private static final int TIMEOUT_MS = 1000;
private IGenerateRkpKeyService mBinder;
private Context mContext;
+ private CountDownLatch mCountDownLatch;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mBinder = IGenerateRkpKeyService.Stub.asInterface(service);
+ mCountDownLatch.countDown();
+ }
+
+ @Override public void onBindingDied(ComponentName className) {
+ mCountDownLatch.countDown();
}
@Override
@@ -64,36 +79,51 @@
mContext = context;
}
- /**
- * Fulfills the use case of (2) described in the class documentation. Blocks until the
- * RemoteProvisioner application can get new attestation keys signed by the server.
- */
- public void notifyEmpty(int securityLevel) throws RemoteException {
+ private void bindAndSendCommand(int command, int securityLevel) throws RemoteException {
Intent intent = new Intent(IGenerateRkpKeyService.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+ if (comp == null) {
+ throw new RemoteException("Could not resolve GenerateRkpKeyService.");
+ }
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
- throw new RemoteException("Failed to bind to GenerateKeyService");
+ mCountDownLatch = new CountDownLatch(1);
+ if (!mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
+ throw new RemoteException("Failed to bind to GenerateRkpKeyService");
+ }
+ try {
+ mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted: ", e);
}
if (mBinder != null) {
- mBinder.generateKey(securityLevel);
+ switch (command) {
+ case NOTIFY_EMPTY:
+ mBinder.generateKey(securityLevel);
+ break;
+ case NOTIFY_KEY_GENERATED:
+ mBinder.notifyKeyGenerated(securityLevel);
+ break;
+ default:
+ Log.e(TAG, "Invalid case for command");
+ }
+ } else {
+ Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService.");
}
mContext.unbindService(mConnection);
}
/**
- * FUlfills the use case of (1) described in the class documentation. Non blocking call.
+ * Fulfills the use case of (2) described in the class documentation. Blocks until the
+ * RemoteProvisioner application can get new attestation keys signed by the server.
+ */
+ public void notifyEmpty(int securityLevel) throws RemoteException {
+ bindAndSendCommand(NOTIFY_EMPTY, securityLevel);
+ }
+
+ /**
+ * Fulfills the use case of (1) described in the class documentation. Non blocking call.
*/
public void notifyKeyGenerated(int securityLevel) throws RemoteException {
- Intent intent = new Intent(IGenerateRkpKeyService.class.getName());
- ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
- intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
- throw new RemoteException("Failed to bind to GenerateKeyService");
- }
- if (mBinder != null) {
- mBinder.notifyKeyGenerated(securityLevel);
- }
- mContext.unbindService(mConnection);
+ bindAndSendCommand(NOTIFY_KEY_GENERATED, securityLevel);
}
}
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
index 4da2a28..67484d4 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -21,18 +21,13 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
-import android.os.Build;
-import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterCertificateChain;
-import android.security.keymaster.KeymasterDefs;
-import android.telephony.TelephonyManager;
-import android.util.ArraySet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
+import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
@@ -41,7 +36,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Random;
-import java.util.Set;
/**
* Utilities for attesting the device's hardware identifiers.
@@ -110,92 +104,6 @@
}
}
- @NonNull private static KeymasterArguments prepareAttestationArgumentsForDeviceId(
- Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
- DeviceIdAttestationException {
- // Verify that device ID attestation types are provided.
- if (idTypes == null) {
- throw new NullPointerException("Missing id types");
- }
-
- return prepareAttestationArguments(context, idTypes, attestationChallenge);
- }
-
- /**
- * Prepares Keymaster Arguments with attestation data.
- * @hide should only be used by KeyChain.
- */
- @NonNull public static KeymasterArguments prepareAttestationArguments(Context context,
- @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
- DeviceIdAttestationException {
- // Check method arguments, retrieve requested device IDs and prepare attestation arguments.
- if (attestationChallenge == null) {
- throw new NullPointerException("Missing attestation challenge");
- }
- final KeymasterArguments attestArgs = new KeymasterArguments();
- attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, attestationChallenge);
- // Return early if the caller did not request any device identifiers to be included in the
- // attestation record.
- if (idTypes == null) {
- return attestArgs;
- }
- final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
- for (int idType : idTypes) {
- idTypesSet.add(idType);
- }
- TelephonyManager telephonyService = null;
- if (idTypesSet.contains(ID_TYPE_IMEI) || idTypesSet.contains(ID_TYPE_MEID)) {
- telephonyService = (TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE);
- if (telephonyService == null) {
- throw new DeviceIdAttestationException("Unable to access telephony service");
- }
- }
- for (final Integer idType : idTypesSet) {
- switch (idType) {
- case ID_TYPE_SERIAL:
- attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
- Build.getSerial().getBytes(StandardCharsets.UTF_8));
- break;
- case ID_TYPE_IMEI: {
- final String imei = telephonyService.getImei(0);
- if (imei == null) {
- throw new DeviceIdAttestationException("Unable to retrieve IMEI");
- }
- attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
- imei.getBytes(StandardCharsets.UTF_8));
- break;
- }
- case ID_TYPE_MEID: {
- final String meid = telephonyService.getMeid(0);
- if (meid == null) {
- throw new DeviceIdAttestationException("Unable to retrieve MEID");
- }
- attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
- meid.getBytes(StandardCharsets.UTF_8));
- break;
- }
- case USE_INDIVIDUAL_ATTESTATION: {
- attestArgs.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION);
- break;
- }
- default:
- throw new IllegalArgumentException("Unknown device ID type " + idType);
- }
- }
- attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
- Build.BRAND.getBytes(StandardCharsets.UTF_8));
- attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
- Build.DEVICE.getBytes(StandardCharsets.UTF_8));
- attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
- Build.PRODUCT.getBytes(StandardCharsets.UTF_8));
- attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
- Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8));
- attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL,
- Build.MODEL.getBytes(StandardCharsets.UTF_8));
- return attestArgs;
- }
-
/**
* Performs attestation of the device's identifiers. This method returns a certificate chain
* whose first element contains the requested device identifiers in an extension. The device's
@@ -229,6 +137,13 @@
@NonNull public static X509Certificate[] attestDeviceIds(Context context,
@NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
DeviceIdAttestationException {
+ if (attestationChallenge == null) {
+ throw new NullPointerException("Missing attestation challenge");
+ }
+ if (idTypes == null) {
+ throw new NullPointerException("Missing id types");
+ }
+
String keystoreAlias = generateRandomAlias();
KeyGenParameterSpec.Builder builder =
new KeyGenParameterSpec.Builder(keystoreAlias, KeyProperties.PURPOSE_SIGN)
@@ -265,6 +180,12 @@
if (e.getCause() instanceof DeviceIdAttestationException) {
throw (DeviceIdAttestationException) e.getCause();
}
+ // Illegal argument errors are wrapped up by a ProviderException. Catch those so that
+ // we can unwrap them into a more meaningful exception type for the caller.
+ if (e instanceof ProviderException
+ && e.getCause() instanceof IllegalArgumentException) {
+ throw (IllegalArgumentException) e.getCause();
+ }
throw new DeviceIdAttestationException("Unable to perform attestation", e);
}
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index dc7f3dd..c048f3b 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -580,7 +580,7 @@
} catch (RemoteException e) {
// This is not really an error state, and necessarily does not apply to non RKP
// systems or hybrid systems where RKP is not currently turned on.
- Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.");
+ Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e);
}
success = true;
return new KeyPair(publicKey, publicKey.getPrivateKey());
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 10df726..8881be7 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -19,7 +19,7 @@
package="com.android.wm.shell">
<!-- System permission required by WM Shell Task Organizer. -->
<uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT" />
- <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
</manifest>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index dddf2c1..70f03d2 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -44,7 +44,7 @@
<dimen name="pip_bottom_offset_buffer">1dp</dimen>
<!-- The corner radius for PiP window. -->
- <dimen name="pip_corner_radius">8dp</dimen>
+ <dimen name="pip_corner_radius">16dp</dimen>
<!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. -->
<dimen name="pip_dismiss_text_bottom_margin">24dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index a88be31..f7fb63d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -46,6 +46,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.startingsurface.StartingWindowController;
@@ -55,6 +56,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.function.Consumer;
/**
* Unified task organizer for all components in the shell.
@@ -342,6 +344,19 @@
notifySizeCompatUI(info.getTaskInfo(), listener);
}
+ /**
+ * Take a screenshot of a task.
+ */
+ public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
+ Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
+ final TaskAppearedInfo info = mTasks.get(taskInfo.taskId);
+ if (info == null) {
+ return;
+ }
+ ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer);
+ }
+
+
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
synchronized (mLock) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index 2fc696c..35a4f33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
@@ -231,7 +231,8 @@
* Fade animation for consecutive flyouts.
*/
void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, PointF stackPos,
- boolean hideDot) {
+ boolean hideDot, Runnable onHide) {
+ mOnHide = onHide;
final Runnable afterFadeOut = () -> {
updateFlyoutMessage(flyoutMessage, parentWidth);
// Wait for TextViews to layout with updated height.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index d821c6f..8613dcb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1492,22 +1492,22 @@
mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
}
- if (getBubbleCount() == 0) {
- mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
- }
-
if (bubble.getIconView() == null) {
return;
}
+ mBubbleContainer.addView(bubble.getIconView(), 0,
+ new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
+ mPositioner.getBubbleSize()));
+
+ if (getBubbleCount() == 0) {
+ mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
+ }
// Set the dot position to the opposite of the side the stack is resting on, since the stack
// resting slightly off-screen would result in the dot also being off-screen.
bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */);
bubble.getIconView().setOnClickListener(mBubbleClickListener);
bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
- mBubbleContainer.addView(bubble.getIconView(), 0,
- new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
- mPositioner.getBubbleSize()));
updateBubbleShadows(false /* showForAllBubbles */);
animateInFlyoutForBubble(bubble);
requestUpdate();
@@ -2400,7 +2400,8 @@
if (mFlyout.getVisibility() == View.VISIBLE) {
mFlyout.animateUpdate(bubble.getFlyoutMessage(), getWidth(),
- mStackAnimationController.getStackPosition(), !bubble.showDot());
+ mStackAnimationController.getStackPosition(), !bubble.showDot(),
+ mAfterFlyoutHidden /* onHide */);
} else {
mFlyout.setVisibility(INVISIBLE);
mFlyout.setupFlyoutStartingAsDot(bubble.getFlyoutMessage(),
@@ -2408,7 +2409,7 @@
mStackAnimationController.isStackOnLeftSide(),
bubble.getIconView().getDotColor() /* dotColor */,
expandFlyoutAfterDelay /* onLayoutComplete */,
- mAfterFlyoutHidden,
+ mAfterFlyoutHidden /* onHide */,
bubble.getIconView().getDotCenter(),
!bubble.showDot(),
mPositioner);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 8043d2b..12d55b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -124,9 +124,6 @@
*/
private Rect mAnimatingToBounds = new Rect();
- /** Initial starting location for the stack. */
- @Nullable private BubbleStackView.RelativeStackPosition mStackStartPosition;
-
/** Whether or not the stack's start position has been set. */
private boolean mStackMovedToStartPosition = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
index 32575fa..eea6e3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
@@ -21,6 +21,8 @@
import android.graphics.Rect;
import android.view.SurfaceControl;
+import java.util.function.Consumer;
+
/**
* Helpers for working with screenshots.
*/
@@ -29,6 +31,60 @@
/**
* Take a screenshot of the specified SurfaceControl.
*
+ * @param sc the SurfaceControl to take a screenshot of
+ * @param crop the crop to use when capturing the screenshot
+ * @param consumer Consumer for the captured buffer
+ */
+ public static void captureLayer(SurfaceControl sc, Rect crop,
+ Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
+ consumer.accept(SurfaceControl.captureLayers(
+ new SurfaceControl.LayerCaptureArgs.Builder(sc)
+ .setSourceCrop(crop)
+ .setCaptureSecureLayers(true)
+ .setAllowProtected(true)
+ .build()));
+ }
+
+ private static class BufferConsumer implements
+ Consumer<SurfaceControl.ScreenshotHardwareBuffer> {
+ SurfaceControl mScreenshot = null;
+ SurfaceControl.Transaction mTransaction;
+ SurfaceControl mSurfaceControl;
+ int mLayer;
+
+ BufferConsumer(SurfaceControl.Transaction t, SurfaceControl sc, int layer) {
+ mTransaction = t;
+ mSurfaceControl = sc;
+ mLayer = layer;
+ }
+
+ @Override
+ public void accept(SurfaceControl.ScreenshotHardwareBuffer buffer) {
+ if (buffer == null || buffer.getHardwareBuffer() == null) {
+ return;
+ }
+ final GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
+ buffer.getHardwareBuffer());
+ mScreenshot = new SurfaceControl.Builder()
+ .setName("ScreenshotUtils screenshot")
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .setSecure(buffer.containsSecureLayers())
+ .setCallsite("ScreenshotUtils.takeScreenshot")
+ .setBLASTLayer()
+ .build();
+
+ mTransaction.setBuffer(mScreenshot, graphicBuffer);
+ mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace());
+ mTransaction.reparent(mScreenshot, mSurfaceControl);
+ mTransaction.setLayer(mScreenshot, mLayer);
+ mTransaction.show(mScreenshot);
+ mTransaction.apply();
+ }
+ }
+
+ /**
+ * Take a screenshot of the specified SurfaceControl.
+ *
* @param t the transaction used to set changes on the resulting screenshot.
* @param sc the SurfaceControl to take a screenshot of
* @param crop the crop to use when capturing the screenshot
@@ -38,32 +94,8 @@
*/
public static SurfaceControl takeScreenshot(SurfaceControl.Transaction t, SurfaceControl sc,
Rect crop, int layer) {
- final SurfaceControl.ScreenshotHardwareBuffer buffer = SurfaceControl.captureLayers(
- new SurfaceControl.LayerCaptureArgs.Builder(sc)
- .setSourceCrop(crop)
- .setCaptureSecureLayers(true)
- .setAllowProtected(true)
- .build()
- );
- if (buffer == null || buffer.getHardwareBuffer() == null) {
- return null;
- }
- final GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
- buffer.getHardwareBuffer());
- final SurfaceControl screenshot = new SurfaceControl.Builder()
- .setName("ScreenshotUtils screenshot")
- .setFormat(PixelFormat.TRANSLUCENT)
- .setSecure(buffer.containsSecureLayers())
- .setCallsite("ScreenshotUtils.takeScreenshot")
- .setBLASTLayer()
- .build();
-
- t.setBuffer(screenshot, graphicBuffer);
- t.setColorSpace(screenshot, buffer.getColorSpace());
- t.reparent(screenshot, sc);
- t.setLayer(screenshot, layer);
- t.show(screenshot);
- t.apply();
- return screenshot;
+ BufferConsumer consumer = new BufferConsumer(t, sc, layer);
+ captureLayer(sc, crop, consumer);
+ return consumer.mScreenshot;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index a2c6567..c46b559 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -435,7 +435,8 @@
SurfaceControl.Transaction tx, float fraction) {
final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction;
setCurrentValue(alpha);
- getSurfaceTransactionHelper().alpha(tx, leash, alpha);
+ getSurfaceTransactionHelper().alpha(tx, leash, alpha)
+ .round(tx, leash, shouldApplyCornerRadius());
tx.apply();
}
@@ -526,16 +527,22 @@
float angle = (1.0f - fraction) * startingAngle;
setCurrentValue(bounds);
if (inScaleTransition() || sourceHintRect == null) {
-
if (isOutPipDirection) {
getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
} else {
- getSurfaceTransactionHelper().scale(tx, leash, base, bounds, angle);
+ getSurfaceTransactionHelper().scale(tx, leash, base, bounds, angle)
+ .round(tx, leash, base, bounds);
}
} else {
final Rect insets = computeInsets(fraction);
getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
initialSourceValue, bounds, insets);
+ if (shouldApplyCornerRadius()) {
+ final Rect destinationBounds = new Rect(bounds);
+ destinationBounds.inset(insets);
+ getSurfaceTransactionHelper().round(tx, leash,
+ initialContainerRect, destinationBounds);
+ }
}
if (!handlePipTransaction(leash, tx, bounds)) {
tx.apply();
@@ -564,9 +571,11 @@
x = fraction * (end.left - start.left) + start.left;
y = fraction * (end.bottom - start.top) + start.top;
}
- getSurfaceTransactionHelper().rotateAndScaleWithCrop(tx, leash,
- initialContainerRect, bounds, insets, degree, x, y, isOutPipDirection,
- rotationDelta == ROTATION_270 /* clockwise */);
+ getSurfaceTransactionHelper()
+ .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
+ insets, degree, x, y, isOutPipDirection,
+ rotationDelta == ROTATION_270 /* clockwise */)
+ .round(tx, leash, initialContainerRect, bounds);
tx.apply();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 48a15d8..9fa3f69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -186,6 +186,18 @@
}
/**
+ * Operates the round corner radius on a given transaction and leash, scaled by bounds
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect fromBounds, Rect toBounds) {
+ final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height())
+ / Math.hypot(toBounds.width(), toBounds.height()));
+ tx.setCornerRadius(leash, mCornerRadius * scale);
+ return this;
+ }
+
+ /**
* Re-parents the snapshot to the parent's surface control and shows it.
*/
public PipSurfaceTransactionHelper reparentAndShowSurfaceSnapshot(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e1b198c..7d032ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -184,6 +184,7 @@
mTaskOrganizer.applyTransaction(wct);
// The final task bounds will be applied by onFixedRotationFinished so that all
// coordinates are in new rotation.
+ mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
mDeferredAnimEndTransaction = tx;
return;
}
@@ -1041,7 +1042,9 @@
}
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, toBounds, degrees);
+ mSurfaceTransactionHelper
+ .scale(tx, mLeash, startBounds, toBounds, degrees)
+ .round(tx, mLeash, startBounds, toBounds);
if (mPipMenuController.isMenuVisible()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
@@ -1216,6 +1219,7 @@
// Just a resize in PIP
taskBounds = destinationBounds;
}
+ mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
wct.setBounds(mToken, taskBounds);
wct.setBoundsChangeTransaction(mToken, tx);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 1d37a12..51a67e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -22,7 +22,10 @@
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.app.ActivityThread;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -34,15 +37,19 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
+import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Trace;
+import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.View;
import android.window.SplashScreenView;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.Quantizer;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
@@ -51,6 +58,8 @@
import java.util.List;
import java.util.function.Consumer;
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
/**
* Util class to create the view for a splash screen content.
@@ -76,9 +85,12 @@
private int mBrandingImageWidth;
private int mBrandingImageHeight;
private int mMainWindowShiftLength;
+ private int mLastPackageContextConfigHash;
private final TransactionPool mTransactionPool;
private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
private final Handler mSplashscreenWorkerHandler;
+ @VisibleForTesting
+ final ColorCache mColorCache;
SplashscreenContentDrawer(Context context, TransactionPool pool) {
mContext = context;
@@ -92,6 +104,7 @@
new HandlerThread("wmshell.splashworker", THREAD_PRIORITY_TOP_APP_BOOST);
shellSplashscreenWorkerThread.start();
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
+ mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
}
/**
@@ -183,14 +196,14 @@
updateDensity();
getWindowAttrs(context, mTmpAttrs);
- final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
- final int themeBGColor = peekWindowBGColor(context, this.mTmpAttrs);
+ mLastPackageContextConfigHash = context.getResources().getConfiguration().hashCode();
+ final int themeBGColor = mColorCache.getWindowColor(ai.packageName,
+ mLastPackageContextConfigHash, mTmpAttrs.mWindowBgColor, mTmpAttrs.mWindowBgResId,
+ () -> peekWindowBGColor(context, mTmpAttrs)).mBgColor;
// TODO (b/173975965) Tracking the performance on improved splash screen.
- return builder
- .setContext(context)
+ return new StartingWindowViewBuilder(context, ai)
.setWindowBGColor(themeBGColor)
.makeEmptyView(emptyView)
- .setActivityInfo(ai)
.build();
}
@@ -232,50 +245,30 @@
}
private class StartingWindowViewBuilder {
- private ActivityInfo mActivityInfo;
- private Context mContext;
- private boolean mEmptyView;
+ private final Context mContext;
+ private final ActivityInfo mActivityInfo;
- // result
- private boolean mBuildComplete = false;
- private SplashScreenView mCachedResult;
+ private boolean mEmptyView;
private int mThemeColor;
private Drawable mFinalIconDrawable;
private int mFinalIconSize = mIconSize;
+ StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
+ mContext = context;
+ mActivityInfo = aInfo;
+ }
+
StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
mThemeColor = background;
- mBuildComplete = false;
return this;
}
StartingWindowViewBuilder makeEmptyView(boolean empty) {
mEmptyView = empty;
- mBuildComplete = false;
- return this;
- }
-
- StartingWindowViewBuilder setActivityInfo(ActivityInfo ai) {
- mActivityInfo = ai;
- mBuildComplete = false;
- return this;
- }
-
- StartingWindowViewBuilder setContext(Context context) {
- mContext = context;
- mBuildComplete = false;
return this;
}
SplashScreenView build() {
- if (mBuildComplete) {
- return mCachedResult;
- }
- if (mContext == null || mActivityInfo == null) {
- Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
- return null;
- }
-
Drawable iconDrawable;
final int animationDuration;
if (mEmptyView) {
@@ -292,7 +285,9 @@
final int densityDpi = mContext.getResources().getDisplayMetrics().densityDpi;
final int scaledIconDpi =
(int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon");
iconDrawable = mIconProvider.getIcon(mActivityInfo, scaledIconDpi);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (iconDrawable == null) {
iconDrawable = mContext.getPackageManager().getDefaultActivityIcon();
}
@@ -306,9 +301,7 @@
animationDuration = 0;
}
- mCachedResult = fillViewWithIcon(mFinalIconSize, mFinalIconDrawable, animationDuration);
- mBuildComplete = true;
- return mCachedResult;
+ return fillViewWithIcon(mFinalIconSize, mFinalIconDrawable, animationDuration);
}
private void createIconDrawable(Drawable iconDrawable, boolean legacy) {
@@ -331,28 +324,20 @@
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon");
- final AdaptiveIconDrawable adaptiveIconDrawable =
- (AdaptiveIconDrawable) iconDrawable;
- final DrawableColorTester backIconTester =
- new DrawableColorTester(adaptiveIconDrawable.getBackground());
-
+ final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) iconDrawable;
final Drawable iconForeground = adaptiveIconDrawable.getForeground();
- final DrawableColorTester foreIconTester =
- new DrawableColorTester(iconForeground, true /* filterTransparent */);
-
- final boolean foreComplex = foreIconTester.isComplexColor();
- final int foreMainColor = foreIconTester.getDominateColor();
+ final ColorCache.IconColor iconColor = mColorCache.getIconColor(
+ mActivityInfo.packageName, mActivityInfo.getIconResource(),
+ mLastPackageContextConfigHash,
+ () -> new DrawableColorTester(iconForeground, true /* filterTransparent */),
+ () -> new DrawableColorTester(adaptiveIconDrawable.getBackground()));
if (DEBUG) {
- Slog.d(TAG, "foreground complex color? " + foreComplex + " main color: "
- + Integer.toHexString(foreMainColor));
- }
- final boolean backComplex = backIconTester.isComplexColor();
- final int backMainColor = backIconTester.getDominateColor();
- if (DEBUG) {
- Slog.d(TAG, "background complex color? " + backComplex + " main color: "
- + Integer.toHexString(backMainColor));
- Slog.d(TAG, "theme color? " + Integer.toHexString(mThemeColor));
+ Slog.d(TAG, "FgMainColor=" + Integer.toHexString(iconColor.mFgColor)
+ + " BgMainColor=" + Integer.toHexString(iconColor.mBgColor)
+ + " IsBgComplex=" + iconColor.mIsBgComplex
+ + " FromCache=" + (iconColor.mReuseCount > 0)
+ + " ThemeColor=" + Integer.toHexString(mThemeColor));
}
// Only draw the foreground of AdaptiveIcon to the splash screen if below condition
@@ -363,17 +348,17 @@
// C. The background of the adaptive icon is grayscale, and the foreground of the
// adaptive icon forms a certain contrast with the theme color.
// D. Didn't specify icon background color.
- if (!backComplex && mTmpAttrs.mIconBgColor == Color.TRANSPARENT
- && (isRgbSimilarInHsv(mThemeColor, backMainColor)
- || (backIconTester.isGrayscale()
- && !isRgbSimilarInHsv(mThemeColor, foreMainColor)))) {
+ if (!iconColor.mIsBgComplex && mTmpAttrs.mIconBgColor == Color.TRANSPARENT
+ && (isRgbSimilarInHsv(mThemeColor, iconColor.mBgColor)
+ || (iconColor.mIsBgGrayscale
+ && !isRgbSimilarInHsv(mThemeColor, iconColor.mFgColor)))) {
if (DEBUG) {
Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
}
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
// scale by 192/160 if we only draw adaptiveIcon's foreground.
final float noBgScale =
- foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD
+ iconColor.mFgNonTransparentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD
? NO_BACKGROUND_SCALE : 1f;
// Using AdaptiveIconDrawable here can help keep the shape consistent with the
// current settings.
@@ -689,6 +674,150 @@
}
}
+ /** Cache the result of {@link DrawableColorTester} to reduce expensive calculation. */
+ @VisibleForTesting
+ static class ColorCache extends BroadcastReceiver {
+ /**
+ * The color may be different according to resource id and configuration (e.g. night mode),
+ * so this allows to cache more than one color per package.
+ */
+ private static final int CACHE_SIZE = 2;
+
+ /** The computed colors of packages. */
+ private final ArrayMap<String, Colors> mColorMap = new ArrayMap<>();
+
+ private static class Colors {
+ final WindowColor[] mWindowColors = new WindowColor[CACHE_SIZE];
+ final IconColor[] mIconColors = new IconColor[CACHE_SIZE];
+ }
+
+ private static class Cache {
+ /** The hash used to check whether this cache is hit. */
+ final int mHash;
+
+ /** The number of times this cache has been reused. */
+ int mReuseCount;
+
+ Cache(int hash) {
+ mHash = hash;
+ }
+ }
+
+ static class WindowColor extends Cache {
+ final int mBgColor;
+
+ WindowColor(int hash, int bgColor) {
+ super(hash);
+ mBgColor = bgColor;
+ }
+ }
+
+ static class IconColor extends Cache {
+ final int mFgColor;
+ final int mBgColor;
+ final boolean mIsBgComplex;
+ final boolean mIsBgGrayscale;
+ final float mFgNonTransparentRatio;
+
+ IconColor(int hash, int fgColor, int bgColor, boolean isBgComplex,
+ boolean isBgGrayscale, float fgNonTransparentRatio) {
+ super(hash);
+ mFgColor = fgColor;
+ mBgColor = bgColor;
+ mIsBgComplex = isBgComplex;
+ mIsBgGrayscale = isBgGrayscale;
+ mFgNonTransparentRatio = fgNonTransparentRatio;
+ }
+ }
+
+ ColorCache(Context context, Handler handler) {
+ // This includes reinstall and uninstall.
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+ context.registerReceiverAsUser(this, UserHandle.ALL, filter,
+ null /* broadcastPermission */, handler);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Uri packageUri = intent.getData();
+ if (packageUri != null) {
+ mColorMap.remove(packageUri.getEncodedSchemeSpecificPart());
+ }
+ }
+
+ /**
+ * Gets the existing cache if the hash matches. If null is returned, the caller can use
+ * outLeastUsedIndex to put the new cache.
+ */
+ private static <T extends Cache> T getCache(T[] caches, int hash, int[] outLeastUsedIndex) {
+ int minReuseCount = Integer.MAX_VALUE;
+ for (int i = 0; i < CACHE_SIZE; i++) {
+ final T cache = caches[i];
+ if (cache == null) {
+ // Empty slot has the highest priority to put new cache.
+ minReuseCount = -1;
+ outLeastUsedIndex[0] = i;
+ continue;
+ }
+ if (cache.mHash == hash) {
+ cache.mReuseCount++;
+ return cache;
+ }
+ if (cache.mReuseCount < minReuseCount) {
+ minReuseCount = cache.mReuseCount;
+ outLeastUsedIndex[0] = i;
+ }
+ }
+ return null;
+ }
+
+ @NonNull WindowColor getWindowColor(String packageName, int configHash, int windowBgColor,
+ int windowBgResId, IntSupplier windowBgColorSupplier) {
+ Colors colors = mColorMap.get(packageName);
+ int hash = 31 * configHash + windowBgColor;
+ hash = 31 * hash + windowBgResId;
+ final int[] leastUsedIndex = { 0 };
+ if (colors != null) {
+ final WindowColor windowColor = getCache(colors.mWindowColors, hash,
+ leastUsedIndex);
+ if (windowColor != null) {
+ return windowColor;
+ }
+ } else {
+ colors = new Colors();
+ mColorMap.put(packageName, colors);
+ }
+ final WindowColor windowColor = new WindowColor(hash, windowBgColorSupplier.getAsInt());
+ colors.mWindowColors[leastUsedIndex[0]] = windowColor;
+ return windowColor;
+ }
+
+ @NonNull IconColor getIconColor(String packageName, int configHash, int iconResId,
+ Supplier<DrawableColorTester> fgColorTesterSupplier,
+ Supplier<DrawableColorTester> bgColorTesterSupplier) {
+ Colors colors = mColorMap.get(packageName);
+ final int hash = configHash * 31 + iconResId;
+ final int[] leastUsedIndex = { 0 };
+ if (colors != null) {
+ final IconColor iconColor = getCache(colors.mIconColors, hash, leastUsedIndex);
+ if (iconColor != null) {
+ return iconColor;
+ }
+ } else {
+ colors = new Colors();
+ mColorMap.put(packageName, colors);
+ }
+ final DrawableColorTester fgTester = fgColorTesterSupplier.get();
+ final DrawableColorTester bgTester = bgColorTesterSupplier.get();
+ final IconColor iconColor = new IconColor(hash, fgTester.getDominateColor(),
+ bgTester.getDominateColor(), bgTester.isComplexColor(), bgTester.isGrayscale(),
+ fgTester.nonTransparentRatio());
+ colors.mIconColors[leastUsedIndex[0]] = iconColor;
+ return iconColor;
+ }
+ }
+
/**
* Create and play the default exit animation for splash screen view.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index ff91d82..8463da6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.startingsurface;
import static android.content.Context.CONTEXT_RESTRICTED;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -34,6 +35,7 @@
import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
@@ -49,8 +51,10 @@
import android.window.TaskSnapshot;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import java.util.function.Supplier;
@@ -90,6 +94,7 @@
* => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
* directly).
*/
+@ShellSplashscreenThread
public class StartingSurfaceDrawer {
static final String TAG = StartingSurfaceDrawer.class.getSimpleName();
static final boolean DEBUG_SPLASH_SCREEN = StartingWindowController.DEBUG_SPLASH_SCREEN;
@@ -98,7 +103,8 @@
private final Context mContext;
private final DisplayManager mDisplayManager;
private final ShellExecutor mSplashScreenExecutor;
- private final SplashscreenContentDrawer mSplashscreenContentDrawer;
+ @VisibleForTesting
+ final SplashscreenContentDrawer mSplashscreenContentDrawer;
private Choreographer mChoreographer;
private static final boolean DEBUG_ENABLE_REVEAL_ANIMATION =
@@ -276,6 +282,7 @@
final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
final FrameLayout rootLayout = new FrameLayout(context);
final Runnable setViewSynchronized = () -> {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
// waiting for setContentView before relayoutWindow
SplashScreenView contentView = viewSupplier.get();
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
@@ -294,13 +301,14 @@
}
record.setSplashScreenView(contentView);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
};
mSplashscreenContentDrawer.createContentView(context, emptyView, activityInfo, taskId,
viewSupplier::setView);
try {
final WindowManager wm = context.getSystemService(WindowManager.class);
- if (postAddWindow(taskId, appToken, rootLayout, wm, params)) {
+ if (addWindow(taskId, appToken, rootLayout, wm, params)) {
// We use the splash screen worker thread to create SplashScreenView while adding
// the window, as otherwise Choreographer#doFrame might be delayed on this thread.
// And since Choreographer#doFrame won't happen immediately after adding the window,
@@ -393,10 +401,11 @@
ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
}
- protected boolean postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
+ protected boolean addWindow(int taskId, IBinder appToken, View view, WindowManager wm,
WindowManager.LayoutParams params) {
boolean shouldSaveView = true;
try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
wm.addView(view, params);
} catch (WindowManager.BadTokenException e) {
// ignore
@@ -404,6 +413,7 @@
+ e.getMessage());
shouldSaveView = false;
} finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (view != null && view.getParent() == null) {
Slog.w(TAG, "view not successfully added to wm, removing view");
wm.removeViewImmediate(view);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
index fbbd09f..ad9dda6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
@@ -16,6 +16,13 @@
package com.android.wm.shell.tasksurfacehelper;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
/**
* Interface to communicate with a Task's SurfaceControl.
*/
@@ -23,4 +30,8 @@
/** Sets the METADATA_GAME_MODE for the layer corresponding to the task **/
default void setGameModeForTask(int taskId, int gameMode) {}
+
+ /** Takes a screenshot for a task **/
+ default void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor,
+ Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
index b459b9f..064d9d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
@@ -16,11 +16,16 @@
package com.android.wm.shell.tasksurfacehelper;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.graphics.Rect;
import android.view.SurfaceControl;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
/**
* Intermediary controller that communicates with {@link ShellTaskOrganizer} to send commands
* to SurfaceControl.
@@ -49,6 +54,14 @@
mTaskOrganizer.setSurfaceMetadata(taskId, SurfaceControl.METADATA_GAME_MODE, gameMode);
}
+ /**
+ * Take screenshot of the specified task.
+ */
+ public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
+ Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
+ mTaskOrganizer.screenshotTask(taskInfo, crop, consumer);
+ }
+
private class TaskSurfaceHelperImpl implements TaskSurfaceHelper {
@Override
public void setGameModeForTask(int taskId, int gameMode) {
@@ -56,5 +69,14 @@
TaskSurfaceHelperController.this.setGameModeForTask(taskId, gameMode);
});
}
+
+ @Override
+ public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor,
+ Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
+ mMainExecutor.execute(() -> {
+ TaskSurfaceHelperController.this.screenshotTask(taskInfo, crop,
+ (t) -> executor.execute(() -> consumer.accept(t)));
+ });
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 4e3e133..903e63a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -18,25 +18,27 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.UserHandle;
import android.testing.TestableContext;
import android.view.SurfaceControl;
import android.view.View;
@@ -58,6 +60,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.IntSupplier;
+
/**
* Tests for the starting surface drawer.
*/
@@ -71,6 +75,9 @@
@Mock
private TransactionPool mTransactionPool;
+ private final Handler mTestHandler = new Handler(Looper.getMainLooper());
+ private final TestableContext mTestContext = new TestContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext());
TestStartingSurfaceDrawer mStartingSurfaceDrawer;
static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
@@ -83,7 +90,7 @@
}
@Override
- protected boolean postAddWindow(int taskId, IBinder appToken,
+ protected boolean addWindow(int taskId, IBinder appToken,
View view, WindowManager wm, WindowManager.LayoutParams params) {
// listen for addView
mAddWindowForTask = taskId;
@@ -101,45 +108,50 @@
}
}
+ private static class TestContext extends TestableContext {
+ TestContext(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ return this;
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ return null;
+ }
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- final TestableContext context = new TestableContext(
- InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
- final WindowManager realWindowManager = context.getSystemService(WindowManager.class);
+ final WindowManager realWindowManager = mTestContext.getSystemService(WindowManager.class);
final WindowMetrics metrics = realWindowManager.getMaximumWindowMetrics();
- context.addMockSystemService(WindowManager.class, mMockWindowManager);
+ mTestContext.addMockSystemService(WindowManager.class, mMockWindowManager);
- spyOn(context);
- spyOn(realWindowManager);
- try {
- doReturn(context).when(context)
- .createPackageContextAsUser(anyString(), anyInt(), any());
- } catch (PackageManager.NameNotFoundException e) {
- //
- }
doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
doNothing().when(mMockWindowManager).addView(any(), any());
- final HandlerExecutor testExecutor =
- new HandlerExecutor(new Handler(Looper.getMainLooper()));
- mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(context, testExecutor,
- mTransactionPool));
+ mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(mTestContext,
+ new HandlerExecutor(mTestHandler), mTransactionPool));
}
@Test
public void testAddSplashScreenSurface() {
final int taskId = 1;
- final Handler mainLoop = new Handler(Looper.getMainLooper());
final StartingWindowInfo windowInfo =
createWindowInfo(taskId, android.R.style.Theme);
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, false);
- waitHandlerIdle(mainLoop);
- verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+ waitHandlerIdle(mTestHandler);
+ verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any());
assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId, null, null, false);
- waitHandlerIdle(mainLoop);
+ waitHandlerIdle(mTestHandler);
verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId), any(), any(), eq(false));
assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
}
@@ -147,15 +159,45 @@
@Test
public void testFallbackDefaultTheme() {
final int taskId = 1;
- final Handler mainLoop = new Handler(Looper.getMainLooper());
final StartingWindowInfo windowInfo =
createWindowInfo(taskId, 0);
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, false);
- waitHandlerIdle(mainLoop);
- verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+ waitHandlerIdle(mTestHandler);
+ verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any());
assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0);
}
+ @Test
+ public void testColorCache() {
+ final String packageName = mTestContext.getPackageName();
+ final int configHash = 1;
+ final int windowBgColor = 0xff000000;
+ final int windowBgResId = 1;
+ final IntSupplier windowBgColorSupplier = () -> windowBgColor;
+ final SplashscreenContentDrawer.ColorCache colorCache =
+ mStartingSurfaceDrawer.mSplashscreenContentDrawer.mColorCache;
+ final SplashscreenContentDrawer.ColorCache.WindowColor windowColor1 =
+ colorCache.getWindowColor(packageName, configHash, windowBgColor, windowBgResId,
+ windowBgColorSupplier);
+ assertEquals(windowBgColor, windowColor1.mBgColor);
+ assertEquals(0, windowColor1.mReuseCount);
+
+ final SplashscreenContentDrawer.ColorCache.WindowColor windowColor2 =
+ colorCache.getWindowColor(packageName, configHash, windowBgColor, windowBgResId,
+ windowBgColorSupplier);
+ assertEquals(windowColor1, windowColor2);
+ assertEquals(1, windowColor1.mReuseCount);
+
+ final Intent packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ packageRemoved.setData(Uri.parse("package:" + packageName));
+ colorCache.onReceive(mTestContext, packageRemoved);
+
+ final SplashscreenContentDrawer.ColorCache.WindowColor windowColor3 =
+ colorCache.getWindowColor(packageName, configHash, windowBgColor, windowBgResId,
+ windowBgColorSupplier);
+ assertEquals(0, windowColor3.mReuseCount);
+ }
+
private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
StartingWindowInfo windowInfo = new StartingWindowInfo();
final ActivityInfo info = new ActivityInfo();
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index c482fc1..e7432ac 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -31,7 +31,7 @@
// Cache size limits.
static const size_t maxKeySize = 1024;
-static const size_t maxValueSize = 512 * 1024;
+static const size_t maxValueSize = 2 * 1024 * 1024;
static const size_t maxTotalSize = 1024 * 1024;
ShaderCache::ShaderCache() {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 01a2ec5..fe9a30a 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -429,7 +429,9 @@
kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr);
if (bufferInfo->skSurface.get() == nullptr) {
ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
- mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, fence_fd.release());
+ mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
+ mNativeBuffers[idx].dequeue_fence.release());
+ mNativeBuffers[idx].dequeued = false;
return nullptr;
}
}
diff --git a/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl b/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
index 1755657..63c52a1 100644
--- a/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
@@ -26,9 +26,9 @@
*/
oneway interface IMediaRoute2ProviderServiceCallback {
// TODO: Change it to updateRoutes?
- void updateState(in MediaRoute2ProviderInfo providerInfo);
+ void notifyProviderUpdated(in MediaRoute2ProviderInfo providerInfo);
void notifySessionCreated(long requestId, in RoutingSessionInfo sessionInfo);
- void notifySessionUpdated(in RoutingSessionInfo sessionInfo);
+ void notifySessionsUpdated(in List<RoutingSessionInfo> sessionInfo);
void notifySessionReleased(in RoutingSessionInfo sessionInfo);
void notifyRequestFailed(long requestId, int reason);
}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 93fe06a..49e0411 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -141,6 +141,7 @@
private final Object mSessionLock = new Object();
private final Object mRequestIdsLock = new Object();
private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false);
+ private final AtomicBoolean mSessionUpdateScheduled = new AtomicBoolean(false);
private MediaRoute2ProviderServiceStub mStub;
private IMediaRoute2ProviderServiceCallback mRemoteCallback;
private volatile MediaRoute2ProviderInfo mProviderInfo;
@@ -287,16 +288,8 @@
Log.w(TAG, "notifySessionUpdated: Ignoring unknown session info.");
return;
}
-
- if (mRemoteCallback == null) {
- return;
- }
- try {
- mRemoteCallback.notifySessionUpdated(sessionInfo);
- } catch (RemoteException ex) {
- Log.w(TAG, "Failed to notify session info changed.");
- }
}
+ scheduleUpdateSessions();
}
/**
@@ -479,6 +472,7 @@
void setCallback(IMediaRoute2ProviderServiceCallback callback) {
mRemoteCallback = callback;
schedulePublishState();
+ scheduleUpdateSessions();
}
void schedulePublishState() {
@@ -497,12 +491,40 @@
}
try {
- mRemoteCallback.updateState(mProviderInfo);
+ mRemoteCallback.notifyProviderUpdated(mProviderInfo);
} catch (RemoteException ex) {
Log.w(TAG, "Failed to publish provider state.", ex);
}
}
+ void scheduleUpdateSessions() {
+ if (mSessionUpdateScheduled.compareAndSet(false, true)) {
+ mHandler.post(this::updateSessions);
+ }
+ }
+
+ private void updateSessions() {
+ if (!mSessionUpdateScheduled.compareAndSet(true, false)) {
+ return;
+ }
+
+ if (mRemoteCallback == null) {
+ return;
+ }
+
+ List<RoutingSessionInfo> sessions;
+ synchronized (mSessionLock) {
+ sessions = new ArrayList<>(mSessionInfo.values());
+ }
+
+ try {
+ mRemoteCallback.notifySessionsUpdated(sessions);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to notify session info changed.");
+ }
+
+ }
+
/**
* Adds a requestId in the request ID list whose max size is {@link #MAX_REQUEST_IDS_SIZE}.
* When the max size is reached, the first element is removed (FIFO).
diff --git a/packages/PackageInstaller/res/values-eu/strings.xml b/packages/PackageInstaller/res/values-eu/strings.xml
index 9922fe1..f5ad2be 100644
--- a/packages/PackageInstaller/res/values-eu/strings.xml
+++ b/packages/PackageInstaller/res/values-eu/strings.xml
@@ -46,7 +46,7 @@
<string name="out_of_space_dlg_text" msgid="8727714096031856231">"Ezin izan da instalatu <xliff:g id="APP_NAME">%1$s</xliff:g>. Egin toki pixka bat eta saiatu berriro."</string>
<string name="app_not_found_dlg_title" msgid="5107924008597470285">"Ez da aurkitu aplikazioa"</string>
<string name="app_not_found_dlg_text" msgid="5219983779377811611">"Ez da aurkitu aplikazioa instalatutako aplikazioen zerrendan."</string>
- <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"Ez du baimenik"</string>
+ <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"Baimendu gabe"</string>
<string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"Erabiltzaile honek ez dauka desinstalatzeko baimenik."</string>
<string name="generic_error_dlg_title" msgid="5863195085927067752">"Errorea"</string>
<string name="generic_error_dlg_text" msgid="5287861443265795232">"Ezin izan da desinstalatu aplikazioa."</string>
diff --git a/packages/PackageInstaller/res/values-gl/strings.xml b/packages/PackageInstaller/res/values-gl/strings.xml
index 637e15a..ac1f6ac 100644
--- a/packages/PackageInstaller/res/values-gl/strings.xml
+++ b/packages/PackageInstaller/res/values-gl/strings.xml
@@ -46,7 +46,7 @@
<string name="out_of_space_dlg_text" msgid="8727714096031856231">"Non se puido instalar a aplicación <xliff:g id="APP_NAME">%1$s</xliff:g>. Libera espazo e téntao de novo."</string>
<string name="app_not_found_dlg_title" msgid="5107924008597470285">"Non se atopou a aplicación"</string>
<string name="app_not_found_dlg_text" msgid="5219983779377811611">"Non se atopou a aplicación na lista de aplicacións instaladas."</string>
- <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"Sen permiso"</string>
+ <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"Permiso non concedido"</string>
<string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"O usuario actual non pode realizar esta desinstalación."</string>
<string name="generic_error_dlg_title" msgid="5863195085927067752">"Erro"</string>
<string name="generic_error_dlg_text" msgid="5287861443265795232">"Non se puido desinstalar a aplicación."</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4caced6..7ae1970 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -886,7 +886,7 @@
<!-- UI debug setting: force right to left layout summary [CHAR LIMIT=100] -->
<string name="force_rtl_layout_all_locales_summary">Force screen layout direction to RTL for all locales</string>
- <!-- UI debug setting: enable or disable window blurs [CHAR LIMIT=25] -->
+ <!-- UI debug setting: enable or disable window blurs [CHAR LIMIT=50] -->
<string name="window_blurs">Allow window-level blurs</string>
<!-- UI debug setting: force anti-aliasing to render apps [CHAR LIMIT=25] -->
diff --git a/packages/Shell/res/values-az/strings.xml b/packages/Shell/res/values-az/strings.xml
index 1522f3f..15853c2 100644
--- a/packages/Shell/res/values-az/strings.xml
+++ b/packages/Shell/res/values-az/strings.xml
@@ -29,7 +29,7 @@
<string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"baq hesabatınızı skrinşot olmadan paylaşmaq üçün tıklayın, skrinşotun tamamlanması üçün isə gözləyin"</string>
<string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"baq hesabatınızı skrinşot olmadan paylaşmaq üçün tıklayın, skrinşotun tamamlanması üçün isə gözləyin"</string>
<string name="bugreport_confirm" msgid="5917407234515812495">"Baq hesabatları sistemin müxtəlif jurnal fayllarından həssas təyin etdiyiniz data (tətbiq istifadəsi və məkan datası kimi) içərir. Baq raportlarını yalnız inandığınız tətbiq və adamlarla paylaşın."</string>
- <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Daha göstərməyin"</string>
+ <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Göstərilməsin"</string>
<string name="bugreport_storage_title" msgid="5332488144740527109">"Baq hesabatları"</string>
<string name="bugreport_unreadable_text" msgid="586517851044535486">"Baq hesabat faylı oxunmur"</string>
<string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"Zip faylı üçün baq hesabat detalları əlavə edilmədi"</string>
diff --git a/packages/StatementService/Android.bp b/packages/StatementService/Android.bp
index 32defc8..a0d8ac9 100644
--- a/packages/StatementService/Android.bp
+++ b/packages/StatementService/Android.bp
@@ -22,17 +22,24 @@
android_app {
name: "StatementService",
- defaults: ["platform_app_defaults"],
- srcs: ["src/**/*.java"],
+ // Removed because Errorprone doesn't work with Kotlin, can fix up in the future
+ // defaults: ["platform_app_defaults"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
optimize: {
proguard_flags_files: ["proguard.flags"],
},
+ target_sdk_version: "29",
platform_apis: true,
privileged: true,
- libs: ["org.apache.http.legacy"],
- uses_libs: ["org.apache.http.legacy"],
+ certificate: "platform",
static_libs: [
- "libprotobuf-java-nano",
- "volley",
+ "androidx.appcompat_appcompat",
+ "androidx.collection_collection-ktx",
+ "androidx.work_work-runtime",
+ "androidx.work_work-runtime-ktx",
+ "kotlinx-coroutines-android",
],
}
diff --git a/packages/StatementService/AndroidManifest.xml b/packages/StatementService/AndroidManifest.xml
index e0abd50..42cd143 100644
--- a/packages/StatementService/AndroidManifest.xml
+++ b/packages/StatementService/AndroidManifest.xml
@@ -14,41 +14,62 @@
limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.statementservice"
- android:versionCode="1"
- android:versionName="1.0">
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.statementservice"
+ android:versionCode="1"
+ android:versionName="1.0">
- <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
<uses-permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+ <uses-permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION"/>
<application
- android:label="@string/service_name"
- android:allowBackup="false">
- <uses-library android:name="org.apache.http.legacy" />
- <service
- android:name=".DirectStatementService"
- android:exported="false">
- <intent-filter>
- <category android:name="android.intent.category.DEFAULT"/>
- <action android:name="com.android.statementservice.aosp.service.CHECK_ACTION"/>
- </intent-filter>
- </service>
+ android:label="@string/service_name"
+ android:allowBackup="false"
+ android:name=".StatementServiceApplication"
+ >
<receiver
- android:name=".IntentFilterVerificationReceiver"
- android:permission="android.permission.BIND_INTENT_FILTER_VERIFIER"
- android:exported="true">
- <!-- Set the priority 1 so newer implementation can have higher priority. -->
- <intent-filter
- android:priority="1">
+ android:name=".domain.BootCompletedReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
+ </intent-filter>
+ </receiver>
+
+ <receiver
+ android:name=".domain.DomainVerificationReceiverV1"
+ android:permission="android.permission.BIND_INTENT_FILTER_VERIFIER"
+ android:exported="true"
+ >
+ <intent-filter android:priority="1">
<action android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"/>
<data android:mimeType="application/vnd.android.package-archive"/>
</intent-filter>
</receiver>
+ <!--
+ v2 receiver remains disabled assuming the device ships its own updated version.
+ If necessary, this can be enabled using shell.
+ -->
+ <receiver
+ android:name=".domain.DomainVerificationReceiverV2"
+ android:permission="android.permission.BIND_DOMAIN_VERIFICATION_AGENT"
+ android:directBootAware="true"
+ android:exported="true"
+ android:enabled="false"
+ >
+ <intent-filter android:priority="1">
+ <action android:name="android.intent.action.DOMAINS_NEED_VERIFICATION"/>
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/packages/StatementService/src/com/android/statementservice/DirectStatementService.java b/packages/StatementService/src/com/android/statementservice/DirectStatementService.java
deleted file mode 100644
index 659696e..0000000
--- a/packages/StatementService/src/com/android/statementservice/DirectStatementService.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2015 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.statementservice;
-
-import android.app.Service;
-import android.content.Intent;
-import android.net.http.HttpResponseCache;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-import com.android.statementservice.retriever.AbstractAsset;
-import com.android.statementservice.retriever.AbstractAssetMatcher;
-import com.android.statementservice.retriever.AbstractStatementRetriever;
-import com.android.statementservice.retriever.AbstractStatementRetriever.Result;
-import com.android.statementservice.retriever.AssociationServiceException;
-import com.android.statementservice.retriever.Relation;
-import com.android.statementservice.retriever.Statement;
-
-import org.json.JSONException;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-/**
- * Handles com.android.statementservice.service.CHECK_ALL_ACTION intents.
- */
-public final class DirectStatementService extends Service {
- private static final String TAG = DirectStatementService.class.getSimpleName();
-
- /**
- * Returns true if every asset in {@code SOURCE_ASSET_DESCRIPTORS} is associated with {@code
- * EXTRA_TARGET_ASSET_DESCRIPTOR} for {@code EXTRA_RELATION} relation.
- *
- * <p>Takes parameter {@code EXTRA_RELATION}, {@code SOURCE_ASSET_DESCRIPTORS}, {@code
- * EXTRA_TARGET_ASSET_DESCRIPTOR}, and {@code EXTRA_RESULT_RECEIVER}.
- */
- public static final String CHECK_ALL_ACTION =
- "com.android.statementservice.service.CHECK_ALL_ACTION";
-
- /**
- * Parameter for {@link #CHECK_ALL_ACTION}.
- *
- * <p>A relation string.
- */
- public static final String EXTRA_RELATION =
- "com.android.statementservice.service.RELATION";
-
- /**
- * Parameter for {@link #CHECK_ALL_ACTION}.
- *
- * <p>An array of asset descriptors in JSON.
- */
- public static final String EXTRA_SOURCE_ASSET_DESCRIPTORS =
- "com.android.statementservice.service.SOURCE_ASSET_DESCRIPTORS";
-
- /**
- * Parameter for {@link #CHECK_ALL_ACTION}.
- *
- * <p>An asset descriptor in JSON.
- */
- public static final String EXTRA_TARGET_ASSET_DESCRIPTOR =
- "com.android.statementservice.service.TARGET_ASSET_DESCRIPTOR";
-
- /**
- * Parameter for {@link #CHECK_ALL_ACTION}.
- *
- * <p>A {@code ResultReceiver} instance that will be used to return the result. If the request
- * failed, return {@link #RESULT_FAIL} and an empty {@link android.os.Bundle}. Otherwise, return
- * {@link #RESULT_SUCCESS} and a {@link android.os.Bundle} with the result stored in {@link
- * #IS_ASSOCIATED}.
- */
- public static final String EXTRA_RESULT_RECEIVER =
- "com.android.statementservice.service.RESULT_RECEIVER";
-
- /**
- * A boolean bundle entry that stores the result of {@link #CHECK_ALL_ACTION}.
- * This is set only if the service returns with {@code RESULT_SUCCESS}.
- * {@code IS_ASSOCIATED} is true if and only if {@code FAILED_SOURCES} is empty.
- */
- public static final String IS_ASSOCIATED = "is_associated";
-
- /**
- * A String ArrayList bundle entry that stores sources that can't be verified.
- */
- public static final String FAILED_SOURCES = "failed_sources";
-
- /**
- * Returned by the service if the request is successfully processed. The caller should check
- * the {@code IS_ASSOCIATED} field to determine if the association exists or not.
- */
- public static final int RESULT_SUCCESS = 0;
-
- /**
- * Returned by the service if the request failed. The request will fail if, for example, the
- * input is not well formed, or the network is not available.
- */
- public static final int RESULT_FAIL = 1;
-
- private static final long HTTP_CACHE_SIZE_IN_BYTES = 1 * 1024 * 1024; // 1 MBytes
- private static final String CACHE_FILENAME = "request_cache";
-
- private AbstractStatementRetriever mStatementRetriever;
- private Handler mHandler;
- private HandlerThread mThread;
- private HttpResponseCache mHttpResponseCache;
-
- @Override
- public void onCreate() {
- mThread = new HandlerThread("DirectStatementService thread",
- android.os.Process.THREAD_PRIORITY_BACKGROUND);
- mThread.start();
- onCreate(AbstractStatementRetriever.createDirectRetriever(this), mThread.getLooper(),
- getCacheDir());
- }
-
- /**
- * Creates a DirectStatementService with the dependencies passed in for easy testing.
- */
- public void onCreate(AbstractStatementRetriever statementRetriever, Looper looper,
- File cacheDir) {
- super.onCreate();
- mStatementRetriever = statementRetriever;
- mHandler = new Handler(looper);
-
- try {
- File httpCacheDir = new File(cacheDir, CACHE_FILENAME);
- mHttpResponseCache = HttpResponseCache.install(httpCacheDir, HTTP_CACHE_SIZE_IN_BYTES);
- } catch (IOException e) {
- Log.i(TAG, "HTTPS response cache installation failed:" + e);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- final HttpResponseCache responseCache = mHttpResponseCache;
- mHandler.post(new Runnable() {
- public void run() {
- try {
- if (responseCache != null) {
- responseCache.delete();
- }
- } catch (IOException e) {
- Log.i(TAG, "HTTP(S) response cache deletion failed:" + e);
- }
- Looper.myLooper().quit();
- }
- });
- mHttpResponseCache = null;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- super.onStartCommand(intent, flags, startId);
-
- if (intent == null) {
- Log.e(TAG, "onStartCommand called with null intent");
- return START_STICKY;
- }
-
- if (intent.getAction().equals(CHECK_ALL_ACTION)) {
-
- Bundle extras = intent.getExtras();
- List<String> sources = extras.getStringArrayList(EXTRA_SOURCE_ASSET_DESCRIPTORS);
- String target = extras.getString(EXTRA_TARGET_ASSET_DESCRIPTOR);
- String relation = extras.getString(EXTRA_RELATION);
- ResultReceiver resultReceiver = extras.getParcelable(EXTRA_RESULT_RECEIVER);
-
- if (resultReceiver == null) {
- Log.e(TAG, " Intent does not have extra " + EXTRA_RESULT_RECEIVER);
- return START_STICKY;
- }
- if (sources == null) {
- Log.e(TAG, " Intent does not have extra " + EXTRA_SOURCE_ASSET_DESCRIPTORS);
- resultReceiver.send(RESULT_FAIL, Bundle.EMPTY);
- return START_STICKY;
- }
- if (target == null) {
- Log.e(TAG, " Intent does not have extra " + EXTRA_TARGET_ASSET_DESCRIPTOR);
- resultReceiver.send(RESULT_FAIL, Bundle.EMPTY);
- return START_STICKY;
- }
- if (relation == null) {
- Log.e(TAG, " Intent does not have extra " + EXTRA_RELATION);
- resultReceiver.send(RESULT_FAIL, Bundle.EMPTY);
- return START_STICKY;
- }
-
- mHandler.post(new ExceptionLoggingFutureTask<Void>(
- new IsAssociatedCallable(sources, target, relation, resultReceiver), TAG));
- } else {
- Log.e(TAG, "onStartCommand called with unsupported action: " + intent.getAction());
- }
- return START_STICKY;
- }
-
- private class IsAssociatedCallable implements Callable<Void> {
-
- private List<String> mSources;
- private String mTarget;
- private String mRelation;
- private ResultReceiver mResultReceiver;
-
- public IsAssociatedCallable(List<String> sources, String target, String relation,
- ResultReceiver resultReceiver) {
- mSources = sources;
- mTarget = target;
- mRelation = relation;
- mResultReceiver = resultReceiver;
- }
-
- private boolean verifyOneSource(AbstractAsset source, AbstractAssetMatcher target,
- Relation relation) throws AssociationServiceException {
- Result statements = mStatementRetriever.retrieveStatements(source);
- for (Statement statement : statements.getStatements()) {
- if (relation.matches(statement.getRelation())
- && target.matches(statement.getTarget())) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public Void call() {
- Bundle result = new Bundle();
- ArrayList<String> failedSources = new ArrayList<String>();
- AbstractAssetMatcher target;
- Relation relation;
- try {
- target = AbstractAssetMatcher.createMatcher(mTarget);
- relation = Relation.create(mRelation);
- } catch (AssociationServiceException | JSONException e) {
- Log.e(TAG, "isAssociatedCallable failed with exception", e);
- mResultReceiver.send(RESULT_FAIL, Bundle.EMPTY);
- return null;
- }
-
- boolean allSourcesVerified = true;
- for (String sourceString : mSources) {
- AbstractAsset source;
- try {
- source = AbstractAsset.create(sourceString);
- } catch (AssociationServiceException e) {
- mResultReceiver.send(RESULT_FAIL, Bundle.EMPTY);
- return null;
- }
-
- try {
- if (!verifyOneSource(source, target, relation)) {
- failedSources.add(source.toJson());
- allSourcesVerified = false;
- }
- } catch (AssociationServiceException e) {
- failedSources.add(source.toJson());
- allSourcesVerified = false;
- }
- }
-
- result.putBoolean(IS_ASSOCIATED, allSourcesVerified);
- result.putStringArrayList(FAILED_SOURCES, failedSources);
- mResultReceiver.send(RESULT_SUCCESS, result);
- return null;
- }
- }
-}
diff --git a/packages/StatementService/src/com/android/statementservice/ExceptionLoggingFutureTask.java b/packages/StatementService/src/com/android/statementservice/ExceptionLoggingFutureTask.java
deleted file mode 100644
index 20c7f97..0000000
--- a/packages/StatementService/src/com/android/statementservice/ExceptionLoggingFutureTask.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 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.statementservice;
-
-import android.util.Log;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-
-/**
- * {@link FutureTask} that logs unhandled exceptions.
- */
-final class ExceptionLoggingFutureTask<V> extends FutureTask<V> {
-
- private final String mTag;
-
- public ExceptionLoggingFutureTask(Callable<V> callable, String tag) {
- super(callable);
- mTag = tag;
- }
-
- @Override
- protected void done() {
- try {
- get();
- } catch (ExecutionException | InterruptedException e) {
- Log.e(mTag, "Uncaught exception.", e);
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java b/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java
deleted file mode 100644
index ba8e7a1..0000000
--- a/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2015 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.statementservice;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Patterns;
-
-import com.android.statementservice.retriever.Utils;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * Receives {@link Intent#ACTION_INTENT_FILTER_NEEDS_VERIFICATION} broadcast and calls
- * {@link DirectStatementService} to verify the request. Calls
- * {@link PackageManager#verifyIntentFilter} to notify {@link PackageManager} the result of the
- * verification.
- *
- * This implementation of the API will send a HTTP request for each host specified in the query.
- * To avoid overwhelming the network at app install time, {@code MAX_HOSTS_PER_REQUEST} limits
- * the maximum number of hosts in a query. If a query contains more than
- * {@code MAX_HOSTS_PER_REQUEST} hosts, it will fail immediately without making any HTTP request
- * and call {@link PackageManager#verifyIntentFilter} with
- * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}.
- */
-public final class IntentFilterVerificationReceiver extends BroadcastReceiver {
- private static final String TAG = IntentFilterVerificationReceiver.class.getSimpleName();
-
- private static final Integer MAX_HOSTS_PER_REQUEST = 10;
-
- private static final String HANDLE_ALL_URLS_RELATION
- = "delegate_permission/common.handle_all_urls";
-
- private static final String ANDROID_ASSET_FORMAT = "{\"namespace\": \"android_app\", "
- + "\"package_name\": \"%s\", \"sha256_cert_fingerprints\": [\"%s\"]}";
- private static final String WEB_ASSET_FORMAT = "{\"namespace\": \"web\", \"site\": \"%s\"}";
- private static final Pattern ANDROID_PACKAGE_NAME_PATTERN =
- Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$");
- private static final String TOO_MANY_HOSTS_FORMAT =
- "Request contains %d hosts which is more than the allowed %d.";
-
- private static void sendErrorToPackageManager(PackageManager packageManager,
- int verificationId) {
- packageManager.verifyIntentFilter(verificationId,
- PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
- Collections.<String>emptyList());
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION.equals(action)) {
- Bundle inputExtras = intent.getExtras();
- if (inputExtras != null) {
- Intent serviceIntent = new Intent(context, DirectStatementService.class);
- serviceIntent.setAction(DirectStatementService.CHECK_ALL_ACTION);
-
- int verificationId = inputExtras.getInt(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID);
- String scheme = inputExtras.getString(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME);
- String hosts = inputExtras.getString(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS);
- String packageName = inputExtras.getString(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME);
-
- Bundle extras = new Bundle();
- extras.putString(DirectStatementService.EXTRA_RELATION, HANDLE_ALL_URLS_RELATION);
-
- String[] hostList = hosts.split(" ");
- if (hostList.length > MAX_HOSTS_PER_REQUEST) {
- Log.w(TAG, String.format(TOO_MANY_HOSTS_FORMAT,
- hostList.length, MAX_HOSTS_PER_REQUEST));
- sendErrorToPackageManager(context.getPackageManager(), verificationId);
- return;
- }
-
- ArrayList<String> finalHosts = new ArrayList<String>(hostList.length);
- try {
- ArrayList<String> sourceAssets = new ArrayList<String>();
- for (String host : hostList) {
- // "*.example.tld" is validated via https://example.tld
- if (host.startsWith("*.")) {
- host = host.substring(2);
- }
- sourceAssets.add(createWebAssetString(scheme, host));
- finalHosts.add(host);
- }
- extras.putStringArrayList(DirectStatementService.EXTRA_SOURCE_ASSET_DESCRIPTORS,
- sourceAssets);
- } catch (MalformedURLException e) {
- Log.w(TAG, "Error when processing input host: " + e.getMessage());
- sendErrorToPackageManager(context.getPackageManager(), verificationId);
- return;
- }
- try {
- extras.putString(DirectStatementService.EXTRA_TARGET_ASSET_DESCRIPTOR,
- createAndroidAssetString(context, packageName));
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Error when processing input Android package: " + e.getMessage());
- sendErrorToPackageManager(context.getPackageManager(), verificationId);
- return;
- }
- extras.putParcelable(DirectStatementService.EXTRA_RESULT_RECEIVER,
- new IsAssociatedResultReceiver(
- new Handler(), context.getPackageManager(), verificationId));
-
- // Required for CTS: log a few details of the validcation operation to be performed
- logValidationParametersForCTS(verificationId, scheme, finalHosts, packageName);
-
- serviceIntent.putExtras(extras);
- context.startService(serviceIntent);
- }
- } else {
- Log.w(TAG, "Intent action not supported: " + action);
- }
- }
-
- // CTS requirement: logging of the validation parameters in a specific format
- private static final String CTS_LOG_FORMAT =
- "Verifying IntentFilter. verificationId:%d scheme:\"%s\" hosts:\"%s\" package:\"%s\".";
- private void logValidationParametersForCTS(int verificationId, String scheme,
- ArrayList<String> finalHosts, String packageName) {
- String hostString = TextUtils.join(" ", finalHosts.toArray());
- Log.i(TAG, String.format(CTS_LOG_FORMAT, verificationId, scheme, hostString, packageName));
- }
-
- private String createAndroidAssetString(Context context, String packageName)
- throws NameNotFoundException {
- if (!ANDROID_PACKAGE_NAME_PATTERN.matcher(packageName).matches()) {
- throw new NameNotFoundException("Input package name is not valid.");
- }
-
- List<String> certFingerprints =
- Utils.getCertFingerprintsFromPackageManager(packageName, context);
-
- return String.format(ANDROID_ASSET_FORMAT, packageName,
- Utils.joinStrings("\", \"", certFingerprints));
- }
-
- private String createWebAssetString(String scheme, String host) throws MalformedURLException {
- if (!Patterns.DOMAIN_NAME.matcher(host).matches()) {
- throw new MalformedURLException("Input host is not valid.");
- }
- if (!scheme.equals("http") && !scheme.equals("https")) {
- throw new MalformedURLException("Input scheme is not valid.");
- }
-
- return String.format(WEB_ASSET_FORMAT, new URL(scheme, host, "").toString());
- }
-
- /**
- * Receives the result of {@code StatementService.CHECK_ACTION} from
- * {@link DirectStatementService} and passes it back to {@link PackageManager}.
- */
- private static class IsAssociatedResultReceiver extends ResultReceiver {
-
- private final int mVerificationId;
- private final PackageManager mPackageManager;
-
- public IsAssociatedResultReceiver(Handler handler, PackageManager packageManager,
- int verificationId) {
- super(handler);
- mVerificationId = verificationId;
- mPackageManager = packageManager;
- }
-
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode == DirectStatementService.RESULT_SUCCESS) {
- if (resultData.getBoolean(DirectStatementService.IS_ASSOCIATED)) {
- mPackageManager.verifyIntentFilter(mVerificationId,
- PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
- Collections.<String>emptyList());
- } else {
- mPackageManager.verifyIntentFilter(mVerificationId,
- PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
- resultData.getStringArrayList(DirectStatementService.FAILED_SOURCES));
- }
- } else {
- sendErrorToPackageManager(mPackageManager, mVerificationId);
- }
- }
- }
-}
diff --git a/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt b/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
new file mode 100644
index 0000000..021a514
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice
+
+import android.app.Application
+import android.os.UserManager
+import androidx.work.WorkManager
+import com.android.statementservice.domain.DomainVerificationUtils
+
+class StatementServiceApplication : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+ val userManager = getSystemService(UserManager::class.java) ?: return
+ if (userManager.isUserUnlocked) {
+ // WorkManager can only schedule when the user data directories are unencrypted (after
+ // the user has entered their lock password.
+ DomainVerificationUtils.schedulePeriodicCheckUnlocked(WorkManager.getInstance(this))
+ }
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/BaseDomainVerificationReceiver.kt b/packages/StatementService/src/com/android/statementservice/domain/BaseDomainVerificationReceiver.kt
new file mode 100644
index 0000000..de41486
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/BaseDomainVerificationReceiver.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain
+
+import android.content.BroadcastReceiver
+import android.util.Log
+import androidx.work.Constraints
+import androidx.work.NetworkType
+
+abstract class BaseDomainVerificationReceiver : BroadcastReceiver() {
+
+ companion object {
+ const val DEBUG = false
+ }
+
+ protected abstract val tag: String
+
+ protected val networkConstraints = Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build()
+
+ protected fun debugLog(block: () -> String) {
+ if (DEBUG) {
+ Log.d(tag, block())
+ }
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/BootCompletedReceiver.kt b/packages/StatementService/src/com/android/statementservice/domain/BootCompletedReceiver.kt
new file mode 100644
index 0000000..7b5da83
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/BootCompletedReceiver.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import androidx.work.Constraints
+import androidx.work.ExistingWorkPolicy
+import androidx.work.NetworkType
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import com.android.statementservice.domain.worker.RetryRequestWorker
+
+/**
+ * Handles [Intent.ACTION_BOOT_COMPLETED] to schedule recurring maintenance [WorkManager] tasks and
+ * run a one-time retry request to attempt to verify domains that may have failed or been added
+ * since last device reboot.
+ *
+ * Note that this requires the user to have unlocked the device, since [WorkManager] cannot handle
+ * the encrypted user data directories.
+ */
+class BootCompletedReceiver : BroadcastReceiver() {
+
+ companion object {
+ private const val PACKAGE_BOOT_REQUEST_KEY = "package_boot_request"
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action != Intent.ACTION_BOOT_COMPLETED) return
+ val workManager = WorkManager.getInstance(context)
+ DomainVerificationUtils.schedulePeriodicCheckUnlocked(workManager)
+ workManager.beginUniqueWork(
+ PACKAGE_BOOT_REQUEST_KEY,
+ ExistingWorkPolicy.REPLACE,
+ OneTimeWorkRequestBuilder<RetryRequestWorker>()
+ .setConstraints(
+ Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build()
+ )
+ .build()
+ ).enqueue()
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
new file mode 100644
index 0000000..0ec8ed3
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import androidx.work.ExistingWorkPolicy
+import androidx.work.WorkManager
+import com.android.statementservice.domain.worker.CollectV1Worker
+import com.android.statementservice.domain.worker.SingleV1RequestWorker
+
+/**
+ * Receiver for V1 API. Separated so that the receiver permission can be declared for only the
+ * v1 and v2 permissions individually, exactly matching the intended usage.
+ */
+class DomainVerificationReceiverV1 : BaseDomainVerificationReceiver() {
+
+ companion object {
+ private const val ENABLE_V1 = true
+ private const val PACKAGE_WORK_PREFIX_V1 = "package_request_v1-"
+ }
+
+ override val tag = DomainVerificationReceiverV1::class.java.simpleName
+
+ override fun onReceive(context: Context, intent: Intent) {
+ when (intent.action) {
+ Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION ->
+ scheduleUnlockedV1(context, intent)
+ else -> debugLog { "Received invalid broadcast: $intent" }
+ }
+ }
+
+ private fun scheduleUnlockedV1(context: Context, intent: Intent) {
+ if (!ENABLE_V1) {
+ return
+ }
+
+ val verificationId =
+ intent.getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1)
+ val hosts =
+ (intent.getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS) ?: return)
+ .split(" ")
+ val packageName =
+ intent.getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME)
+ ?: return
+
+ debugLog { "Attempting v1 verification for $packageName" }
+
+ val workRequests = hosts.map {
+ SingleV1RequestWorker.buildRequest(packageName, it) {
+ setConstraints(networkConstraints)
+ }
+ }
+
+ WorkManager.getInstance(context)
+ .beginUniqueWork(
+ "$PACKAGE_WORK_PREFIX_V1$packageName",
+ ExistingWorkPolicy.REPLACE,
+ workRequests
+ )
+ .then(CollectV1Worker.buildRequest(verificationId, packageName))
+ .enqueue()
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV2.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV2.kt
new file mode 100644
index 0000000..24e0f50
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV2.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationRequest
+import android.os.UserManager
+import androidx.work.BackoffPolicy
+import androidx.work.ExistingWorkPolicy
+import androidx.work.WorkManager
+import com.android.statementservice.domain.worker.SingleV2RequestWorker
+import com.android.statementservice.utils.component1
+import com.android.statementservice.utils.component2
+import com.android.statementservice.utils.component3
+
+import java.time.Duration
+
+/**
+ * Handles [DomainVerificationRequest]s from the system, which indicates a package on the device
+ * has domains which require verification against a server side assetlinks.json file, allowing the
+ * app to resolve web [Intent]s.
+ *
+ * This will delegate to v1 or v2 depending on the received broadcast and which components are
+ * enabled. See [DomainVerificationManager] for the full API.
+ */
+open class DomainVerificationReceiverV2 : BaseDomainVerificationReceiver() {
+
+ companion object {
+
+ private const val ENABLE_V2 = true
+
+ /**
+ * Toggle to always re-verify packages that this receiver is notified of. This means on
+ * every package change, even previously successful requests are re-sent. Generally only
+ * for debugging.
+ */
+ @Suppress("SimplifyBooleanWithConstants")
+ private const val ALWAYS_VERIFY = false || DEBUG
+
+ private const val PACKAGE_WORK_PREFIX_V2 = "package_request_v2-"
+ }
+
+ override val tag = DomainVerificationReceiverV2::class.java.simpleName
+
+ override fun onReceive(context: Context, intent: Intent) {
+ when (intent.action) {
+ Intent.ACTION_DOMAINS_NEED_VERIFICATION -> {
+ // If the user isn't unlocked yet, the request will be ignored, as WorkManager
+ // cannot schedule workers when the user data directories are encrypted.
+ if (context.getSystemService(UserManager::class.java)?.isUserUnlocked == true) {
+ scheduleUnlockedV2(context, intent)
+ }
+ }
+ else -> debugLog { "Received invalid broadcast: $intent" }
+ }
+ }
+
+ private fun scheduleUnlockedV2(context: Context, intent: Intent) {
+ if (!ENABLE_V2) {
+ return
+ }
+
+ val manager = context.getSystemService(DomainVerificationManager::class.java) ?: return
+ val workManager = WorkManager.getInstance(context)
+
+ val request = intent.getParcelableExtra<DomainVerificationRequest>(
+ DomainVerificationManager.EXTRA_VERIFICATION_REQUEST
+ ) ?: return
+
+ debugLog { "Attempting v2 verification for ${request.packageNames}" }
+
+ request.packageNames.forEach { packageName ->
+ val (domainSetId, _, hostToStateMap) = manager.getDomainVerificationInfo(packageName)
+ ?: return@forEach
+
+ val workRequests = hostToStateMap
+ .filterValues {
+ // TODO(b/159952358): Should we support re-query? There's no good way to
+ // signal to an AOSP implementation from an entity's website about when
+ // to re-query, unless it's just done on each update.
+ // AOSP implementation does not support re-query
+ ALWAYS_VERIFY || VerifyStatus.shouldRetry(it)
+ }
+ .map { (host, _) ->
+ SingleV2RequestWorker.buildRequest(domainSetId, packageName, host) {
+ setConstraints(networkConstraints)
+ setBackoffCriteria(BackoffPolicy.EXPONENTIAL, Duration.ofHours(1))
+ }
+ }
+
+ if (workRequests.isNotEmpty()) {
+ workManager.beginUniqueWork(
+ "$PACKAGE_WORK_PREFIX_V2$packageName",
+ ExistingWorkPolicy.REPLACE, workRequests
+ )
+ .enqueue()
+ }
+ }
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
new file mode 100644
index 0000000..6944248
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain
+
+import androidx.work.Constraints
+import androidx.work.ExistingPeriodicWorkPolicy
+import androidx.work.NetworkType
+import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.WorkManager
+import com.android.statementservice.domain.worker.RetryRequestWorker
+import java.time.Duration
+
+object DomainVerificationUtils {
+
+ private const val PERIODIC_SHORT_ID = "retry_short"
+ private const val PERIODIC_SHORT_HOURS = 24L
+ private const val PERIODIC_LONG_ID = "retry_long"
+ private const val PERIODIC_LONG_HOURS = 72L
+
+ /**
+ * In a majority of cases, the initial requests will be enough to verify domains, since they
+ * are also restricted to [NetworkType.CONNECTED], but for cases where they aren't sufficient,
+ * attempts are also made on a periodic basis.
+ *
+ * Once per 24 hours, a check of all packages is done with [NetworkType.CONNECTED]. To avoid
+ * cases where a proxy or other unusual device configuration prevents [WorkManager] from
+ * running, also schedule a 3 day task without constraints which will force the check to run.
+ *
+ * The actual logic may be skipped if a request was previously run successfully or there are no
+ * more domains that need verifying.
+ */
+ fun schedulePeriodicCheckUnlocked(workManager: WorkManager) {
+ workManager.apply {
+ PeriodicWorkRequestBuilder<RetryRequestWorker>(Duration.ofHours(PERIODIC_SHORT_HOURS))
+ .setConstraints(
+ Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .setRequiresDeviceIdle(true)
+ .build()
+ )
+ .build()
+ .let {
+ enqueueUniquePeriodicWork(
+ PERIODIC_SHORT_ID,
+ ExistingPeriodicWorkPolicy.KEEP, it
+ )
+ }
+ PeriodicWorkRequestBuilder<RetryRequestWorker>(Duration.ofDays(PERIODIC_LONG_HOURS))
+ .setConstraints(
+ Constraints.Builder()
+ .setRequiresDeviceIdle(true)
+ .build()
+ )
+ .build()
+ .let {
+ enqueueUniquePeriodicWork(
+ PERIODIC_LONG_ID,
+ ExistingPeriodicWorkPolicy.KEEP, it
+ )
+ }
+ }
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
new file mode 100644
index 0000000..29f844f
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain
+
+import android.content.Context
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.net.Network
+import android.util.Log
+import androidx.collection.LruCache
+import com.android.statementservice.network.retriever.StatementRetriever
+import com.android.statementservice.retriever.AbstractAsset
+import com.android.statementservice.retriever.AbstractAssetMatcher
+import com.android.statementservice.utils.Result
+import com.android.statementservice.utils.StatementUtils
+import com.android.statementservice.utils.component1
+import com.android.statementservice.utils.component2
+import com.android.statementservice.utils.component3
+import java.net.HttpURLConnection
+import java.util.Optional
+import java.util.UUID
+
+private typealias WorkResult = androidx.work.ListenableWorker.Result
+
+class DomainVerifier private constructor(
+ private val appContext: Context,
+ private val manager: DomainVerificationManager
+) {
+ companion object {
+ private val TAG = DomainVerifier::class.java.simpleName
+ private const val DEBUG = false
+
+ private var singleton: DomainVerifier? = null
+
+ fun getInstance(context: Context) = when {
+ singleton != null -> singleton!!
+ else -> synchronized(this) {
+ if (singleton == null) {
+ val appContext = context.applicationContext
+ val manager =
+ appContext.getSystemService(DomainVerificationManager::class.java)!!
+ singleton = DomainVerifier(appContext, manager)
+ }
+ singleton!!
+ }
+ }
+ }
+
+ private val retriever = StatementRetriever()
+
+ private val targetAssetCache = AssetLruCache()
+
+ fun collectHosts(packageNames: Iterable<String>): Iterable<Triple<UUID, String, String>> {
+ return packageNames.mapNotNull { packageName ->
+ val (domainSetId, _, hostToStateMap) = try {
+ manager.getDomainVerificationInfo(packageName)
+ } catch (ignored: Exception) {
+ // Package disappeared, assume it will be rescheduled if the package reappears
+ null
+ } ?: return@mapNotNull null
+
+ val hostsToRetry = hostToStateMap
+ .filterValues(VerifyStatus::shouldRetry)
+ .takeIf { it.isNotEmpty() }
+ ?.map { it.key }
+ ?: return@mapNotNull null
+
+ hostsToRetry.map { Triple(domainSetId, packageName, it) }
+ }
+ .flatten()
+ }
+
+ suspend fun verifyHost(
+ host: String,
+ packageName: String,
+ network: Network? = null
+ ): Pair<WorkResult, VerifyStatus> {
+ val assetMatcher = synchronized(targetAssetCache) { targetAssetCache[packageName] }
+ .takeIf { it!!.isPresent }
+ ?: return WorkResult.failure() to VerifyStatus.FAILURE_PACKAGE_MANAGER
+ return verifyHost(host, assetMatcher.get(), network)
+ }
+
+ private suspend fun verifyHost(
+ host: String,
+ assetMatcher: AbstractAssetMatcher,
+ network: Network? = null
+ ): Pair<WorkResult, VerifyStatus> {
+ var exception: Exception? = null
+ val resultAndStatus = try {
+ val sourceAsset = StatementUtils.createWebAssetString(host)
+ .let(AbstractAsset::create)
+ val result = retriever.retrieve(sourceAsset, network)
+ ?: return WorkResult.success() to VerifyStatus.FAILURE_UNKNOWN
+ when (result.responseCode) {
+ HttpURLConnection.HTTP_MOVED_PERM,
+ HttpURLConnection.HTTP_MOVED_TEMP -> {
+ WorkResult.failure() to VerifyStatus.FAILURE_REDIRECT
+ }
+ else -> {
+ val isVerified = result.statements.any { statement ->
+ (StatementUtils.RELATION.matches(statement.relation) &&
+ assetMatcher.matches(statement.target))
+ }
+
+ if (isVerified) {
+ WorkResult.success() to VerifyStatus.SUCCESS
+ } else {
+ WorkResult.failure() to VerifyStatus.FAILURE_REJECTED_BY_SERVER
+ }
+ }
+ }
+ } catch (e: Exception) {
+ exception = e
+ WorkResult.retry() to VerifyStatus.FAILURE_UNKNOWN
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Verifying $host: ${resultAndStatus.second}", exception)
+ }
+
+ return resultAndStatus
+ }
+
+ private inner class AssetLruCache : LruCache<String, Optional<AbstractAssetMatcher>>(50) {
+ override fun create(packageName: String) =
+ StatementUtils.getCertFingerprintsFromPackageManager(appContext, packageName)
+ .let { (it as? Result.Success)?.value }
+ ?.let { StatementUtils.createAndroidAsset(packageName, it) }
+ ?.let(AbstractAssetMatcher::createMatcher)
+ .let { Optional.ofNullable(it) }
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt b/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
new file mode 100644
index 0000000..2193ec5
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain
+
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationManager
+
+/**
+ * Wraps known [DomainVerificationManager] status codes so that they can be used in a when
+ * statement. Unknown codes are coerced to [VerifyStatus.UNKNOWN] and should be treated as
+ * unverified.
+ *
+ * Also includes error codes specific to this implementation of the domain verification agent.
+ * These must be stable across all versions, as codes are persisted to disk. They do not
+ * technically have to be stable across different device factory resets, since they will be reset
+ * once the apps are re-initialized, but easier to keep them unique forever.
+ */
+enum class VerifyStatus(val value: Int) {
+ NO_RESPONSE(DomainVerificationInfo.STATE_NO_RESPONSE),
+ SUCCESS(DomainVerificationInfo.STATE_SUCCESS),
+
+ UNKNOWN(DomainVerificationInfo.STATE_FIRST_VERIFIER_DEFINED),
+ FAILURE_LEGACY_UNSUPPORTED_WILDCARD(DomainVerificationInfo.STATE_FIRST_VERIFIER_DEFINED + 1),
+ FAILURE_REJECTED_BY_SERVER(DomainVerificationInfo.STATE_FIRST_VERIFIER_DEFINED + 2),
+ FAILURE_TIMEOUT(DomainVerificationInfo.STATE_FIRST_VERIFIER_DEFINED + 3),
+ FAILURE_UNKNOWN(DomainVerificationInfo.STATE_FIRST_VERIFIER_DEFINED + 4),
+ FAILURE_REDIRECT(DomainVerificationInfo.STATE_FIRST_VERIFIER_DEFINED + 5),
+
+ // Failed to retrieve signature information from PackageManager
+ FAILURE_PACKAGE_MANAGER(DomainVerificationInfo.STATE_FIRST_VERIFIER_DEFINED + 6);
+
+ companion object {
+ fun shouldRetry(state: Int): Boolean {
+ if (state == DomainVerificationInfo.STATE_UNMODIFIABLE) {
+ return false
+ }
+
+ val status = values().find { it.value == state } ?: return true
+ return when (status) {
+ SUCCESS,
+ FAILURE_LEGACY_UNSUPPORTED_WILDCARD,
+ FAILURE_REJECTED_BY_SERVER,
+ FAILURE_PACKAGE_MANAGER,
+ UNKNOWN -> false
+ NO_RESPONSE,
+ FAILURE_TIMEOUT,
+ FAILURE_UNKNOWN,
+ FAILURE_REDIRECT -> true
+ }
+ }
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
new file mode 100644
index 0000000..a17f9c9
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain.worker
+
+import android.content.Context
+import android.content.pm.verify.domain.DomainVerificationManager
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import com.android.statementservice.domain.DomainVerifier
+
+abstract class BaseRequestWorker(
+ protected val appContext: Context,
+ protected val params: WorkerParameters
+) : CoroutineWorker(appContext, params) {
+
+ protected val verificationManager =
+ appContext.getSystemService(DomainVerificationManager::class.java)!!
+
+ protected val verifier = DomainVerifier.getInstance(appContext)
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/CollectV1Worker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/CollectV1Worker.kt
new file mode 100644
index 0000000..3a3aea9
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/CollectV1Worker.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain.worker
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.util.Log
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkerParameters
+import com.android.statementservice.utils.AndroidUtils
+import kotlinx.coroutines.coroutineScope
+
+class CollectV1Worker(appContext: Context, params: WorkerParameters) :
+ BaseRequestWorker(appContext, params) {
+
+ companion object {
+ private val TAG = CollectV1Worker::class.java.simpleName
+ private const val DEBUG = false
+
+ private const val VERIFICATION_ID_KEY = "verificationId"
+ private const val PACKAGE_NAME_KEY = "packageName"
+
+ fun buildRequest(verificationId: Int, packageName: String) =
+ OneTimeWorkRequestBuilder<CollectV1Worker>()
+ .setInputData(
+ Data.Builder()
+ .putInt(VERIFICATION_ID_KEY, verificationId)
+ .apply {
+ if (DEBUG) {
+ putString(PACKAGE_NAME_KEY, packageName)
+ }
+ }
+ .build()
+ )
+ .build()
+ }
+
+ override suspend fun doWork() = coroutineScope {
+ if (!AndroidUtils.isReceiverV1Enabled(appContext)) {
+ return@coroutineScope Result.success()
+ }
+
+ val inputData = params.inputData
+ val verificationId = inputData.getInt(VERIFICATION_ID_KEY, -1)
+ val successfulHosts = mutableListOf<String>()
+ val failedHosts = mutableListOf<String>()
+ inputData.keyValueMap.entries.forEach { (key, _) ->
+ when {
+ key.startsWith(SingleV1RequestWorker.HOST_SUCCESS_PREFIX) ->
+ successfulHosts += key.removePrefix(SingleV1RequestWorker.HOST_SUCCESS_PREFIX)
+ key.startsWith(SingleV1RequestWorker.HOST_FAILURE_PREFIX) ->
+ failedHosts += key.removePrefix(SingleV1RequestWorker.HOST_FAILURE_PREFIX)
+ }
+ }
+
+ if (DEBUG) {
+ val packageName = inputData.getString(PACKAGE_NAME_KEY)
+ Log.d(
+ TAG, "Domain verification v1 request for $packageName: " +
+ "success = $successfulHosts, failed = $failedHosts"
+ )
+ }
+
+ val resultCode = if (failedHosts.isEmpty()) {
+ PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS
+ } else {
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE
+ }
+
+ appContext.packageManager.verifyIntentFilter(verificationId, resultCode, failedHosts)
+
+ Result.success()
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
new file mode 100644
index 0000000..61ab2c2
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain.worker
+
+import android.content.Context
+import androidx.work.NetworkType
+import androidx.work.WorkerParameters
+import com.android.statementservice.domain.VerifyStatus
+import com.android.statementservice.utils.AndroidUtils
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.isActive
+import java.util.UUID
+
+/**
+ * Scheduled every 24 hours with [NetworkType.CONNECTED] and every 72 hours without any constraints
+ * to retry all domains for all packages with a failing error code.
+ */
+class RetryRequestWorker(
+ appContext: Context,
+ params: WorkerParameters
+) : BaseRequestWorker(appContext, params) {
+
+ data class VerifyResult(val domainSetId: UUID, val host: String, val status: VerifyStatus)
+
+ override suspend fun doWork() = coroutineScope {
+ if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
+ return@coroutineScope Result.success()
+ }
+
+ val packageNames = verificationManager.queryValidVerificationPackageNames()
+
+ verifier.collectHosts(packageNames)
+ .map { (domainSetId, packageName, host) ->
+ async {
+ if (isActive && !isStopped) {
+ val (_, status) = verifier.verifyHost(host, packageName, params.network)
+ VerifyResult(domainSetId, host, status)
+ } else {
+ // If the job gets cancelled, stop the remaining hosts, but continue the
+ // job to commit the results for hosts that were already requested.
+ null
+ }
+ }
+ }
+ .awaitAll()
+ .filterNotNull() // TODO(b/159952358): Fast fail packages which can't be retrieved.
+ .groupBy { it.domainSetId }
+ .forEach { (domainSetId, resultsById) ->
+ resultsById.groupBy { it.status }
+ .mapValues { it.value.map(VerifyResult::host).toSet() }
+ .forEach { (status, hosts) ->
+ verificationManager.setDomainVerificationStatus(
+ domainSetId,
+ hosts,
+ status.value
+ )
+ }
+ }
+
+ // Succeed regardless of results since this retry is best effort and not required
+ Result.success()
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
new file mode 100644
index 0000000..cd8a182
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain.worker
+
+import android.content.Context
+import android.util.Log
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequest
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkerParameters
+import com.android.statementservice.utils.AndroidUtils
+import kotlinx.coroutines.coroutineScope
+
+class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
+ BaseRequestWorker(appContext, params) {
+
+ companion object {
+ private val TAG = SingleV1RequestWorker::class.java.simpleName
+ private const val DEBUG = false
+
+ private const val PACKAGE_NAME_KEY = "packageName"
+ private const val HOST_KEY = "host"
+ const val HOST_SUCCESS_PREFIX = "hostSuccess:"
+ const val HOST_FAILURE_PREFIX = "hostFailure:"
+
+ fun buildRequest(
+ packageName: String,
+ host: String,
+ block: OneTimeWorkRequest.Builder.() -> Unit = {}
+ ) = OneTimeWorkRequestBuilder<SingleV1RequestWorker>()
+ .setInputData(
+ Data.Builder()
+ .putString(PACKAGE_NAME_KEY, packageName)
+ .putString(HOST_KEY, host)
+ .build()
+ )
+ .apply(block)
+ .build()
+ }
+
+ override suspend fun doWork() = coroutineScope {
+ if (!AndroidUtils.isReceiverV1Enabled(appContext)) {
+ return@coroutineScope Result.success()
+ }
+
+ val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
+ val host = params.inputData.getString(HOST_KEY)!!
+
+ val (result, status) = verifier.verifyHost(host, packageName, params.network)
+
+ if (DEBUG) {
+ Log.d(
+ TAG, "Domain verification v1 request for $packageName: " +
+ "host = $host, status = $status"
+ )
+ }
+
+ // Coerce failure results into success so that final collection task gets a chance to run
+ when (result) {
+ is Result.Success -> Result.success(
+ Data.Builder()
+ .putInt("$HOST_SUCCESS_PREFIX$host", status.value)
+ .build()
+ )
+ is Result.Failure -> Result.success(
+ Data.Builder()
+ .putInt("$HOST_FAILURE_PREFIX$host", status.value)
+ .build()
+ )
+ else -> result
+ }
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
new file mode 100644
index 0000000..562b132
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain.worker
+
+import android.content.Context
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequest
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkerParameters
+import com.android.statementservice.utils.AndroidUtils
+import kotlinx.coroutines.coroutineScope
+import java.util.UUID
+
+class SingleV2RequestWorker(appContext: Context, params: WorkerParameters) :
+ BaseRequestWorker(appContext, params) {
+
+ companion object {
+ private const val DOMAIN_SET_ID_KEY = "domainSetId"
+ private const val PACKAGE_NAME_KEY = "packageName"
+ private const val HOST_KEY = "host"
+
+ fun buildRequest(
+ domainSetId: UUID,
+ packageName: String,
+ host: String,
+ block: OneTimeWorkRequest.Builder.() -> Unit = {}
+ ) = OneTimeWorkRequestBuilder<SingleV2RequestWorker>()
+ .setInputData(
+ Data.Builder()
+ .putString(DOMAIN_SET_ID_KEY, domainSetId.toString())
+ .putString(PACKAGE_NAME_KEY, packageName)
+ .putString(HOST_KEY, host)
+ .build()
+ )
+ .apply(block)
+ .build()
+ }
+
+ override suspend fun doWork() = coroutineScope {
+ if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
+ return@coroutineScope Result.success()
+ }
+
+ val domainSetId = params.inputData.getString(DOMAIN_SET_ID_KEY)!!.let(UUID::fromString)
+ val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
+ val host = params.inputData.getString(HOST_KEY)!!
+
+ val (result, status) = verifier.verifyHost(host, packageName, params.network)
+
+ verificationManager.setDomainVerificationStatus(domainSetId, setOf(host), status.value)
+
+ result
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
new file mode 100644
index 0000000..455e8085
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.network.retriever
+
+import android.util.JsonReader
+import com.android.statementservice.retriever.AbstractAsset
+import com.android.statementservice.retriever.AssetFactory
+import com.android.statementservice.retriever.JsonParser
+import com.android.statementservice.retriever.Relation
+import com.android.statementservice.retriever.Statement
+import com.android.statementservice.utils.Result
+import com.android.statementservice.utils.StatementUtils
+import java.io.StringReader
+import java.util.ArrayList
+import com.android.statementservice.retriever.WebAsset
+import com.android.statementservice.retriever.AndroidAppAsset
+
+/**
+ * Parses JSON from the Digital Asset Links specification. For examples, see [WebAsset],
+ * [AndroidAppAsset], and [Statement].
+ */
+object StatementParser {
+
+ private const val FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string."
+ private const val FIELD_NOT_ARRAY_FORMAT_STRING = "Expected %s to be array."
+
+ /**
+ * Parses a JSON array of statements.
+ */
+ fun parseStatementList(statementList: String, source: AbstractAsset): Result<ParsedStatement> {
+ val statements: MutableList<Statement> = ArrayList()
+ val delegates: MutableList<String> = ArrayList()
+ StringReader(statementList).use { stringReader ->
+ JsonReader(stringReader).use { reader ->
+ reader.isLenient = false
+ reader.beginArray()
+ while (reader.hasNext()) {
+ val result = parseOneStatement(reader, source)
+ if (result is Result.Failure) {
+ continue
+ }
+ result as Result.Success
+ statements.addAll(result.value.statements)
+ delegates.addAll(result.value.delegates)
+ }
+ reader.endArray()
+ }
+ }
+ return Result.Success(ParsedStatement(statements, delegates))
+ }
+
+ /**
+ * Parses a single JSON statement.
+ */
+ fun parseStatement(statementString: String, source: AbstractAsset) =
+ StringReader(statementString).use { stringReader ->
+ JsonReader(stringReader).use { reader ->
+ reader.isLenient = false
+ parseOneStatement(reader, source)
+ }
+ }
+
+ /**
+ * Parses a single JSON statement. This method guarantees that exactly one JSON object
+ * will be consumed.
+ */
+ private fun parseOneStatement(
+ reader: JsonReader,
+ source: AbstractAsset
+ ): Result<ParsedStatement> {
+ val statement = JsonParser.parse(reader)
+ val delegate = statement.optString(StatementUtils.DELEGATE_FIELD_DELEGATE)
+ if (!delegate.isNullOrEmpty()) {
+ return Result.Success(ParsedStatement(emptyList(), listOfNotNull(delegate)))
+ }
+
+ val targetObject = statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_TARGET)
+ ?: return Result.Failure(
+ FIELD_NOT_STRING_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_TARGET)
+ )
+ val relations = statement.optJSONArray(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
+ ?: return Result.Failure(
+ FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
+ )
+ val target = AssetFactory.create(targetObject)
+
+ val statements = (0 until relations.length())
+ .map { relations.getString(it) }
+ .map(Relation::create)
+ .map { Statement.create(source, target, it) }
+ return Result.Success(ParsedStatement(statements, listOfNotNull(delegate)))
+ }
+
+ data class ParsedStatement(val statements: List<Statement>, val delegates: List<String>)
+}
diff --git a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementRetriever.kt b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementRetriever.kt
new file mode 100644
index 0000000..c27a26a
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementRetriever.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.network.retriever
+
+import android.content.Intent
+import android.net.Network
+import com.android.statementservice.retriever.AbstractAsset
+import com.android.statementservice.retriever.AndroidAppAsset
+import com.android.statementservice.retriever.Statement
+import com.android.statementservice.retriever.WebAsset
+import com.android.statementservice.utils.StatementUtils.tryOrNull
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.withContext
+import java.net.URL
+
+/**
+ * Retrieves the JSON configured at a given domain that's compliant with the Digital Asset Links
+ * specification, returning the list of statements which serve as assertions by the web server as
+ * to what other assets it can be connected with.
+ *
+ * Relevant to this app, it allows the website to report which Android app package and signature
+ * digest has been approved by the website owner, which considers them as the same author and safe
+ * to automatically delegate web [Intent]s to.
+ *
+ * The relevant data classes are [WebAsset], [AndroidAppAsset], and [Statement].
+ */
+class StatementRetriever {
+
+ companion object {
+ private const val HTTP_CONNECTION_TIMEOUT_MILLIS = 5000
+ private const val HTTP_CONTENT_SIZE_LIMIT_IN_BYTES = (1024 * 1024).toLong()
+ private const val MAX_INCLUDE_LEVEL = 1
+ private const val WELL_KNOWN_STATEMENT_PATH = "/.well-known/assetlinks.json"
+ }
+
+ private val fetcher = UrlFetcher()
+
+ data class Result(
+ val statements: List<Statement>,
+ val responseCode: Int?
+ ) {
+ companion object {
+ val EMPTY = Result(emptyList(), null)
+ }
+
+ constructor(statements: List<Statement>, webResult: UrlFetcher.Response) : this(
+ statements,
+ webResult.responseCode
+ )
+ }
+
+ suspend fun retrieve(source: AbstractAsset, network: Network? = null) = when (source) {
+ // TODO:(b/171219506): Does this have to be implemented?
+ is AndroidAppAsset -> null
+ is WebAsset -> retrieveFromWeb(source, network)
+ else -> null
+ }
+
+ private suspend fun retrieveFromWeb(asset: WebAsset, network: Network? = null): Result? {
+ val url = computeAssociationJsonUrl(asset) ?: return null
+ return retrieve(url, MAX_INCLUDE_LEVEL, asset, network)
+ }
+
+ private fun computeAssociationJsonUrl(asset: WebAsset) = tryOrNull {
+ URL(asset.scheme, asset.domain, asset.port, WELL_KNOWN_STATEMENT_PATH).toExternalForm()
+ }
+
+ private suspend fun retrieve(
+ urlString: String,
+ maxIncludeLevel: Int,
+ source: AbstractAsset,
+ network: Network? = null
+ ): Result {
+ if (maxIncludeLevel < 0) {
+ return Result.EMPTY
+ }
+
+ return withContext(Dispatchers.IO) {
+ val url = try {
+ @Suppress("BlockingMethodInNonBlockingContext")
+ URL(urlString)
+ } catch (ignored: Exception) {
+ return@withContext Result.EMPTY
+ }
+
+ val webResponse = fetcher.fetch(
+ url = url,
+ connectionTimeoutMillis = HTTP_CONNECTION_TIMEOUT_MILLIS,
+ fileSizeLimit = HTTP_CONTENT_SIZE_LIMIT_IN_BYTES,
+ network
+ ).successValueOrNull() ?: return@withContext Result.EMPTY
+
+ val content = webResponse.content ?: return@withContext Result(emptyList(), webResponse)
+ val (statements, delegates) = StatementParser.parseStatementList(content, source)
+ .successValueOrNull() ?: return@withContext Result(emptyList(), webResponse)
+
+ val delegatedStatements = delegates
+ .map { async { retrieve(it, maxIncludeLevel - 1, source).statements } }
+ .awaitAll()
+ .flatten()
+
+ Result(statements + delegatedStatements, webResponse)
+ }
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/network/retriever/UrlFetcher.kt b/packages/StatementService/src/com/android/statementservice/network/retriever/UrlFetcher.kt
new file mode 100644
index 0000000..5c1f5e0
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/network/retriever/UrlFetcher.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.network.retriever
+
+import android.net.Network
+import android.net.TrafficStats
+import android.util.Log
+import com.android.statementservice.utils.Result
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.withContext
+import java.net.HttpURLConnection
+import java.net.URL
+import java.nio.charset.Charset
+import javax.net.ssl.HttpsURLConnection
+
+class UrlFetcher {
+
+ companion object {
+ private val TAG = UrlFetcher::class.java.simpleName
+ }
+
+ suspend fun fetch(
+ url: URL,
+ connectionTimeoutMillis: Int,
+ fileSizeLimit: Long,
+ network: Network? = null
+ ) = withContext(Dispatchers.IO) {
+ TrafficStats.setThreadStatsTag(Thread.currentThread().id.toInt())
+ @Suppress("BlockingMethodInNonBlockingContext")
+ val connection =
+ ((network?.openConnection(url) ?: url.openConnection()) as HttpsURLConnection)
+ try {
+ connection.apply {
+ connectTimeout = connectionTimeoutMillis
+ readTimeout = connectionTimeoutMillis
+ useCaches = true
+ instanceFollowRedirects = false
+ addRequestProperty("Cache-Control", "max-stale=60")
+ }
+ val responseCode = connection.responseCode
+ when {
+ responseCode != HttpURLConnection.HTTP_OK -> {
+ Log.w(TAG, "The responses code is not 200 but $responseCode")
+ Result.Success(Response(responseCode))
+ }
+ connection.contentLength > fileSizeLimit -> {
+ Log.w(TAG, "The content size of the url is larger than $fileSizeLimit")
+ Result.Success(Response(responseCode))
+ }
+ else -> {
+ val content = async {
+ connection.inputStream
+ .bufferedReader(Charset.forName("UTF-8"))
+ .readText()
+ }
+
+ Result.Success(Response(responseCode, content.await()))
+ }
+ }
+ } catch (ignored: Throwable) {
+ Result.Failure(ignored)
+ } finally {
+ connection.disconnect()
+ }
+ }
+
+ data class Response(
+ val responseCode: Int,
+ val content: String? = null
+ )
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java b/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java
index 8d6fd66..4834626 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java
@@ -81,6 +81,9 @@
/**
* If this is the source asset of a statement file, should the retriever follow
* any insecure (non-HTTPS) include statements made by the asset.
+ *
+ * TODO(b/171219506): Why would this be allowed? Can it be removed, even for web assets?
+ * Android doesn't even allow non-secure traffic by default.
*/
public abstract boolean followInsecureInclude();
}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AbstractStatementRetriever.java b/packages/StatementService/src/com/android/statementservice/retriever/AbstractStatementRetriever.java
deleted file mode 100644
index fe9b99a..0000000
--- a/packages/StatementService/src/com/android/statementservice/retriever/AbstractStatementRetriever.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2015 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.statementservice.retriever;
-
-import android.content.Context;
-import android.annotation.NonNull;
-
-import java.util.List;
-
-/**
- * Retrieves the statements made by assets. This class is the entry point of the package.
- * <p>
- * An asset is an identifiable and addressable online entity that typically
- * provides some service or content. Examples of assets are websites, Android
- * apps, Twitter feeds, and Plus Pages.
- * <p>
- * Ownership of an asset is defined by being able to control it and speak for it.
- * An asset owner may establish a relationship between the asset and another
- * asset by making a statement about an intended relationship between the two.
- * An example of a relationship is permission delegation. For example, the owner
- * of a website (the webmaster) may delegate the ability the handle URLs to a
- * particular mobile app. Relationships are considered public information.
- * <p>
- * A particular kind of relationship (like permission delegation) defines a binary
- * relation on assets. The relation is not symmetric or transitive, nor is it
- * antisymmetric or anti-transitive.
- * <p>
- * A statement S(r, a, b) is an assertion that the relation r holds for the
- * ordered pair of assets (a, b). For example, taking r = "delegates permission
- * to view user's location", a = New York Times mobile app,
- * b = nytimes.com website, S(r, a, b) would be an assertion that "the New York
- * Times mobile app delegates its ability to use the user's location to the
- * nytimes.com website".
- * <p>
- * A statement S(r, a, b) is considered <b>reliable</b> if we have confidence that
- * the statement is true; the exact criterion depends on the kind of statement,
- * since some kinds of statements may be true on their face whereas others may
- * require multiple parties to agree.
- * <p>
- * For example, to get the statements made by www.example.com use:
- * <pre>
- * result = retrieveStatements(AssetFactory.create(
- * "{\"namespace\": \"web\", \"site\": \"https://www.google.com\"}"))
- * </pre>
- * {@code result} will contain the statements and the expiration time of this result. The statements
- * are considered reliable until the expiration time.
- */
-public abstract class AbstractStatementRetriever {
-
- /**
- * Returns the statements made by the {@code source} asset with ttl.
- *
- * @throws AssociationServiceException if the asset namespace is not supported.
- */
- public abstract Result retrieveStatements(AbstractAsset source)
- throws AssociationServiceException;
-
- /**
- * The retrieved statements and the expiration date.
- */
- public interface Result {
-
- /**
- * @return the retrieved statements.
- */
- @NonNull
- public List<Statement> getStatements();
-
- /**
- * @return the expiration time in millisecond.
- */
- public long getExpireMillis();
- }
-
- /**
- * Creates a new StatementRetriever that directly retrieves statements from the asset.
- *
- * <p> For web assets, {@link AbstractStatementRetriever} will try to retrieve the statement
- * file from URL: {@code [webAsset.site]/.well-known/assetlinks.json"} where {@code
- * [webAsset.site]} is in the form {@code http{s}://[hostname]:[optional_port]}. The file
- * should contain one JSON array of statements.
- *
- * <p> For Android assets, {@link AbstractStatementRetriever} will try to retrieve the statement
- * from the AndroidManifest.xml. The developer should add a {@code meta-data} tag under
- * {@code application} tag where attribute {@code android:name} equals "associated_assets"
- * and {@code android:recourse} points to a string array resource. Each entry in the string
- * array should contain exactly one statement in JSON format. Note that this implementation
- * can only return statements made by installed apps.
- */
- public static AbstractStatementRetriever createDirectRetriever(Context context) {
- return new DirectStatementRetriever(new URLFetcher(),
- new AndroidPackageInfoFetcher(context));
- }
-}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAsset.java b/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAsset.java
index 8ead90b..14ca232 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAsset.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAsset.java
@@ -16,6 +16,8 @@
package com.android.statementservice.retriever;
+import com.android.statementservice.utils.StatementUtils;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -34,7 +36,8 @@
* "sha256_cert_fingerprints": ["[SHA256 fingerprint of signing cert]", "[additional cert]", ...] }
*
* <p>For example, { "namespace": "android_app", "package_name": "com.test.mytestapp",
- * "sha256_cert_fingerprints": ["24:D9:B4:57:A6:42:FB:E6:E5:B8:D6:9E:7B:2D:C2:D1:CB:D1:77:17:1D:7F:D4:A9:16:10:11:AB:92:B9:8F:3F"]
+ * "sha256_cert_fingerprints": ["24:D9:B4:57:A6:42:FB:E6:E5:B8:D6:9E:7B:2D:C2:D1:CB:D1:77:17:1D
+ * :7F:D4:A9:16:10:11:AB:92:B9:8F:3F"]
* }
*
* <p>Given a signed APK, Java 7's commandline keytool can compute the fingerprint using:
@@ -43,7 +46,7 @@
* <p>Each entry in "sha256_cert_fingerprints" is a colon-separated hex string (e.g. 14:6D:E9:...)
* representing the certificate SHA-256 fingerprint.
*/
-/* package private */ final class AndroidAppAsset extends AbstractAsset {
+public final class AndroidAppAsset extends AbstractAsset {
private static final String MISSING_FIELD_FORMAT_STRING = "Expected %s to be set.";
private static final String MISSING_APPCERTS_FORMAT_STRING =
@@ -65,9 +68,10 @@
public String toJson() {
AssetJsonWriter writer = new AssetJsonWriter();
- writer.writeFieldLower(Utils.NAMESPACE_FIELD, Utils.NAMESPACE_ANDROID_APP);
- writer.writeFieldLower(Utils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME, mPackageName);
- writer.writeArrayUpper(Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS, mCertFingerprints);
+ writer.writeFieldLower(StatementUtils.NAMESPACE_FIELD,
+ StatementUtils.NAMESPACE_ANDROID_APP);
+ writer.writeFieldLower(StatementUtils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME, mPackageName);
+ writer.writeArrayUpper(StatementUtils.ANDROID_APP_ASSET_FIELD_CERT_FPS, mCertFingerprints);
return writer.closeAndGetString();
}
@@ -114,17 +118,17 @@
*/
public static AndroidAppAsset create(JSONObject asset)
throws AssociationServiceException {
- String packageName = asset.optString(Utils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME);
+ String packageName = asset.optString(StatementUtils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME);
if (packageName.equals("")) {
throw new AssociationServiceException(String.format(MISSING_FIELD_FORMAT_STRING,
- Utils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME));
+ StatementUtils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME));
}
- JSONArray certArray = asset.optJSONArray(Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS);
+ JSONArray certArray = asset.optJSONArray(StatementUtils.ANDROID_APP_ASSET_FIELD_CERT_FPS);
if (certArray == null || certArray.length() == 0) {
throw new AssociationServiceException(
String.format(MISSING_APPCERTS_FORMAT_STRING,
- Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS));
+ StatementUtils.ANDROID_APP_ASSET_FIELD_CERT_FPS));
}
List<String> certFingerprints = new ArrayList<>(certArray.length());
for (int i = 0; i < certArray.length(); i++) {
@@ -133,7 +137,7 @@
} catch (JSONException e) {
throw new AssociationServiceException(
String.format(APPCERT_NOT_STRING_FORMAT_STRING,
- Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS));
+ StatementUtils.ANDROID_APP_ASSET_FIELD_CERT_FPS));
}
}
@@ -143,7 +147,7 @@
/**
* Creates a new AndroidAppAsset.
*
- * @param packageName the package name of the Android app.
+ * @param packageName the package name of the Android app.
* @param certFingerprints at least one of the Android app signing certificate sha-256
* fingerprint.
*/
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAssetMatcher.java b/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAssetMatcher.java
index 8a9d838..45798fa 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAssetMatcher.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAssetMatcher.java
@@ -23,7 +23,7 @@
* Match assets that have the same 'package_name' field and have at least one common certificate
* fingerprint in 'sha256_cert_fingerprints' field.
*/
-/* package private */ final class AndroidAppAssetMatcher extends AbstractAssetMatcher {
+public final class AndroidAppAssetMatcher extends AbstractAssetMatcher {
private final AndroidAppAsset mQuery;
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AndroidPackageInfoFetcher.java b/packages/StatementService/src/com/android/statementservice/retriever/AndroidPackageInfoFetcher.java
deleted file mode 100644
index 1000c4c..0000000
--- a/packages/StatementService/src/com/android/statementservice/retriever/AndroidPackageInfoFetcher.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2015 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.statementservice.retriever;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources.NotFoundException;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Class that provides information about an android app from {@link PackageManager}.
- *
- * Visible for testing.
- *
- * @hide
- */
-public class AndroidPackageInfoFetcher {
-
- /**
- * The name of the metadata tag in AndroidManifest.xml that stores the associated asset array
- * ID. The metadata tag should use the android:resource attribute to point to an array resource
- * that contains the associated assets.
- */
- private static final String ASSOCIATED_ASSETS_KEY = "associated_assets";
-
- private Context mContext;
-
- public AndroidPackageInfoFetcher(Context context) {
- mContext = context;
- }
-
- /**
- * Returns the Sha-256 fingerprints of all certificates from the specified package as a list of
- * upper case HEX Strings with bytes separated by colons. Given an app {@link
- * android.content.pm.Signature}, the fingerprint can be computed as {@link
- * Utils#computeNormalizedSha256Fingerprint} {@code(signature.toByteArray())}.
- *
- * <p>Given a signed APK, Java 7's commandline keytool can compute the fingerprint using: {@code
- * keytool -list -printcert -jarfile signed_app.apk}
- *
- * <p>Example: "10:39:38:EE:45:37:E5:9E:8E:E7:92:F6:54:50:4F:B8:34:6F:C6:B3:46:D0:BB:C4:41:5F:C3:39:FC:FC:8E:C1"
- *
- * @throws NameNotFoundException if an app with packageName is not installed on the device.
- */
- public List<String> getCertFingerprints(String packageName) throws NameNotFoundException {
- return Utils.getCertFingerprintsFromPackageManager(packageName, mContext);
- }
-
- /**
- * Returns all statements that the specified package makes in its AndroidManifest.xml.
- *
- * @throws NameNotFoundException if the app is not installed on the device.
- */
- public List<String> getStatements(String packageName) throws NameNotFoundException {
- PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(
- packageName, PackageManager.GET_META_DATA);
- ApplicationInfo appInfo = packageInfo.applicationInfo;
- if (appInfo.metaData == null) {
- return Collections.<String>emptyList();
- }
- int tokenResourceId = appInfo.metaData.getInt(ASSOCIATED_ASSETS_KEY);
- if (tokenResourceId == 0) {
- return Collections.<String>emptyList();
- }
- try {
- return Arrays.asList(
- mContext.getPackageManager().getResourcesForApplication(packageName)
- .getStringArray(tokenResourceId));
- } catch (NotFoundException e) {
- return Collections.<String>emptyList();
- }
- }
-}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java b/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java
index 519d73a2..ac0bfab 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java
@@ -16,12 +16,14 @@
package com.android.statementservice.retriever;
+import com.android.statementservice.utils.StatementUtils;
+
import org.json.JSONObject;
/**
* Factory to create asset from JSON string.
*/
-/* package private */ final class AssetFactory {
+public final class AssetFactory {
private static final String FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string.";
@@ -34,15 +36,15 @@
*/
public static AbstractAsset create(JSONObject asset)
throws AssociationServiceException {
- String namespace = asset.optString(Utils.NAMESPACE_FIELD, null);
+ String namespace = asset.optString(StatementUtils.NAMESPACE_FIELD, null);
if (namespace == null) {
throw new AssociationServiceException(String.format(
- FIELD_NOT_STRING_FORMAT_STRING, Utils.NAMESPACE_FIELD));
+ FIELD_NOT_STRING_FORMAT_STRING, StatementUtils.NAMESPACE_FIELD));
}
- if (namespace.equals(Utils.NAMESPACE_WEB)) {
+ if (namespace.equals(StatementUtils.NAMESPACE_WEB)) {
return WebAsset.create(asset);
- } else if (namespace.equals(Utils.NAMESPACE_ANDROID_APP)) {
+ } else if (namespace.equals(StatementUtils.NAMESPACE_ANDROID_APP)) {
return AndroidAppAsset.create(asset);
} else {
throw new AssociationServiceException("Namespace " + namespace + " is not supported.");
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AssetMatcherFactory.java b/packages/StatementService/src/com/android/statementservice/retriever/AssetMatcherFactory.java
index 1a50757..7773668 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/AssetMatcherFactory.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AssetMatcherFactory.java
@@ -16,6 +16,8 @@
package com.android.statementservice.retriever;
+import com.android.statementservice.utils.StatementUtils;
+
import org.json.JSONException;
import org.json.JSONObject;
@@ -31,15 +33,15 @@
JSONException {
JSONObject queryObject = new JSONObject(query);
- String namespace = queryObject.optString(Utils.NAMESPACE_FIELD, null);
+ String namespace = queryObject.optString(StatementUtils.NAMESPACE_FIELD, null);
if (namespace == null) {
throw new AssociationServiceException(String.format(
- FIELD_NOT_STRING_FORMAT_STRING, Utils.NAMESPACE_FIELD));
+ FIELD_NOT_STRING_FORMAT_STRING, StatementUtils.NAMESPACE_FIELD));
}
- if (namespace.equals(Utils.NAMESPACE_WEB)) {
+ if (namespace.equals(StatementUtils.NAMESPACE_WEB)) {
return new WebAssetMatcher(WebAsset.create(queryObject));
- } else if (namespace.equals(Utils.NAMESPACE_ANDROID_APP)) {
+ } else if (namespace.equals(StatementUtils.NAMESPACE_ANDROID_APP)) {
return new AndroidAppAssetMatcher(AndroidAppAsset.create(queryObject));
} else {
throw new AssociationServiceException(
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java b/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java
deleted file mode 100644
index 9839329..0000000
--- a/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2015 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.statementservice.retriever;
-
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Log;
-
-import org.json.JSONException;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * An implementation of {@link AbstractStatementRetriever} that directly retrieves statements from
- * the asset.
- */
-/* package private */ final class DirectStatementRetriever extends AbstractStatementRetriever {
-
- private static final long DO_NOT_CACHE_RESULT = 0L;
- private static final int HTTP_CONNECTION_TIMEOUT_MILLIS = 5000;
- private static final int HTTP_CONNECTION_BACKOFF_MILLIS = 3000;
- private static final int HTTP_CONNECTION_RETRY = 3;
- private static final long HTTP_CONTENT_SIZE_LIMIT_IN_BYTES = 1024 * 1024;
- private static final int MAX_INCLUDE_LEVEL = 1;
- private static final String WELL_KNOWN_STATEMENT_PATH = "/.well-known/assetlinks.json";
-
- private final URLFetcher mUrlFetcher;
- private final AndroidPackageInfoFetcher mAndroidFetcher;
-
- /**
- * An immutable value type representing the retrieved statements and the expiration date.
- */
- public static class Result implements AbstractStatementRetriever.Result {
-
- private final List<Statement> mStatements;
- private final Long mExpireMillis;
-
- @Override
- public List<Statement> getStatements() {
- return mStatements;
- }
-
- @Override
- public long getExpireMillis() {
- return mExpireMillis;
- }
-
- private Result(List<Statement> statements, Long expireMillis) {
- mStatements = statements;
- mExpireMillis = expireMillis;
- }
-
- public static Result create(List<Statement> statements, Long expireMillis) {
- return new Result(statements, expireMillis);
- }
-
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder();
- result.append("Result: ");
- result.append(mStatements.toString());
- result.append(", mExpireMillis=");
- result.append(mExpireMillis);
- return result.toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- Result result = (Result) o;
-
- if (!mExpireMillis.equals(result.mExpireMillis)) {
- return false;
- }
- if (!mStatements.equals(result.mStatements)) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = mStatements.hashCode();
- result = 31 * result + mExpireMillis.hashCode();
- return result;
- }
- }
-
- public DirectStatementRetriever(URLFetcher urlFetcher,
- AndroidPackageInfoFetcher androidFetcher) {
- this.mUrlFetcher = urlFetcher;
- this.mAndroidFetcher = androidFetcher;
- }
-
- @Override
- public Result retrieveStatements(AbstractAsset source) throws AssociationServiceException {
- if (source instanceof AndroidAppAsset) {
- return retrieveFromAndroid((AndroidAppAsset) source);
- } else if (source instanceof WebAsset) {
- return retrieveFromWeb((WebAsset) source);
- } else {
- throw new AssociationServiceException("Namespace is not supported.");
- }
- }
-
- private String computeAssociationJsonUrl(WebAsset asset) {
- try {
- return new URL(asset.getScheme(), asset.getDomain(), asset.getPort(),
- WELL_KNOWN_STATEMENT_PATH)
- .toExternalForm();
- } catch (MalformedURLException e) {
- throw new AssertionError("Invalid domain name in database.");
- }
- }
-
- private Result retrieveStatementFromUrl(String urlString, int maxIncludeLevel,
- AbstractAsset source)
- throws AssociationServiceException {
- List<Statement> statements = new ArrayList<Statement>();
- if (maxIncludeLevel < 0) {
- return Result.create(statements, DO_NOT_CACHE_RESULT);
- }
-
- WebContent webContent;
- try {
- URL url = new URL(urlString);
- if (!source.followInsecureInclude()
- && !url.getProtocol().toLowerCase().equals("https")) {
- return Result.create(statements, DO_NOT_CACHE_RESULT);
- }
- webContent = mUrlFetcher.getWebContentFromUrlWithRetry(url,
- HTTP_CONTENT_SIZE_LIMIT_IN_BYTES, HTTP_CONNECTION_TIMEOUT_MILLIS,
- HTTP_CONNECTION_BACKOFF_MILLIS, HTTP_CONNECTION_RETRY);
- } catch (IOException | InterruptedException e) {
- return Result.create(statements, DO_NOT_CACHE_RESULT);
- }
-
- try {
- ParsedStatement result = StatementParser
- .parseStatementList(webContent.getContent(), source);
- statements.addAll(result.getStatements());
- for (String delegate : result.getDelegates()) {
- statements.addAll(
- retrieveStatementFromUrl(delegate, maxIncludeLevel - 1, source)
- .getStatements());
- }
- return Result.create(statements, webContent.getExpireTimeMillis());
- } catch (JSONException | IOException e) {
- return Result.create(statements, DO_NOT_CACHE_RESULT);
- }
- }
-
- private Result retrieveFromWeb(WebAsset asset)
- throws AssociationServiceException {
- return retrieveStatementFromUrl(computeAssociationJsonUrl(asset), MAX_INCLUDE_LEVEL, asset);
- }
-
- private Result retrieveFromAndroid(AndroidAppAsset asset) throws AssociationServiceException {
- try {
- List<String> delegates = new ArrayList<String>();
- List<Statement> statements = new ArrayList<Statement>();
-
- List<String> certFps = mAndroidFetcher.getCertFingerprints(asset.getPackageName());
- if (!Utils.hasCommonString(certFps, asset.getCertFingerprints())) {
- throw new AssociationServiceException(
- "Specified certs don't match the installed app.");
- }
-
- AndroidAppAsset actualSource = AndroidAppAsset.create(asset.getPackageName(), certFps);
- for (String statementJson : mAndroidFetcher.getStatements(asset.getPackageName())) {
- ParsedStatement result =
- StatementParser.parseStatement(statementJson, actualSource);
- statements.addAll(result.getStatements());
- delegates.addAll(result.getDelegates());
- }
-
- for (String delegate : delegates) {
- statements.addAll(retrieveStatementFromUrl(delegate, MAX_INCLUDE_LEVEL,
- actualSource).getStatements());
- }
-
- return Result.create(statements, DO_NOT_CACHE_RESULT);
- } catch (JSONException | IOException | NameNotFoundException e) {
- Log.w(DirectStatementRetriever.class.getSimpleName(), e);
- return Result.create(Collections.<Statement>emptyList(), DO_NOT_CACHE_RESULT);
- }
- }
-}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/ParsedStatement.java b/packages/StatementService/src/com/android/statementservice/retriever/ParsedStatement.java
deleted file mode 100644
index 9446e66..0000000
--- a/packages/StatementService/src/com/android/statementservice/retriever/ParsedStatement.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2015 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.statementservice.retriever;
-
-import java.util.List;
-
-/**
- * A class that stores a list of statement and/or a list of delegate url.
- */
-/* package private */ final class ParsedStatement {
-
- private final List<Statement> mStatements;
- private final List<String> mDelegates;
-
- public ParsedStatement(List<Statement> statements, List<String> delegates) {
- this.mStatements = statements;
- this.mDelegates = delegates;
- }
-
- public List<Statement> getStatements() {
- return mStatements;
- }
-
- public List<String> getDelegates() {
- return mDelegates;
- }
-}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/Statement.java b/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
index 0f40a62..f8bab3e 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
@@ -17,6 +17,11 @@
package com.android.statementservice.retriever;
import android.annotation.NonNull;
+import android.net.Network;
+
+import com.android.statementservice.network.retriever.StatementRetriever;
+
+import kotlin.coroutines.Continuation;
/**
* An immutable value type representing a statement, consisting of a source, target, and relation.
@@ -31,9 +36,9 @@
* }
* </pre>
*
- * Then invoking {@link AbstractStatementRetriever#retrieveStatements(AbstractAsset)} will return a
- * {@link Statement} with {@link #getSource} equal to the input parameter, {@link #getRelation}
- * equal to
+ * Then invoking {@link StatementRetriever#retrieve(AbstractAsset, Network, Continuation)} will
+ * return a {@link Statement} with {@link #getSource} equal to the input parameter,
+ * {@link #getRelation} equal to
*
* <pre>Relation.create("delegate_permission", "common.get_login_creds");</pre>
*
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java b/packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java
deleted file mode 100644
index 0369718..0000000
--- a/packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2015 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.statementservice.retriever;
-
-import android.util.JsonReader;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Utility class that parses JSON-formatted statements.
- */
-/* package private */ final class StatementParser {
-
- private static final String FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string.";
- private static final String FIELD_NOT_ARRAY_FORMAT_STRING = "Expected %s to be array.";
-
- /**
- * Parses a JSON array of statements.
- */
- static ParsedStatement parseStatementList(String statementList, AbstractAsset source)
- throws JSONException, IOException {
- List<Statement> statements = new ArrayList<Statement>();
- List<String> delegates = new ArrayList<String>();
-
- JsonReader reader = new JsonReader(new StringReader(statementList));
- reader.setLenient(false);
-
- reader.beginArray();
- while (reader.hasNext()) {
- ParsedStatement result;
- try {
- result = parseStatement(reader, source);
- } catch (AssociationServiceException e) {
- // The element in the array is well formatted Json but not a well-formed Statement.
- continue;
- }
- statements.addAll(result.getStatements());
- delegates.addAll(result.getDelegates());
- }
- reader.endArray();
-
- return new ParsedStatement(statements, delegates);
- }
-
- /**
- * Parses a single JSON statement.
- */
- static ParsedStatement parseStatement(String statementString, AbstractAsset source)
- throws AssociationServiceException, IOException, JSONException {
- JsonReader reader = new JsonReader(new StringReader(statementString));
- reader.setLenient(false);
- return parseStatement(reader, source);
- }
-
- /**
- * Parses a single JSON statement. This method guarantees that exactly one JSON object
- * will be consumed.
- */
- static ParsedStatement parseStatement(JsonReader reader, AbstractAsset source)
- throws JSONException, AssociationServiceException, IOException {
- List<Statement> statements = new ArrayList<Statement>();
- List<String> delegates = new ArrayList<String>();
-
- JSONObject statement = JsonParser.parse(reader);
-
- if (statement.optString(Utils.DELEGATE_FIELD_DELEGATE, null) != null) {
- delegates.add(statement.optString(Utils.DELEGATE_FIELD_DELEGATE));
- } else {
- JSONObject targetObject = statement.optJSONObject(Utils.ASSET_DESCRIPTOR_FIELD_TARGET);
- if (targetObject == null) {
- throw new AssociationServiceException(String.format(
- FIELD_NOT_STRING_FORMAT_STRING, Utils.ASSET_DESCRIPTOR_FIELD_TARGET));
- }
-
- JSONArray relations = statement.optJSONArray(Utils.ASSET_DESCRIPTOR_FIELD_RELATION);
- if (relations == null) {
- throw new AssociationServiceException(String.format(
- FIELD_NOT_ARRAY_FORMAT_STRING, Utils.ASSET_DESCRIPTOR_FIELD_RELATION));
- }
-
- AbstractAsset target = AssetFactory.create(targetObject);
- for (int i = 0; i < relations.length(); i++) {
- statements.add(Statement
- .create(source, target, Relation.create(relations.getString(i))));
- }
- }
-
- return new ParsedStatement(statements, delegates);
- }
-}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/URLFetcher.java b/packages/StatementService/src/com/android/statementservice/retriever/URLFetcher.java
deleted file mode 100644
index 23cd832..0000000
--- a/packages/StatementService/src/com/android/statementservice/retriever/URLFetcher.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2015 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.statementservice.retriever;
-
-import android.util.Log;
-
-import com.android.volley.Cache;
-import com.android.volley.NetworkResponse;
-import com.android.volley.toolbox.HttpHeaderParser;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-/**
- * Helper class for fetching HTTP or HTTPS URL.
- *
- * Visible for testing.
- *
- * @hide
- */
-public class URLFetcher {
- private static final String TAG = URLFetcher.class.getSimpleName();
-
- private static final long DO_NOT_CACHE_RESULT = 0L;
- private static final int INPUT_BUFFER_SIZE_IN_BYTES = 1024;
-
- /**
- * Fetches the specified url and returns the content and ttl.
- *
- * <p>
- * Retry {@code retry} times if the connection failed or timed out for any reason.
- * HTTP error code (e.g. 404/500) won't be retried.
- *
- * @throws IOException if it can't retrieve the content due to a network problem.
- * @throws AssociationServiceException if the URL scheme is not http or https or the content
- * length exceeds {code fileSizeLimit}.
- */
- public WebContent getWebContentFromUrlWithRetry(URL url, long fileSizeLimit,
- int connectionTimeoutMillis, int backoffMillis, int retry)
- throws AssociationServiceException, IOException, InterruptedException {
- if (retry <= 0) {
- throw new IllegalArgumentException("retry should be a postive inetger.");
- }
- while (retry > 0) {
- try {
- return getWebContentFromUrl(url, fileSizeLimit, connectionTimeoutMillis);
- } catch (IOException e) {
- retry--;
- if (retry == 0) {
- throw e;
- }
- }
-
- Thread.sleep(backoffMillis);
- }
-
- // Should never reach here.
- return null;
- }
-
- /**
- * Fetches the specified url and returns the content and ttl.
- *
- * @throws IOException if it can't retrieve the content due to a network problem.
- * @throws AssociationServiceException if the URL scheme is not http or https or the content
- * length exceeds {code fileSizeLimit}.
- */
- public WebContent getWebContentFromUrl(URL url, long fileSizeLimit, int connectionTimeoutMillis)
- throws AssociationServiceException, IOException {
- final String scheme = url.getProtocol().toLowerCase(Locale.US);
- if (!scheme.equals("http") && !scheme.equals("https")) {
- throw new IllegalArgumentException("The url protocol should be on http or https.");
- }
-
- HttpURLConnection connection = null;
- try {
- connection = (HttpURLConnection) url.openConnection();
- connection.setInstanceFollowRedirects(true);
- connection.setConnectTimeout(connectionTimeoutMillis);
- connection.setReadTimeout(connectionTimeoutMillis);
- connection.setUseCaches(true);
- connection.setInstanceFollowRedirects(false);
- connection.addRequestProperty("Cache-Control", "max-stale=60");
-
- if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
- Log.e(TAG, "The responses code is not 200 but " + connection.getResponseCode());
- return new WebContent("", DO_NOT_CACHE_RESULT);
- }
-
- if (connection.getContentLength() > fileSizeLimit) {
- Log.e(TAG, "The content size of the url is larger than " + fileSizeLimit);
- return new WebContent("", DO_NOT_CACHE_RESULT);
- }
-
- Long expireTimeMillis = getExpirationTimeMillisFromHTTPHeader(
- connection.getHeaderFields());
-
- return new WebContent(inputStreamToString(
- connection.getInputStream(), connection.getContentLength(), fileSizeLimit),
- expireTimeMillis);
- } finally {
- if (connection != null) {
- connection.disconnect();
- }
- }
- }
-
- /**
- * Visible for testing.
- * @hide
- */
- public static String inputStreamToString(InputStream inputStream, int length, long sizeLimit)
- throws IOException, AssociationServiceException {
- if (length < 0) {
- length = 0;
- }
- ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
- BufferedInputStream bis = new BufferedInputStream(inputStream);
- byte[] buffer = new byte[INPUT_BUFFER_SIZE_IN_BYTES];
- int len = 0;
- while ((len = bis.read(buffer)) != -1) {
- baos.write(buffer, 0, len);
- if (baos.size() > sizeLimit) {
- throw new AssociationServiceException("The content size of the url is larger than "
- + sizeLimit);
- }
- }
- return baos.toString("UTF-8");
- }
-
- /**
- * Parses the HTTP headers to compute the ttl.
- *
- * @param headers a map that map the header key to the header values. Can be null.
- * @return the ttl in millisecond or null if the ttl is not specified in the header.
- */
- private Long getExpirationTimeMillisFromHTTPHeader(Map<String, List<String>> headers) {
- if (headers == null) {
- return null;
- }
- Map<String, String> joinedHeaders = joinHttpHeaders(headers);
-
- NetworkResponse response = new NetworkResponse(null, joinedHeaders);
- Cache.Entry cachePolicy = HttpHeaderParser.parseCacheHeaders(response);
-
- if (cachePolicy == null) {
- // Cache is disabled, set the expire time to 0.
- return DO_NOT_CACHE_RESULT;
- } else if (cachePolicy.ttl == 0) {
- // Cache policy is not specified, set the expire time to 0.
- return DO_NOT_CACHE_RESULT;
- } else {
- // cachePolicy.ttl is actually the expire timestamp in millisecond.
- return cachePolicy.ttl;
- }
- }
-
- /**
- * Converts an HTTP header map of the format provided by {@linkHttpUrlConnection} to a map of
- * the format accepted by {@link HttpHeaderParser}. It does this by joining all the entries for
- * a given header key with ", ".
- */
- private Map<String, String> joinHttpHeaders(Map<String, List<String>> headers) {
- Map<String, String> joinedHeaders = new HashMap<String, String>();
- for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
- List<String> values = entry.getValue();
- if (values.size() == 1) {
- joinedHeaders.put(entry.getKey(), values.get(0));
- } else {
- joinedHeaders.put(entry.getKey(), Utils.joinStrings(", ", values));
- }
- }
- return joinedHeaders;
- }
-}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/Utils.java b/packages/StatementService/src/com/android/statementservice/retriever/Utils.java
deleted file mode 100644
index afb4c75..0000000
--- a/packages/StatementService/src/com/android/statementservice/retriever/Utils.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2015 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.statementservice.retriever;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.Signature;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * Utility library for computing certificate fingerprints. Also includes fields name used by
- * Statement JSON string.
- */
-public final class Utils {
-
- private Utils() {}
-
- /**
- * Field name for namespace.
- */
- public static final String NAMESPACE_FIELD = "namespace";
-
- /**
- * Supported asset namespaces.
- */
- public static final String NAMESPACE_WEB = "web";
- public static final String NAMESPACE_ANDROID_APP = "android_app";
-
- /**
- * Field names in a web asset descriptor.
- */
- public static final String WEB_ASSET_FIELD_SITE = "site";
-
- /**
- * Field names in a Android app asset descriptor.
- */
- public static final String ANDROID_APP_ASSET_FIELD_PACKAGE_NAME = "package_name";
- public static final String ANDROID_APP_ASSET_FIELD_CERT_FPS = "sha256_cert_fingerprints";
-
- /**
- * Field names in a statement.
- */
- public static final String ASSET_DESCRIPTOR_FIELD_RELATION = "relation";
- public static final String ASSET_DESCRIPTOR_FIELD_TARGET = "target";
- public static final String DELEGATE_FIELD_DELEGATE = "include";
-
- private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- 'A', 'B', 'C', 'D', 'E', 'F' };
-
- /**
- * Joins a list of strings, by placing separator between each string. For example,
- * {@code joinStrings("; ", Arrays.asList(new String[]{"a", "b", "c"}))} returns
- * "{@code a; b; c}".
- */
- public static String joinStrings(String separator, List<String> strings) {
- switch(strings.size()) {
- case 0:
- return "";
- case 1:
- return strings.get(0);
- default:
- StringBuilder joiner = new StringBuilder();
- boolean first = true;
- for (String field : strings) {
- if (first) {
- first = false;
- } else {
- joiner.append(separator);
- }
- joiner.append(field);
- }
- return joiner.toString();
- }
- }
-
- /**
- * Returns the normalized sha-256 fingerprints of a given package according to the Android
- * package manager.
- */
- public static List<String> getCertFingerprintsFromPackageManager(String packageName,
- Context context) throws NameNotFoundException {
- Signature[] signatures = context.getPackageManager().getPackageInfo(packageName,
- PackageManager.GET_SIGNATURES).signatures;
- ArrayList<String> result = new ArrayList<String>(signatures.length);
- for (Signature sig : signatures) {
- result.add(computeNormalizedSha256Fingerprint(sig.toByteArray()));
- }
- return result;
- }
-
- /**
- * Computes the hash of the byte array using the specified algorithm, returning a hex string
- * with a colon between each byte.
- */
- public static String computeNormalizedSha256Fingerprint(byte[] signature) {
- MessageDigest digester;
- try {
- digester = MessageDigest.getInstance("SHA-256");
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError("No SHA-256 implementation found.");
- }
- digester.update(signature);
- return byteArrayToHexString(digester.digest());
- }
-
- /**
- * Returns true if there is at least one common string between the two lists of string.
- */
- public static boolean hasCommonString(List<String> list1, List<String> list2) {
- HashSet<String> set2 = new HashSet<>(list2);
- for (String string : list1) {
- if (set2.contains(string)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Converts the byte array to an lowercase hexadecimal digits String with a colon character (:)
- * between each byte.
- */
- private static String byteArrayToHexString(byte[] array) {
- if (array.length == 0) {
- return "";
- }
- char[] buf = new char[array.length * 3 - 1];
-
- int bufIndex = 0;
- for (int i = 0; i < array.length; i++) {
- byte b = array[i];
- if (i > 0) {
- buf[bufIndex++] = ':';
- }
- buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
- buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
- }
- return new String(buf);
- }
-}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/WebAsset.java b/packages/StatementService/src/com/android/statementservice/retriever/WebAsset.java
index 947087a..608ce69 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/WebAsset.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/WebAsset.java
@@ -16,6 +16,8 @@
package com.android.statementservice.retriever;
+import com.android.statementservice.utils.StatementUtils;
+
import org.json.JSONObject;
import java.net.MalformedURLException;
@@ -36,7 +38,7 @@
* <p>The only protocol supported now are https and http. If the optional port is not specified,
* the default for each protocol will be used (i.e. 80 for http and 443 for https).
*/
-/* package private */ final class WebAsset extends AbstractAsset {
+public final class WebAsset extends AbstractAsset {
private static final String MISSING_FIELD_FORMAT_STRING = "Expected %s to be set.";
private static final String SCHEME_HTTP = "http";
@@ -73,8 +75,8 @@
public String toJson() {
AssetJsonWriter writer = new AssetJsonWriter();
- writer.writeFieldLower(Utils.NAMESPACE_FIELD, Utils.NAMESPACE_WEB);
- writer.writeFieldLower(Utils.WEB_ASSET_FIELD_SITE, mUrl.toExternalForm());
+ writer.writeFieldLower(StatementUtils.NAMESPACE_FIELD, StatementUtils.NAMESPACE_WEB);
+ writer.writeFieldLower(StatementUtils.WEB_ASSET_FIELD_SITE, mUrl.toExternalForm());
return writer.closeAndGetString();
}
@@ -119,14 +121,14 @@
*/
protected static WebAsset create(JSONObject asset)
throws AssociationServiceException {
- if (asset.optString(Utils.WEB_ASSET_FIELD_SITE).equals("")) {
+ if (asset.optString(StatementUtils.WEB_ASSET_FIELD_SITE).equals("")) {
throw new AssociationServiceException(String.format(MISSING_FIELD_FORMAT_STRING,
- Utils.WEB_ASSET_FIELD_SITE));
+ StatementUtils.WEB_ASSET_FIELD_SITE));
}
URL url;
try {
- url = new URL(asset.optString(Utils.WEB_ASSET_FIELD_SITE));
+ url = new URL(asset.optString(StatementUtils.WEB_ASSET_FIELD_SITE));
} catch (MalformedURLException e) {
throw new AssociationServiceException("Url is not well formatted.", e);
}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/WebContent.java b/packages/StatementService/src/com/android/statementservice/retriever/WebContent.java
index 86a635c..23b1f9b 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/WebContent.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/WebContent.java
@@ -27,10 +27,12 @@
private final String mContent;
private final Long mExpireTimeMillis;
+ private final int mResponseCode;
- public WebContent(String content, Long expireTimeMillis) {
+ public WebContent(String content, Long expireTimeMillis, int responseCode) {
mContent = content;
mExpireTimeMillis = expireTimeMillis;
+ mResponseCode = responseCode;
}
/**
@@ -46,4 +48,8 @@
public String getContent() {
return mContent;
}
+
+ public int getResponseCode() {
+ return mResponseCode;
+ }
}
diff --git a/packages/StatementService/src/com/android/statementservice/utils/AndroidUtils.kt b/packages/StatementService/src/com/android/statementservice/utils/AndroidUtils.kt
new file mode 100644
index 0000000..7fe0a02
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/utils/AndroidUtils.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.utils
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationUserState
+import com.android.statementservice.domain.DomainVerificationReceiverV1
+import com.android.statementservice.domain.DomainVerificationReceiverV2
+
+// Top level extensions for models to allow Kotlin deconstructing declarations
+
+operator fun DomainVerificationRequest.component1() = packageNames
+
+operator fun DomainVerificationInfo.component1() = identifier
+operator fun DomainVerificationInfo.component2() = packageName
+operator fun DomainVerificationInfo.component3() = hostToStateMap
+
+operator fun DomainVerificationUserState.component1() = identifier
+operator fun DomainVerificationUserState.component2() = packageName
+operator fun DomainVerificationUserState.component3() = user
+operator fun DomainVerificationUserState.component4() = isLinkHandlingAllowed
+operator fun DomainVerificationUserState.component5() = hostToStateMap
+
+object AndroidUtils {
+
+ fun isReceiverV1Enabled(context: Context): Boolean {
+ val receiver = ComponentName(context, DomainVerificationReceiverV1::class.java)
+ return when (context.packageManager.getComponentEnabledSetting(receiver)) {
+ // Must change this if the manifest ever changes
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> true
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
+ else -> false
+ }
+ }
+
+ fun isReceiverV2Enabled(context: Context): Boolean {
+ val receiver = ComponentName(context, DomainVerificationReceiverV2::class.java)
+ return when (context.packageManager.getComponentEnabledSetting(receiver)) {
+ // Must change this if the manifest ever changes
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> false
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
+ else -> false
+ }
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/utils/Result.kt b/packages/StatementService/src/com/android/statementservice/utils/Result.kt
new file mode 100644
index 0000000..f23a010
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/utils/Result.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.utils
+
+sealed class Result<T> {
+
+ fun successValueOrNull() = (this as? Success<T>)?.value
+
+ data class Success<T>(val value: T) : Result<T>()
+ data class Failure<T>(val message: String? = null, val throwable: Throwable? = null) :
+ Result<T>() {
+
+ constructor(message: String) : this(message = message, throwable = null)
+ constructor(throwable: Throwable) : this(message = null, throwable = throwable)
+
+ @Suppress("UNCHECKED_CAST")
+ fun <T> asType() = this as Result<T>
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt b/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
new file mode 100644
index 0000000..92d752c
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.statementservice.utils
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.util.Patterns
+import com.android.statementservice.retriever.Relation
+import java.net.URL
+import java.security.MessageDigest
+
+internal object StatementUtils {
+
+ /**
+ * Field name for namespace.
+ */
+ const val NAMESPACE_FIELD = "namespace"
+
+ /**
+ * Supported asset namespaces.
+ */
+ const val NAMESPACE_WEB = "web"
+ const val NAMESPACE_ANDROID_APP = "android_app"
+
+ /**
+ * Field names in a web asset descriptor.
+ */
+ const val WEB_ASSET_FIELD_SITE = "site"
+
+ /**
+ * Field names in a Android app asset descriptor.
+ */
+ const val ANDROID_APP_ASSET_FIELD_PACKAGE_NAME = "package_name"
+ const val ANDROID_APP_ASSET_FIELD_CERT_FPS = "sha256_cert_fingerprints"
+
+ /**
+ * Field names in a statement.
+ */
+ const val ASSET_DESCRIPTOR_FIELD_RELATION = "relation"
+ const val ASSET_DESCRIPTOR_FIELD_TARGET = "target"
+ const val DELEGATE_FIELD_DELEGATE = "include"
+
+ val HEX_DIGITS =
+ charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
+
+ val RELATION by lazy { Relation.create("delegate_permission/common.handle_all_urls") }
+ private const val ANDROID_ASSET_FORMAT =
+ """{"namespace": "android_app", "package_name": "%s", "sha256_cert_fingerprints": [%s]}"""
+ private const val WEB_ASSET_FORMAT = """{"namespace": "web", "site": "%s"}"""
+
+ private val digesterSha256 by lazy { tryOrNull { MessageDigest.getInstance("SHA-256") } }
+
+ internal inline fun <T> tryOrNull(block: () -> T) =
+ try {
+ block()
+ } catch (ignored: Exception) {
+ null
+ }
+
+ /**
+ * Returns the normalized sha-256 fingerprints of a given package according to the Android
+ * package manager.
+ */
+ fun getCertFingerprintsFromPackageManager(
+ context: Context,
+ packageName: String
+ ): Result<List<String>> {
+ val signingInfo = try {
+ context.packageManager.getPackageInfo(
+ packageName,
+ PackageManager.GET_SIGNING_CERTIFICATES or PackageManager.MATCH_ANY_USER
+ )
+ .signingInfo
+ } catch (e: Exception) {
+ return Result.Failure(e)
+ }
+ return if (signingInfo.hasMultipleSigners()) {
+ signingInfo.apkContentsSigners
+ } else {
+ signingInfo.signingCertificateHistory
+ }.map {
+ val result = computeNormalizedSha256Fingerprint(it.toByteArray())
+ if (result is Result.Failure) {
+ return result.asType()
+ } else {
+ (result as Result.Success).value
+ }
+ }.let { Result.Success(it) }
+ }
+
+ /**
+ * Computes the hash of the byte array using the specified algorithm, returning a hex string
+ * with a colon between each byte.
+ */
+ fun computeNormalizedSha256Fingerprint(signature: ByteArray) =
+ digesterSha256?.digest(signature)
+ ?.let(StatementUtils::bytesToHexString)
+ ?.let { Result.Success(it) }
+ ?: Result.Failure()
+
+ private fun bytesToHexString(bytes: ByteArray): String {
+ val hexChars = CharArray(bytes.size * 3 - 1)
+ var bufIndex = 0
+ for (index in bytes.indices) {
+ val byte = bytes[index].toInt() and 0xFF
+ if (index > 0) {
+ hexChars[bufIndex++] = ':'
+ }
+
+ hexChars[bufIndex++] = HEX_DIGITS[byte ushr 4]
+ hexChars[bufIndex++] = HEX_DIGITS[byte and 0x0F]
+ }
+ return String(hexChars)
+ }
+
+ fun createAndroidAssetString(context: Context, packageName: String): Result<String> {
+ val result = getCertFingerprintsFromPackageManager(context, packageName)
+ if (result is Result.Failure) {
+ return result.asType()
+ }
+ return Result.Success(
+ ANDROID_ASSET_FORMAT.format(
+ packageName,
+ (result as Result.Success).value.joinToString(separator = "\", \"")
+ )
+ )
+ }
+
+ fun createAndroidAsset(packageName: String, certFingerprints: List<String>) =
+ String.format(
+ ANDROID_ASSET_FORMAT,
+ packageName,
+ certFingerprints.joinToString(separator = ", ") { "\"$it\"" })
+
+ fun createWebAssetString(scheme: String, host: String): Result<String> {
+ if (!Patterns.DOMAIN_NAME.matcher(host).matches()) {
+ return Result.Failure("Input host is not valid.")
+ }
+ if (scheme != "http" && scheme != "https") {
+ return Result.Failure("Input scheme is not valid.")
+ }
+ return Result.Success(WEB_ASSET_FORMAT.format(URL(scheme, host, "").toString()))
+ }
+
+ // Hosts with *. for wildcard subdomain support are verified against their root domain
+ fun createWebAssetString(host: String) =
+ WEB_ASSET_FORMAT.format(URL("https", host.removePrefix("*."), "").toString())
+}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 7f645ba..31d848d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -35,43 +35,11 @@
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
- <TextView
- android:id="@+id/logout"
- android:layout_height="@dimen/logout_button_layout_height"
- android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_centerHorizontal="true"
- android:layout_marginBottom="@dimen/logout_button_margin_bottom"
- android:gravity="center"
- android:paddingLeft="@dimen/logout_button_padding_horizontal"
- android:paddingRight="@dimen/logout_button_padding_horizontal"
- android:background="@drawable/logout_button_background"
- android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
- android:textAllCaps="true"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="13sp"
- android:text="@*android:string/global_action_logout" />
-
<include
layout="@layout/keyguard_clock_switch"
android:id="@+id/keyguard_clock_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
-
- <TextView
- android:id="@+id/owner_info"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/date_owner_info_margin"
- android:layout_gravity="center_horizontal"
- android:layout_centerHorizontal="true"
- android:textColor="?attr/wallpaperTextColorSecondary"
- android:textSize="@dimen/widget_label_font_size"
- android:letterSpacing="0.05"
- android:ellipsize="marquee"
- android:singleLine="true" />
<FrameLayout
android:id="@+id/status_view_media_container"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 4ca4c1b..b307544f 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -89,7 +89,7 @@
<string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"SIM को PIN कोड गलत छ। तपाईंले अब आफ्नो यन्त्र खोल्न आफ्नो सेवा प्रदायकलाई सम्पर्क गर्नै पर्ने हुन्छ।"</string>
<plurals name="kg_password_wrong_pin_code" formatted="false" msgid="7030584350995485026">
<item quantity="other">SIM को PIN कोड गलत छ, तपाईं अझै <xliff:g id="NUMBER_1">%d</xliff:g> पटक प्रयास गर्न सक्नुहुन्छ।</item>
- <item quantity="one">SIM को PIN कोड गलत छ,तपाईंले आफ्नो यन्त्र अनलक गर्न आफ्नो सेवा प्रदायकलाई सम्पर्क गर्नैपर्ने अवस्था आउनु अघि तपाईं अझै <xliff:g id="NUMBER_0">%d</xliff:g> पटक प्रयास गर्न सक्नुहुन्छ।</item>
+ <item quantity="one">SIM को PIN कोड गलत छ,तपाईंले आफ्नो डिभाइस अनलक गर्न आफ्नो सेवा प्रदायकलाई सम्पर्क गर्नैपर्ने अवस्था आउनु अघि तपाईं अझै <xliff:g id="NUMBER_0">%d</xliff:g> पटक प्रयास गर्न सक्नुहुन्छ।</item>
</plurals>
<string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"SIM काम नलाग्ने भएको छ। आफ्नो सेवा प्रदायकलाई सम्पर्क गर्नुहोस्।"</string>
<plurals name="kg_password_wrong_puk_code" formatted="false" msgid="3937306685604862886">
@@ -129,7 +129,7 @@
<string name="kg_face_not_recognized" msgid="7903950626744419160">"पहिचान भएन"</string>
<plurals name="kg_password_default_pin_message" formatted="false" msgid="7730152526369857818">
<item quantity="other">SIM को PIN प्रविष्टि गर्नुहोस्। तपाईंसँग <xliff:g id="NUMBER_1">%d</xliff:g> प्रयासहरू बाँकी छन्।</item>
- <item quantity="one">SIM को PIN प्रविष्टि गर्नुहोस्। तपाईंसँग <xliff:g id="NUMBER_0">%d</xliff:g> प्रयास बाँकी छ, त्यसपछि भने आफ्नो यन्त्र अनलक गर्नका लागि तपाईंले अनिवार्य रूपमा आफ्नो सेवा प्रदायकलाई सम्पर्क गर्नु पर्ने हुन्छ।</item>
+ <item quantity="one">SIM को PIN प्रविष्टि गर्नुहोस्। तपाईंसँग <xliff:g id="NUMBER_0">%d</xliff:g> प्रयास बाँकी छ, त्यसपछि भने आफ्नो डिभाइस अनलक गर्नका लागि तपाईंले अनिवार्य रूपमा आफ्नो सेवा प्रदायकलाई सम्पर्क गर्नु पर्ने हुन्छ।</item>
</plurals>
<plurals name="kg_password_default_puk_message" formatted="false" msgid="571308542462946935">
<item quantity="other">SIM लाई असक्षम पारिएको छ। जारी राख्न PUK कोड प्रविष्टि गर्नुहोस्। तपाईंसँग <xliff:g id="_NUMBER_1">%d</xliff:g> प्रयासहरू बाँकी छन्, त्यसपछि SIM सदाका लागि प्रयोग गर्न नमिल्ने हुन्छ। विवरणहरूका लागि सेवा प्रदायकलाई सम्पर्क गर्नुहोस्।</item>
diff --git a/packages/SystemUI/res-product/values-ky/strings.xml b/packages/SystemUI/res-product/values-ky/strings.xml
index 4eb90caa..00265a1 100644
--- a/packages/SystemUI/res-product/values-ky/strings.xml
+++ b/packages/SystemUI/res-product/values-ky/strings.xml
@@ -26,18 +26,18 @@
<string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"Планшетте SIM-карта жок."</string>
<string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"Телефондо SIM-карта жок."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"PIN-коддор дал келген жок"</string>
- <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"Планшеттин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул планшет баштапкы абалга келтирилип, андагы бардык маалымат өчүрүлөт."</string>
- <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул телефон баштапкы абалга келтирилип, андагы бардык маалымат өчүрүлөт."</string>
- <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Бул планшет баштапкы абалга келтирилип, андагы бардык маалымат өчүрүлөт."</string>
- <string name="kg_failed_attempts_now_wiping" product="default" msgid="6381835450014881813">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес аракет жасадыңыз. Бул телефон баштапкы абалга келтирилип, андагы бардык маалымат өчүрүлөт."</string>
+ <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"Планшеттин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул планшет баштапкы абалга келтирилип, андагы бардык нерселер өчүрүлөт."</string>
+ <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул телефон баштапкы абалга келтирилип, андагы бардык нерселер өчүрүлөт."</string>
+ <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Бул планшет баштапкы абалга келтирилип, андагы бардык нерселер өчүрүлөт."</string>
+ <string name="kg_failed_attempts_now_wiping" product="default" msgid="6381835450014881813">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес аракет жасадыңыз. Бул телефон баштапкы абалга келтирилип, андагы бардык нерселер өчүрүлөт."</string>
<string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="7325071812832605911">"Планшеттин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы өчүрүлөт."</string>
<string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="8110939900089863103">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы өчүрүлөт."</string>
<string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="8509811676952707883">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы өчүрүлөт."</string>
<string name="kg_failed_attempts_now_erasing_user" product="default" msgid="3051962486994265014">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы өчүрүлөт."</string>
- <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="1049523640263353830">"Планшетиңиздин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, жумуш профилиңиз өчүрүлүп, профилдеги бардык маалымат өчүрүлөт."</string>
- <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, жумуш профилиңиз өчүрүлүп, профилдеги бардык маалымат өчүрүлөт."</string>
- <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Жумуш профили өчүрүлүп, андагы бардык маалымат өчүрүлөт."</string>
- <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Жумуш профили өчүрүлүп, андагы бардык маалымат өчүрүлөт."</string>
+ <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="1049523640263353830">"Планшетиңиздин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, жумуш профилиңиз өчүрүлүп, профилдеги бардык нерселер өчүрүлөт."</string>
+ <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, жумуш профилиңиз өчүрүлүп, профилдеги бардык нерселер өчүрүлөт."</string>
+ <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Жумуш профили өчүрүлүп, андагы бардык нерселер өчүрүлөт."</string>
+ <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Жумуш профили өчүрүлүп, андагы бардык нерселер өчүрүлөт."</string>
<string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="1860049973474855672">"Графикалык ачкычты <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес тарттыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> ийгиликсиз аракеттен кийин планшетиңизди бөгөттөн электрондук почтаңыз аркылуу чыгаруу талап кылынат.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> секунддан кийин кайра аракеттениңиз."</string>
<string name="kg_failed_attempts_almost_at_login" product="default" msgid="44112553371516141">"Графикалык ачкычты <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес тарттыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> ийгиликсиз аракеттен кийин телефонуңузду бөгөттөн электрондук почтаңыз аркылуу чыгаруу талап кылынат.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> секунддан кийин кайра аракеттениңиз."</string>
<string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Дагы башка параметрлерди көрүү үчүн телефонуңуздун кулпусун ачыңыз"</string>
diff --git a/packages/SystemUI/res/drawable/people_space_activity_card.xml b/packages/SystemUI/res/drawable/people_space_activity_card.xml
index 338bff8..7e2db63 100644
--- a/packages/SystemUI/res/drawable/people_space_activity_card.xml
+++ b/packages/SystemUI/res/drawable/people_space_activity_card.xml
@@ -14,5 +14,5 @@
~ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/colorBackgroundFloating" />
+ <solid android:color="@color/people_tile_background" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/people_space_content_background.xml b/packages/SystemUI/res/drawable/people_space_content_background.xml
index 30519ae..9704871 100644
--- a/packages/SystemUI/res/drawable/people_space_content_background.xml
+++ b/packages/SystemUI/res/drawable/people_space_content_background.xml
@@ -15,6 +15,6 @@
~ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
- <solid android:color="?android:attr/colorBackground" />
+ <solid android:color="@color/people_tile_background" />
<corners android:radius="@dimen/people_space_image_radius" />
</shape>
diff --git a/packages/SystemUI/res/drawable/people_space_new_story_outline.xml b/packages/SystemUI/res/drawable/people_space_new_story_outline.xml
index a1737f9..4d6249d 100644
--- a/packages/SystemUI/res/drawable/people_space_new_story_outline.xml
+++ b/packages/SystemUI/res/drawable/people_space_new_story_outline.xml
@@ -15,6 +15,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
- <solid android:color="?android:attr/colorBackground" />
+ <solid android:color="@color/people_tile_background" />
<stroke android:width="2dp" android:color="?android:attr/colorAccent" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/people_space_rounded_border.xml b/packages/SystemUI/res/drawable/people_space_rounded_border.xml
index 9956bc2..50560df 100644
--- a/packages/SystemUI/res/drawable/people_space_rounded_border.xml
+++ b/packages/SystemUI/res/drawable/people_space_rounded_border.xml
@@ -15,5 +15,5 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
- <solid android:color="?android:attr/colorBackground" />
+ <solid android:color="@color/people_tile_background" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/people_space_tile_view_card.xml b/packages/SystemUI/res/drawable/people_space_tile_view_card.xml
index 8fd4388..6e1e5de 100644
--- a/packages/SystemUI/res/drawable/people_space_tile_view_card.xml
+++ b/packages/SystemUI/res/drawable/people_space_tile_view_card.xml
@@ -14,6 +14,6 @@
~ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/colorBackground" />
+ <solid android:color="@color/people_tile_background" />
<corners android:radius="@dimen/people_space_widget_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/people_tile_empty_background.xml b/packages/SystemUI/res/drawable/people_tile_empty_background.xml
index 2dac740..7dbea73 100644
--- a/packages/SystemUI/res/drawable/people_tile_empty_background.xml
+++ b/packages/SystemUI/res/drawable/people_tile_empty_background.xml
@@ -14,8 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <solid android:color="?androidprv:attr/colorSurface" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/people_tile_background" />
<corners android:radius="@dimen/people_space_widget_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml
index 5f83f45..5d80da8 100644
--- a/packages/SystemUI/res/layout/controls_base_item.xml
+++ b/packages/SystemUI/res/layout/controls_base_item.xml
@@ -25,8 +25,7 @@
android:focusable="true"
android:screenReaderFocusable="true"
android:stateListAnimator="@anim/control_state_list_animator"
- android:layout_marginLeft="@dimen/control_base_item_margin"
- android:layout_marginRight="@dimen/control_base_item_margin"
+ android:layout_marginStart="@dimen/control_spacing"
android:background="@drawable/control_background">
<ImageView
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 80b7d1f..e40138e 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -80,8 +80,8 @@
<ImageView
android:id="@+id/wallet_button"
- android:layout_height="@dimen/keyguard_affordance_height"
- android:layout_width="@dimen/keyguard_affordance_width"
+ android:layout_height="@dimen/keyguard_affordance_wallet_height"
+ android:layout_width="@dimen/keyguard_affordance_wallet_width"
android:layout_gravity="bottom|end"
android:scaleType="center"
android:tint="?android:attr/textColorPrimary"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index d0e3d3c..51cab0a 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -29,12 +29,12 @@
android:paddingTop="@dimen/status_bar_padding_top"
android:minHeight="48dp">
- <LinearLayout
+ <FrameLayout
+ android:id="@+id/date_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:minHeight="48dp"
android:layout_weight="1"
- android:orientation="horizontal"
android:gravity="center_vertical|start" >
<com.android.systemui.statusbar.policy.DateView
@@ -46,7 +46,7 @@
android:singleLine="true"
android:textAppearance="@style/TextAppearance.QS.Status"
systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
- </LinearLayout>
+ </FrameLayout>
<android.widget.Space
android:id="@+id/space"
@@ -59,21 +59,22 @@
<FrameLayout
android:id="@+id/header_text_container"
android:layout_height="match_parent"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
+ android:layout_weight="1"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:gravity="center"
/>
- <LinearLayout
+ <FrameLayout
+ android:id="@+id/privacy_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:minHeight="48dp"
android:layout_weight="1"
- android:orientation="horizontal"
android:gravity="center_vertical|end" >
<include layout="@layout/ongoing_privacy_chip" />
- </LinearLayout>
+ </FrameLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index bd9e64b..d3401f8 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -1079,7 +1079,7 @@
<string name="accessibility_control_change_unfavorite" msgid="6997408061750740327">"إزالة من المفضّلة"</string>
<string name="accessibility_control_move" msgid="8980344493796647792">"نقل إلى الموضع <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="controls_favorite_default_title" msgid="967742178688938137">"عناصر التحكّم"</string>
- <string name="controls_favorite_subtitle" msgid="6481675111056961083">"اختيار عناصر التحكّم التي يتم الوصول إليها من \"الإعدادات السريعة\"."</string>
+ <string name="controls_favorite_subtitle" msgid="6481675111056961083">"اختَر عناصر التحكّم التي يتم الوصول إليها من \"الإعدادات السريعة\"."</string>
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"اضغط مع الاستمرار واسحب لإعادة ترتيب عناصر التحكّم."</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"تمت إزالة كل عناصر التحكّم."</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"لم يتم حفظ التغييرات."</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 0beb96c..853e00f 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -39,7 +39,7 @@
<string name="battery_saver_start_action" msgid="4553256017945469937">"Batareya Qənaətini aktiv edin"</string>
<string name="status_bar_settings_settings_button" msgid="534331565185171556">"Ayarlar"</string>
<string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"Wi-Fi"</string>
- <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ekran avtodönüşü"</string>
+ <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ekranın avtomatik dönməsi"</string>
<string name="status_bar_settings_mute_label" msgid="914392730086057522">"SUSDUR"</string>
<string name="status_bar_settings_auto_brightness_label" msgid="2151934479226017725">"AVTO"</string>
<string name="status_bar_settings_notifications" msgid="5285316949980621438">"Bildirişlər"</string>
@@ -343,7 +343,7 @@
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiv edilir..."</string>
<string name="quick_settings_brightness_label" msgid="680259653088849563">"Parlaqlıq"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avtodönüş"</string>
- <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekran avtodönüşü"</string>
+ <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranın avtomatik dönməsi"</string>
<string name="accessibility_quick_settings_rotation_value" msgid="2916484894750819251">"<xliff:g id="ID_1">%s</xliff:g> rejimi"</string>
<string name="quick_settings_rotation_locked_label" msgid="4420863550666310319">"Fırlanma kilidlidir"</string>
<string name="quick_settings_rotation_locked_portrait_label" msgid="1194988975270484482">"Portret"</string>
@@ -509,7 +509,7 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu funksiyanı təmin edən xidmətin yazma və ya yayım zamanı ekranda görünən və ya cihazdan oxudulan bütün bilgilərə girişi olacaq. Buraya parollar, ödəniş detalları, fotolar, mesajlar və oxudulan audio kimi məlumatlar daxildir."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Yazma və ya yayımlama başladılsın?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ilə yazma və ya yayımlama başladılsın?"</string>
- <string name="media_projection_remember_text" msgid="6896767327140422951">"Daha göstərmə"</string>
+ <string name="media_projection_remember_text" msgid="6896767327140422951">"Göstərilməsin"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"İdarə edin"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Tarixçə"</string>
@@ -929,7 +929,7 @@
<string name="accessibility_quick_settings_not_available" msgid="6860875849497473854">"<xliff:g id="REASON">%s</xliff:g> səbəbi ilə əlçatan deyil"</string>
<string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"<xliff:g id="ID_1">%s</xliff:g> ayarlarını açın."</string>
<string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"Ayarların sıralanmasını redaktə edin."</string>
- <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Yandırıb-söndürmə menyusu"</string>
+ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Qidalanma düyməsi menyusu"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> səhifədən <xliff:g id="ID_1">%1$d</xliff:g> səhifə"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Ekran kilidi"</string>
<string name="thermal_shutdown_title" msgid="2702966892682930264">"İstiliyə görə telefon söndü"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index a86532d..75bc1132 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1129,7 +1129,7 @@
<string name="new_story_status" msgid="9012195158584846525">"Нова история"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> сподели нова история"</string>
<string name="video_status" msgid="4548544654316843225">"Гледате"</string>
- <string name="audio_status" msgid="4237055636967709208">"Слуша се"</string>
+ <string name="audio_status" msgid="4237055636967709208">"Слушате"</string>
<string name="game_status" msgid="1340694320630973259">"Играете"</string>
<string name="empty_user_name" msgid="3389155775773578300">"Приятели"</string>
<string name="empty_status" msgid="5938893404951307749">"Да поговорим!"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 2a9d7f5..11dfe71 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -520,7 +520,7 @@
<string name="manage_notifications_text" msgid="6885645344647733116">"Spravovat"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historie"</string>
<string name="notification_section_header_incoming" msgid="850925217908095197">"Nové"</string>
- <string name="notification_section_header_gentle" msgid="6804099527336337197">"Tiché"</string>
+ <string name="notification_section_header_gentle" msgid="6804099527336337197">"Ticho"</string>
<string name="notification_section_header_alerting" msgid="5581175033680477651">"Oznámení"</string>
<string name="notification_section_header_conversations" msgid="821834744538345661">"Konverzace"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Vymazat všechna tichá oznámení"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index a2db1f1..909ddda 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -798,7 +798,7 @@
<item quantity="other">%d minutos</item>
<item quantity="one">%d minuto</item>
</plurals>
- <string name="battery_panel_title" msgid="5931157246673665963">"Uso de la batería"</string>
+ <string name="battery_panel_title" msgid="5931157246673665963">"Uso de batería"</string>
<string name="battery_detail_charging_summary" msgid="8821202155297559706">"Ahorro de batería no disponible mientras se carga el dispositivo"</string>
<string name="battery_detail_switch_title" msgid="6940976502957380405">"Ahorro de batería"</string>
<string name="battery_detail_switch_summary" msgid="3668748557848025990">"Reduce el rendimiento y los datos en segundo plano"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 2517960..d48beba 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -728,7 +728,7 @@
<string name="inline_turn_off_notifications" msgid="8543989584403106071">"Desaktibatu jakinarazpenak"</string>
<string name="inline_keep_showing_app" msgid="4393429060390649757">"Aplikazio honen jakinarazpenak erakusten jarraitzea nahi duzu?"</string>
<string name="notification_silence_title" msgid="8608090968400832335">"Isila"</string>
- <string name="notification_alert_title" msgid="3656229781017543655">"Balio lehenetsia"</string>
+ <string name="notification_alert_title" msgid="3656229781017543655">"Lehenetsia"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"Automatikoa"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Ez du tonurik jotzen edo dar-dar egiten"</string>
<string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ez du tonurik jotzen edo dar-dar egiten, eta elkarrizketaren atalaren behealdean agertzen da"</string>
@@ -1130,7 +1130,7 @@
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> erabiltzaileak istorio berri bat partekatu du"</string>
<string name="video_status" msgid="4548544654316843225">"Ikusten"</string>
<string name="audio_status" msgid="4237055636967709208">"Entzuten"</string>
- <string name="game_status" msgid="1340694320630973259">"Erreproduzitzen"</string>
+ <string name="game_status" msgid="1340694320630973259">"Jolasten"</string>
<string name="empty_user_name" msgid="3389155775773578300">"Lagunak"</string>
<string name="empty_status" msgid="5938893404951307749">"Txatea dezagun gaur gauean!"</string>
<string name="status_before_loading" msgid="1500477307859631381">"Laster agertuko da edukia"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 3f3831f..805695b 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1055,7 +1055,7 @@
<string name="accessibility_control_change_unfavorite" msgid="6997408061750740327">"poista suosikeista"</string>
<string name="accessibility_control_move" msgid="8980344493796647792">"Siirrä kohtaan <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="controls_favorite_default_title" msgid="967742178688938137">"Säätimet"</string>
- <string name="controls_favorite_subtitle" msgid="6481675111056961083">"Valitse ohjaimet, joita käytetään pika-asetuksista"</string>
+ <string name="controls_favorite_subtitle" msgid="6481675111056961083">"Valitse säätimet, joita käytetään pika-asetuksista"</string>
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"Järjestele säätimiä koskettamalla pitkään ja vetämällä"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"Kaikki säätimet poistettu"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"Muutoksia ei tallennettu"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index f279268..5710c50 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -1122,14 +1122,14 @@
<string name="birthday_status_content_description" msgid="682836371128282925">"C\'est l\'anniversaire de <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="upcoming_birthday_status" msgid="2005452239256870351">"Anniversaire à venir"</string>
<string name="upcoming_birthday_status_content_description" msgid="2165036816803797148">"C\'est bientôt l\'anniversaire de <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="anniversary_status" msgid="1790034157507590838">"Fête"</string>
+ <string name="anniversary_status" msgid="1790034157507590838">"Anniversaire"</string>
<string name="anniversary_status_content_description" msgid="8212171790843327442">"C\'est l\'anniversaire de mariage de <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="location_status" msgid="1294990572202541812">"Partage sa position"</string>
<string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g> partage sa position"</string>
<string name="new_story_status" msgid="9012195158584846525">"Nouvelle story"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> a partagé une story"</string>
<string name="video_status" msgid="4548544654316843225">"Regarde une vidéo"</string>
- <string name="audio_status" msgid="4237055636967709208">"Écoute"</string>
+ <string name="audio_status" msgid="4237055636967709208">"Écoute du contenu"</string>
<string name="game_status" msgid="1340694320630973259">"Joue"</string>
<string name="empty_user_name" msgid="3389155775773578300">"Amis"</string>
<string name="empty_status" msgid="5938893404951307749">"Chattez ce soir !"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 07a691c..6230687 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -487,7 +487,7 @@
<string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Comezar de novo"</string>
<string name="guest_wipe_session_dontwipe" msgid="3211052048269304205">"Si, continuar"</string>
<string name="guest_notification_title" msgid="4434456703930764167">"Usuario convidado"</string>
- <string name="guest_notification_text" msgid="4202692942089571351">"Para eliminar aplicacións e datos, quita o usuario invitado"</string>
+ <string name="guest_notification_text" msgid="4202692942089571351">"Para eliminar aplicacións e datos, quita o usuario convidado"</string>
<string name="guest_notification_remove_action" msgid="4153019027696868099">"QUITAR CONVIDADO"</string>
<string name="user_logout_notification_title" msgid="3644848998053832589">"Pechar sesión do usuario"</string>
<string name="user_logout_notification_text" msgid="7441286737342997991">"Pechar sesión do usuario actual"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index f7a1c8e..adc01b8 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -387,7 +387,7 @@
<string name="quick_settings_more_user_settings" msgid="1064187451100861954">"વપરાશકર્તા સેટિંગ"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"થઈ ગયું"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"બંધ કરો"</string>
- <string name="quick_settings_connected" msgid="3873605509184830379">"કનેક્ટ થયેલ"</string>
+ <string name="quick_settings_connected" msgid="3873605509184830379">"કનેક્ટ થયેલું"</string>
<string name="quick_settings_connected_battery_level" msgid="1322075669498906959">"કનેક્ટ કરેલ, <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> બૅટરી"</string>
<string name="quick_settings_connecting" msgid="2381969772953268809">"કનેક્ટ કરી રહ્યું છે..."</string>
<string name="quick_settings_tethering_label" msgid="5257299852322475780">"ટિથરિંગ"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 73a32d3..1b48c09 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1126,7 +1126,7 @@
<string name="anniversary_status_content_description" msgid="8212171790843327442">"Այսօր <xliff:g id="NAME">%1$s</xliff:g>-ի տարեդարձն է"</string>
<string name="location_status" msgid="1294990572202541812">"Տեղադրության ցուցադրում"</string>
<string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g> օգտատերը հայտնում է իր տեղադրության մասին տվյալները"</string>
- <string name="new_story_status" msgid="9012195158584846525">"Նոր հոդված"</string>
+ <string name="new_story_status" msgid="9012195158584846525">"Նոր պատմություն"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> օգտատերը նոր պատմություն է հրապարակել"</string>
<string name="video_status" msgid="4548544654316843225">"Տեսանյութ եմ դիտում"</string>
<string name="audio_status" msgid="4237055636967709208">"Բան եմ լսում"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index ce2a991..a7cdfc1 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -65,7 +65,7 @@
<string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"למשתמש המחובר לחשבון במכשיר הזה אין אפשרות להפעיל ניפוי באגים ב-USB. כדי להשתמש בתכונה הזו יש לעבור אל המשתמש הראשי."</string>
<string name="wifi_debugging_title" msgid="7300007687492186076">"לאשר ניפוי באגים אלחוטי ברשת הזו?"</string>
<string name="wifi_debugging_message" msgid="5461204211731802995">"שם הרשת (SSID)\n<xliff:g id="SSID_0">%1$s</xliff:g>\n\nכתובת Wi‑Fi (BSSID)\n<xliff:g id="BSSID_1">%2$s</xliff:g>"</string>
- <string name="wifi_debugging_always" msgid="2968383799517975155">"אפשר תמיד ברשת הזו"</string>
+ <string name="wifi_debugging_always" msgid="2968383799517975155">"לאשר תמיד ברשת הזו"</string>
<string name="wifi_debugging_allow" msgid="4573224609684957886">"אישור"</string>
<string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"אין הרשאה לניפוי באגים אלחוטי"</string>
<string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"למשתמש המחובר לחשבון במכשיר הזה אין אפשרות להפעיל ניפוי באגים אלחוטי. כדי להשתמש בתכונה הזו, יש לעבור אל המשתמש הראשי."</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index d12c4dc..bbd4ff2 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -746,7 +746,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Әңгіме туралы хабарландырулардың жоғарғы жағында тұрады және құлыптаулы экранда профиль суреті ретінде көрсетіледі, қалқымалы анықтама ретінде шығады, \"Мазаламау\" режимін тоқтатады."</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Параметрлер"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Маңызды"</string>
- <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> әңгімелесу функцияларын қолдамайды."</string>
+ <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> әңгіме функцияларын қолдамайды."</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Бұл хабарландыруларды өзгерту мүмкін емес."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Мұндай хабарландырулар бұл жерде конфигурацияланбайды."</string>
<string name="notification_delegate_header" msgid="1264510071031479920">"Прокси-сервер арқылы жіберілген хабарландыру"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index d5557b9..5ab4fe7 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -1128,7 +1128,7 @@
<string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g> ಅವರು ಸ್ಥಳವನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿದ್ದಾರೆ"</string>
<string name="new_story_status" msgid="9012195158584846525">"ಹೊಸ ಸುದ್ದಿ"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> ಅವರು ಹೊಸ ಸ್ಟೋರಿಯನ್ನು ಹಂಚಿಕೊಂಡಿದ್ದಾರೆ"</string>
- <string name="video_status" msgid="4548544654316843225">"ವೀಕ್ಷಿಸುತ್ತಿರುವವರು"</string>
+ <string name="video_status" msgid="4548544654316843225">"ವೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="audio_status" msgid="4237055636967709208">"ಆಲಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="game_status" msgid="1340694320630973259">"ಪ್ಲೇ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
<string name="empty_user_name" msgid="3389155775773578300">"ಸ್ನೇಹಿತರು"</string>
diff --git a/packages/SystemUI/res/values-land/integers.xml b/packages/SystemUI/res/values-land/integers.xml
new file mode 100644
index 0000000..5937a07
--- /dev/null
+++ b/packages/SystemUI/res/values-land/integers.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <integer name="qs_security_footer_maxLines">1</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 8a4499f..8710610 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -746,7 +746,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"സംഭാഷണ അറിയിപ്പുകളുടെ മുകളിലും സ്ക്രീൻ ലോക്കായിരിക്കുമ്പോൾ ഒരു പ്രൊഫൈൽ ചിത്രമായും കാണിക്കുന്നു, ഒരു ബബിൾ രൂപത്തിൽ ദൃശ്യമാകുന്നു, ശല്യപ്പെടുത്തരുത് മോഡ് തടസ്സപ്പെടുത്തുന്നു"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"ക്രമീകരണം"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"മുൻഗണന"</string>
- <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> സംഭാഷണ ഫീച്ചറുകളെ പിന്തുണയ്ക്കുന്നില്ല"</string>
+ <string name="no_shortcut" msgid="8257177117568230126">"സംഭാഷണ ഫീച്ചറുകളെ <xliff:g id="APP_NAME">%1$s</xliff:g> പിന്തുണയ്ക്കുന്നില്ല"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ഈ അറിയിപ്പുകൾ പരിഷ്ക്കരിക്കാനാവില്ല."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"അറിയിപ്പുകളുടെ ഈ ഗ്രൂപ്പ് ഇവിടെ കോണ്ഫിഗര് ചെയ്യാൻ കഴിയില്ല"</string>
<string name="notification_delegate_header" msgid="1264510071031479920">"പ്രോക്സി അറിയിപ്പ്"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 2a1506f..09de767 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -1096,7 +1096,7 @@
<string name="controls_in_progress" msgid="4421080500238215939">"प्रगतीपथावर आहे"</string>
<string name="controls_added_tooltip" msgid="5866098408470111984">"नवीन नियंत्रणे पाहण्यासाठी क्विक सेटिंग्ज उघडा"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"नियंत्रणे जोडा"</string>
- <string name="controls_menu_edit" msgid="890623986951347062">"नियंत्रणे व्यवस्थापित करा"</string>
+ <string name="controls_menu_edit" msgid="890623986951347062">"नियंत्रणे संपादित करा"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट जोडा"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"गट"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिव्हाइस निवडले"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 24bafa8..165d8c0 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1042,7 +1042,7 @@
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Alihkan ke tepi dan tunjukkan"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"togol"</string>
<string name="quick_controls_title" msgid="7095074621086860062">"Kawalan rumah"</string>
- <string name="controls_providers_title" msgid="6879775889857085056">"Pilih apl untuk menambah kawalan"</string>
+ <string name="controls_providers_title" msgid="6879775889857085056">"Pilih apl untuk menambahkan kawalan"</string>
<plurals name="controls_number_of_favorites" formatted="false" msgid="1057347832073807380">
<item quantity="other"><xliff:g id="NUMBER_1">%s</xliff:g> kawalan ditambah.</item>
<item quantity="one"><xliff:g id="NUMBER_0">%s</xliff:g> kawalan ditambah.</item>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 11e497d..ba63fc3 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -1096,7 +1096,7 @@
<string name="controls_in_progress" msgid="4421080500238215939">"ဆောင်ရွက်နေသည်"</string>
<string name="controls_added_tooltip" msgid="5866098408470111984">"ထိန်းချုပ်မှုအသစ်များ ကြည့်ရန် အမြန် ဆက်တင်များကို ဖွင့်ပါ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"ထိန်းချုပ်မှုများ ထည့်ရန်"</string>
- <string name="controls_menu_edit" msgid="890623986951347062">"ထိန်းချုပ်မှုများ တည်းဖြတ်ရန်"</string>
+ <string name="controls_menu_edit" msgid="890623986951347062">"ထိန်းချုပ်မှုများ ပြင်ရန်"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"မီဒီယာအထွက်များ ထည့်ရန်"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"အုပ်စု"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"စက်ပစ္စည်း ၁ ခုကို ရွေးချယ်ထားသည်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index c23e479..28bd403 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -19,7 +19,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_label" msgid="4811759950673118541">"Sys.gr.snitt"</string>
+ <string name="app_label" msgid="4811759950673118541">"System-UI"</string>
<string name="status_bar_clear_all_button" msgid="2491321682873657397">"Fjern"</string>
<string name="status_bar_no_notifications_title" msgid="7812479124981107507">"Ingen varslinger"</string>
<string name="status_bar_ongoing_events_title" msgid="3986169317496615446">"Aktiviteter"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index d25b38a..ea74b72 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -83,7 +83,7 @@
<string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रिनसट सेभ गरियो"</string>
<string name="screenshot_saved_text" msgid="7778833104901642442">"आफ्नो स्क्रिनसट हेर्न ट्याप गर्नुहोस्"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string>
- <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"यन्त्र अनलक गरेपछि मात्र स्क्रिनसट सुरक्षित गर्न सकिन्छ"</string>
+ <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"डिभाइस अनलक गरेपछि मात्र स्क्रिनसट सुरक्षित गर्न सकिन्छ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रिनसट फेरि लिएर हेर्नुहोस्"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string>
<string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"उक्त एप वा तपाईंको संगठनले स्क्रिनसटहरू लिन दिँदैन"</string>
@@ -730,8 +730,8 @@
<string name="notification_silence_title" msgid="8608090968400832335">"साइलेन्ट"</string>
<string name="notification_alert_title" msgid="3656229781017543655">"डिफल्ट"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"स्वचालित"</string>
- <string name="notification_channel_summary_low" msgid="4860617986908931158">"न घन्टी बज्छ न त कम्पन नै हुन्छ"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"न घन्टी बज्छ न त कम्पन नै हुन्छ र वार्तालाप खण्डको तलतिर देखा पर्छ"</string>
+ <string name="notification_channel_summary_low" msgid="4860617986908931158">"बज्दैन पनि, भाइब्रेट पनि हुँदैन"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"बज्दैन पनि, भाइब्रेट पनि हुँदैन र वार्तालाप खण्डको तलतिर देखा पर्छ"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"फोनको सेटिङका आधारमा घन्टी बज्न वा भाइब्रेट हुन सक्छ"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"फोनको सेटिङका आधारमा घन्टी बज्न वा भाइब्रेट हुन सक्छ। <xliff:g id="APP_NAME">%1$s</xliff:g> का वार्तालापहरू डिफल्ट रूपमा बबलमा देखाइन्छन्।"</string>
<string name="notification_channel_summary_bubble" msgid="7235935211580860537">"फ्लोटिङ सर्टकटमार्फत यो सामग्रीतर्फ तपाईंको ध्यान आकर्षित गर्दछ।"</string>
@@ -1124,7 +1124,7 @@
<string name="upcoming_birthday_status_content_description" msgid="2165036816803797148">"<xliff:g id="NAME">%1$s</xliff:g> को जन्मदिन चाँडै आउँदै छ"</string>
<string name="anniversary_status" msgid="1790034157507590838">"वार्षिकोत्सव"</string>
<string name="anniversary_status_content_description" msgid="8212171790843327442">"आज <xliff:g id="NAME">%1$s</xliff:g> को वार्षिकोत्सव हो"</string>
- <string name="location_status" msgid="1294990572202541812">"स्थानसम्बन्धी जानकारी सेयर गरिँदै छ"</string>
+ <string name="location_status" msgid="1294990572202541812">"लोकेसन सेयर गरिँदै छ"</string>
<string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g> आफ्नो स्थानसम्बन्धी जानकारी सेयर गर्दै हुनुहुन्छ"</string>
<string name="new_story_status" msgid="9012195158584846525">"नयाँ स्टोरी"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> ले एउटा नयाँ स्टोरी सेयर गर्नुभयो"</string>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index c473229..8e6293a 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -97,4 +97,6 @@
<!-- Accessibility floating menu -->
<color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
+
+ <color name="people_tile_background">@android:color/system_accent2_800</color>
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 8217c84..9eae3d4 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -727,13 +727,13 @@
<string name="inline_block_button" msgid="479892866568378793">"Zablokuj"</string>
<string name="inline_keep_button" msgid="299631874103662170">"Pokazuj nadal"</string>
<string name="inline_minimize_button" msgid="1474436209299333445">"Minimalizuj"</string>
- <string name="inline_silent_button_silent" msgid="525243786649275816">"Bez dźwięku"</string>
+ <string name="inline_silent_button_silent" msgid="525243786649275816">"Ciche"</string>
<string name="inline_silent_button_stay_silent" msgid="2129254868305468743">"Zachowaj wyciszenie"</string>
<string name="inline_silent_button_alert" msgid="5705343216858250354">"Alerty"</string>
<string name="inline_silent_button_keep_alerting" msgid="6577845442184724992">"Powiadamiaj dalej"</string>
<string name="inline_turn_off_notifications" msgid="8543989584403106071">"Wyłącz powiadomienia"</string>
<string name="inline_keep_showing_app" msgid="4393429060390649757">"Nadal pokazywać powiadomienia z tej aplikacji?"</string>
- <string name="notification_silence_title" msgid="8608090968400832335">"Bez dźwięku"</string>
+ <string name="notification_silence_title" msgid="8608090968400832335">"Ciche"</string>
<string name="notification_alert_title" msgid="3656229781017543655">"Domyślne"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"Automatycznie"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"Brak dźwięku i wibracji"</string>
@@ -751,7 +751,7 @@
<string name="notification_channel_summary_priority_dnd" msgid="6665395023264154361">"Wyświetla się u góry powiadomień w rozmowach oraz jako zdjęcie profilowe na ekran blokady, przerywa działanie trybu Nie przeszkadzać"</string>
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Wyświetla się u góry powiadomień w rozmowach oraz jako zdjęcie profilowe na ekran blokady, jako dymek, przerywa działanie trybu Nie przeszkadzać"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Ustawienia"</string>
- <string name="notification_priority_title" msgid="2079708866333537093">"Priorytet"</string>
+ <string name="notification_priority_title" msgid="2079708866333537093">"Priorytetowe"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> nie obsługuje funkcji rozmów"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Tych powiadomień nie można zmodyfikować."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Tej grupy powiadomień nie można tu skonfigurować"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 267c308..0abe4a0 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1135,8 +1135,8 @@
<string name="new_story_status" msgid="9012195158584846525">"Subiect nou"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> a trimis o poveste nouă"</string>
<string name="video_status" msgid="4548544654316843225">"Urmăresc"</string>
- <string name="audio_status" msgid="4237055636967709208">"Se ascultă"</string>
- <string name="game_status" msgid="1340694320630973259">"Se redă"</string>
+ <string name="audio_status" msgid="4237055636967709208">"Ascult"</string>
+ <string name="game_status" msgid="1340694320630973259">"Mă joc"</string>
<string name="empty_user_name" msgid="3389155775773578300">"Prieteni"</string>
<string name="empty_status" msgid="5938893404951307749">"Conversăm prin chat diseară?"</string>
<string name="status_before_loading" msgid="1500477307859631381">"Conținutul va apărea în curând"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index f006722..ad33b0d 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1136,13 +1136,13 @@
<string name="upcoming_birthday_status_content_description" msgid="2165036816803797148">"<xliff:g id="NAME">%1$s</xliff:g> má čoskoro narodeniny"</string>
<string name="anniversary_status" msgid="1790034157507590838">"Výročie"</string>
<string name="anniversary_status_content_description" msgid="8212171790843327442">"<xliff:g id="NAME">%1$s</xliff:g> má výročie"</string>
- <string name="location_status" msgid="1294990572202541812">"Zdieľa sa poloha"</string>
+ <string name="location_status" msgid="1294990572202541812">"Zdieľam polohu"</string>
<string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g> zdieľa polohu"</string>
<string name="new_story_status" msgid="9012195158584846525">"Nová správa"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> zdieľal(a) nový príbeh"</string>
- <string name="video_status" msgid="4548544654316843225">"Pozerá sa video"</string>
+ <string name="video_status" msgid="4548544654316843225">"Pozerám video"</string>
<string name="audio_status" msgid="4237055636967709208">"Počúvam"</string>
- <string name="game_status" msgid="1340694320630973259">"Hrá sa hra"</string>
+ <string name="game_status" msgid="1340694320630973259">"Hrám hru"</string>
<string name="empty_user_name" msgid="3389155775773578300">"Priatelia"</string>
<string name="empty_status" msgid="5938893404951307749">"Poďme sa rozprávať."</string>
<string name="status_before_loading" msgid="1500477307859631381">"Obsah sa čoskoro zobrazí"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 94a16ad..d7a5efa 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -777,7 +777,7 @@
<string name="inline_undo" msgid="9026953267645116526">"Zhbëj"</string>
<string name="demote" msgid="6225813324237153980">"Shëno se ky njoftim nuk është një bisedë"</string>
<string name="notification_conversation_favorite" msgid="1905240206975921907">"Bashkëbisedim i rëndësishëm"</string>
- <string name="notification_conversation_unfavorite" msgid="181383708304763807">"Nuk është bashkëbisedim i rëndësishëm"</string>
+ <string name="notification_conversation_unfavorite" msgid="181383708304763807">"Nuk është bisedë e rëndësishme"</string>
<string name="notification_conversation_mute" msgid="268951550222925548">"Në heshtje"</string>
<string name="notification_conversation_unmute" msgid="2692255619510896710">"Po sinjalizon"</string>
<string name="notification_conversation_bubble" msgid="2242180995373949022">"Shfaq flluskën"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index fa6952c..ac8c44f 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -1124,13 +1124,13 @@
<string name="upcoming_birthday_status_content_description" msgid="2165036816803797148">"Siku ya kuzaliwa ya <xliff:g id="NAME">%1$s</xliff:g> inakaribia"</string>
<string name="anniversary_status" msgid="1790034157507590838">"Maadhimisho"</string>
<string name="anniversary_status_content_description" msgid="8212171790843327442">"Ni maadhimisho ya <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="location_status" msgid="1294990572202541812">"Inashiriki mahali"</string>
+ <string name="location_status" msgid="1294990572202541812">"Unashiriki mahali"</string>
<string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g> anashiriki maelezo ya mahali"</string>
<string name="new_story_status" msgid="9012195158584846525">"Habari mpya"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> ameshiriki hadithi mpya"</string>
<string name="video_status" msgid="4548544654316843225">"Unatazama"</string>
- <string name="audio_status" msgid="4237055636967709208">"Inasikiliza"</string>
- <string name="game_status" msgid="1340694320630973259">"Inacheza"</string>
+ <string name="audio_status" msgid="4237055636967709208">"Unasikiliza"</string>
+ <string name="game_status" msgid="1340694320630973259">"Unacheza"</string>
<string name="empty_user_name" msgid="3389155775773578300">"Marafiki"</string>
<string name="empty_status" msgid="5938893404951307749">"Tupige gumzo usiku!"</string>
<string name="status_before_loading" msgid="1500477307859631381">"Maudhui yataonekana hivi karibuni"</string>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 3be73ba..9f8e636 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -68,8 +68,6 @@
phone hints. -->
<dimen name="edge_tap_area_width">80dp</dimen>
- <dimen name="keyguard_indication_margin_bottom">90dp</dimen>
-
<!-- Margin on the right side of the system icon group on Keyguard. -->
<dimen name="system_icons_keyguard_padding_end">2dp</dimen>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 900277a..ea22e5b 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1124,12 +1124,12 @@
<string name="upcoming_birthday_status_content_description" msgid="2165036816803797148">"<xliff:g id="NAME">%1$s</xliff:g> పుట్టినరోజు త్వరలో రాబోతోంది"</string>
<string name="anniversary_status" msgid="1790034157507590838">"వార్షికోత్సవం"</string>
<string name="anniversary_status_content_description" msgid="8212171790843327442">"ఇది <xliff:g id="NAME">%1$s</xliff:g> వార్షికోత్సవం"</string>
- <string name="location_status" msgid="1294990572202541812">"లొకేషన్ షేరింగ్"</string>
+ <string name="location_status" msgid="1294990572202541812">"లొకేషన్ షేర్ ఔతోంది"</string>
<string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g> లొకేషన్ను షేర్ చేస్తోంది"</string>
<string name="new_story_status" msgid="9012195158584846525">"కొత్త కథనం"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> కొత్త కథనాన్ని షేర్ చేసింది"</string>
<string name="video_status" msgid="4548544654316843225">"చూస్తున్నారు"</string>
- <string name="audio_status" msgid="4237055636967709208">"వినడం"</string>
+ <string name="audio_status" msgid="4237055636967709208">"వింటున్నారు"</string>
<string name="game_status" msgid="1340694320630973259">"ఆడుతున్నారు"</string>
<string name="empty_user_name" msgid="3389155775773578300">"ఫ్రెండ్స్"</string>
<string name="empty_status" msgid="5938893404951307749">"రాత్రి చాట్ చేద్దాం!"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 4fd84a1..ae3d16b 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -745,7 +745,7 @@
<string name="notification_channel_summary_priority_dnd" msgid="6665395023264154361">"Görüşme bildirimlerinin üstünde ve kilit ekranında profil resmi olarak gösterilir, Rahatsız Etmeyin\'i kesintiye uğratır"</string>
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Görüşme bildirimlerinin üstünde ve kilit ekranında profil resmi olarak gösterilir, baloncuk olarak görünür, Rahatsız Etmeyin\'i kesintiye uğratır"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Ayarlar"</string>
- <string name="notification_priority_title" msgid="2079708866333537093">"Öncelik"</string>
+ <string name="notification_priority_title" msgid="2079708866333537093">"Öncelikli"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>, sohbet özelliklerini desteklemiyor"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Bu bildirimler değiştirilemez."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Bu bildirim grubu burada yapılandırılamaz"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 4ca003d8..cf66b91 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -746,7 +746,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"یہ گفتگو کی اطلاعات کے اوپری حصّے پر اور مقفل اسکرین پر پروفائل کی تصویر کے بطور دکھائی دیتا ہے، بلبلے کے بطور ظاہر ہوتا ہے، \'ڈسٹرب نہ کریں\' میں مداخلت کرتا ہے"</string>
<string name="notification_conversation_channel_settings" msgid="2409977688430606835">"ترتیبات"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ترجیح"</string>
- <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> گفتگو کی خصوصیات کو سپورٹ نہیں کرتا ہے"</string>
+ <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ گفتگو کی خصوصیات کو سپورٹ نہیں کرتی ہے"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ان اطلاعات کی ترمیم نہیں کی جا سکتی۔"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"اطلاعات کے اس گروپ کو یہاں کنفیگر نہیں کیا جا سکتا"</string>
<string name="notification_delegate_header" msgid="1264510071031479920">"پراکسی اطلاع"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 7b4c5d9..2cd2859 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -1128,9 +1128,9 @@
<string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g> joylashuvni ulashmoqda"</string>
<string name="new_story_status" msgid="9012195158584846525">"Yangi hikoya"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g> yangi hikoyani ulashdi"</string>
- <string name="video_status" msgid="4548544654316843225">"Tomosha"</string>
- <string name="audio_status" msgid="4237055636967709208">"Gapiring"</string>
- <string name="game_status" msgid="1340694320630973259">"Ijro etilmoqda"</string>
+ <string name="video_status" msgid="4548544654316843225">"Tomosha qilmoqda"</string>
+ <string name="audio_status" msgid="4237055636967709208">"Tinglamoqda"</string>
+ <string name="game_status" msgid="1340694320630973259">"Oʻynamoqda"</string>
<string name="empty_user_name" msgid="3389155775773578300">"Doʻstlar"</string>
<string name="empty_status" msgid="5938893404951307749">"Bugun yozishaylik!"</string>
<string name="status_before_loading" msgid="1500477307859631381">"Kontent tezda chiqadi"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 681430b..19eeb8a 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1129,7 +1129,7 @@
<string name="new_story_status" msgid="9012195158584846525">"新故事"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g>分享了一个新故事"</string>
<string name="video_status" msgid="4548544654316843225">"正在观看"</string>
- <string name="audio_status" msgid="4237055636967709208">"正在收听"</string>
+ <string name="audio_status" msgid="4237055636967709208">"正在听音频内容"</string>
<string name="game_status" msgid="1340694320630973259">"正在玩游戏"</string>
<string name="empty_user_name" msgid="3389155775773578300">"好友"</string>
<string name="empty_status" msgid="5938893404951307749">"今晚来聊聊吧!"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 47ffed0..481b92c 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1124,12 +1124,12 @@
<string name="upcoming_birthday_status_content_description" msgid="2165036816803797148">"<xliff:g id="NAME">%1$s</xliff:g>的生日快到了"</string>
<string name="anniversary_status" msgid="1790034157507590838">"週年紀念"</string>
<string name="anniversary_status_content_description" msgid="8212171790843327442">"今天是<xliff:g id="NAME">%1$s</xliff:g>的週年紀念"</string>
- <string name="location_status" msgid="1294990572202541812">"分享位置資訊"</string>
+ <string name="location_status" msgid="1294990572202541812">"正在分享位置"</string>
<string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g>正在分享位置"</string>
- <string name="new_story_status" msgid="9012195158584846525">"最新報導"</string>
+ <string name="new_story_status" msgid="9012195158584846525">"新故事"</string>
<string name="new_story_status_content_description" msgid="4963137422622516708">"<xliff:g id="NAME">%1$s</xliff:g>分享了新的動態消息"</string>
- <string name="video_status" msgid="4548544654316843225">"正在觀看"</string>
- <string name="audio_status" msgid="4237055636967709208">"正在聽取音訊"</string>
+ <string name="video_status" msgid="4548544654316843225">"正在觀看影片"</string>
+ <string name="audio_status" msgid="4237055636967709208">"正在收聽音訊"</string>
<string name="game_status" msgid="1340694320630973259">"正在玩遊戲"</string>
<string name="empty_user_name" msgid="3389155775773578300">"朋友"</string>
<string name="empty_status" msgid="5938893404951307749">"今晚傾下偈啦!"</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 1f6a94d..2cf3058 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -187,7 +187,6 @@
<!-- UDFPS colors -->
<color name="udfps_enroll_icon">#000000</color> <!-- 100% black -->
<color name="udfps_moving_target_fill">#cc4285f4</color> <!-- 80% blue -->
- <color name="udfps_moving_target_stroke">#ff669DF6</color> <!-- 100% blue -->
<color name="udfps_enroll_progress">#ff669DF6</color> <!-- 100% blue -->
<!-- Logout button -->
@@ -282,4 +281,6 @@
<!-- Wallet screen -->
<color name="wallet_card_border">#33FFFFFF</color>
+
+ <color name="people_tile_background">@android:color/system_accent2_50</color>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ad2a1ac..0022039 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -895,6 +895,10 @@
<!-- The width/height of the keyguard bottom area icon view on keyguard. -->
<dimen name="keyguard_affordance_height">48dp</dimen>
<dimen name="keyguard_affordance_width">48dp</dimen>
+
+ <dimen name="keyguard_affordance_wallet_height">48dp</dimen>
+ <dimen name="keyguard_affordance_wallet_width">48dp</dimen>
+
<dimen name="keyguard_affordance_horizontal_offset">32dp</dimen>
<dimen name="keyguard_affordance_vertical_offset">32dp</dimen>
<!-- Value should be at least sum of 'keyguard_affordance_width' +
@@ -906,7 +910,7 @@
<dimen name="keyguard_lock_width">42dp</dimen>
<dimen name="keyguard_lock_padding">20dp</dimen>
- <dimen name="keyguard_indication_margin_bottom">40dp</dimen>
+ <dimen name="keyguard_indication_margin_bottom">32dp</dimen>
<!-- The text size for battery level -->
<dimen name="battery_level_text_size">12sp</dimen>
@@ -1327,15 +1331,15 @@
<dimen name="control_spinner_padding_horizontal">20dp</dimen>
<dimen name="control_text_size">14sp</dimen>
<dimen name="control_icon_size">24dp</dimen>
- <dimen name="control_spacing">4dp</dimen>
+ <dimen name="control_spacing">8dp</dimen>
<dimen name="control_list_divider">1dp</dimen>
- <dimen name="control_corner_radius">12dp</dimen>
- <dimen name="control_height">106dp</dimen>
+ <dimen name="control_corner_radius">14dp</dimen>
+ <dimen name="control_height">104dp</dimen>
<dimen name="control_padding">12dp</dimen>
<dimen name="control_padding_adjustment">4dp</dimen>
<dimen name="control_status_normal">14sp</dimen>
<dimen name="control_status_expanded">18sp</dimen>
- <dimen name="control_base_item_margin">2dp</dimen>
+ <dimen name="control_base_item_margin">4dp</dimen>
<dimen name="control_status_padding">3dp</dimen>
<fraction name="controls_toggle_bg_intensity">5%</fraction>
<fraction name="controls_dimmed_alpha">40%</fraction>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index c388b1e..027f162 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -33,15 +33,11 @@
<!-- People Tile flag -->
<bool name="flag_conversations">false</bool>
- <bool name="flag_wallet">false</bool>
-
<!-- The new animations to/from lockscreen and AOD! -->
<bool name="flag_lockscreen_animations">false</bool>
<bool name="flag_pm_lite">true</bool>
- <bool name="flag_alarm_tile">false</bool>
-
<bool name="flag_charging_ripple">false</bool>
<bool name="flag_ongoing_call_status_bar_chip">true</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3fe6acd..7f4e475 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2821,9 +2821,17 @@
<string name="controls_media_resume">Resume</string>
<!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] -->
<string name="controls_media_settings_button">Settings</string>
+ <!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]-->
+ <string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
<!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
<string name="controls_media_smartspace_rec_title">Play</string>
+ <!-- Description for Smartspace recommendation card within media controls [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_smartspace_rec_description">Open <xliff:g id="app_label" example="Spotify">%1$s</xliff:g></string>
+ <!-- Description for Smartspace recommendation's media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]-->
+ <string name="controls_media_smartspace_rec_item_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
+ <!-- Description for Smartspace recommendation's media item which doesn't have artist info, including information for the media's title and the source app [CHAR LIMIT=NONE]-->
+ <string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string>
<!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
<string name="controls_error_timeout">Inactive, check app</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 619bf1b..92ffb42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -64,9 +64,10 @@
private float mDarkAmount;
/**
- * Boolean value indicating if notifications are visible on lock screen.
+ * Boolean value indicating if notifications are visible on lock screen. Use null to signify
+ * it is uninitialized.
*/
- private boolean mHasVisibleNotifications = true;
+ private Boolean mHasVisibleNotifications = null;
private AnimatorSet mClockInAnim = null;
private AnimatorSet mClockOutAnim = null;
@@ -263,7 +264,8 @@
* the smaller version.
*/
boolean willSwitchToLargeClock(boolean hasVisibleNotifications) {
- if (hasVisibleNotifications == mHasVisibleNotifications) {
+ if (mHasVisibleNotifications != null
+ && hasVisibleNotifications == mHasVisibleNotifications) {
return false;
}
boolean useLargeClock = !hasVisibleNotifications;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 96eda3d..2362a1a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -20,17 +20,10 @@
import android.app.IActivityManager;
import android.content.Context;
import android.graphics.Color;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridLayout;
-import android.widget.TextView;
import androidx.core.graphics.ColorUtils;
@@ -45,26 +38,18 @@
/**
* View consisting of:
* - keyguard clock
- * - logout button (on certain managed devices)
- * - owner information (if set)
* - media player (split shade mode only)
*/
public class KeyguardStatusView extends GridLayout {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "KeyguardStatusView";
- private static final int MARQUEE_DELAY_MS = 2000;
private final LockPatternUtils mLockPatternUtils;
private final IActivityManager mIActivityManager;
private ViewGroup mStatusViewContainer;
- private TextView mLogoutView;
private KeyguardClockSwitch mClockView;
- private TextView mOwnerInfo;
- private boolean mCanShowOwnerInfo = true; // by default, try to show the owner information here
private KeyguardSliceView mKeyguardSlice;
- private Runnable mPendingMarqueeStart;
- private Handler mHandler;
private View mMediaHostContainer;
private float mDarkAmount = 0;
@@ -91,64 +76,17 @@
super(context, attrs, defStyle);
mIActivityManager = ActivityManager.getService();
mLockPatternUtils = new LockPatternUtils(getContext());
- mHandler = new Handler();
- onDensityOrFontScaleChanged();
- }
-
- void setEnableMarquee(boolean enabled) {
- if (DEBUG) Log.v(TAG, "Schedule setEnableMarquee: " + (enabled ? "Enable" : "Disable"));
- if (enabled) {
- if (mPendingMarqueeStart == null) {
- mPendingMarqueeStart = () -> {
- setEnableMarqueeImpl(true);
- mPendingMarqueeStart = null;
- };
- mHandler.postDelayed(mPendingMarqueeStart, MARQUEE_DELAY_MS);
- }
- } else {
- if (mPendingMarqueeStart != null) {
- mHandler.removeCallbacks(mPendingMarqueeStart);
- mPendingMarqueeStart = null;
- }
- setEnableMarqueeImpl(false);
- }
- }
-
- private void setEnableMarqueeImpl(boolean enabled) {
- if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
- if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
- }
-
- void setCanShowOwnerInfo(boolean canShowOwnerInfo) {
- mCanShowOwnerInfo = canShowOwnerInfo;
- mOwnerInfo = findViewById(R.id.owner_info);
- if (mOwnerInfo != null) {
- if (mCanShowOwnerInfo) {
- mOwnerInfo.setVisibility(VISIBLE);
- updateOwnerInfo();
- } else {
- mOwnerInfo.setVisibility(GONE);
- mOwnerInfo = null;
- }
- }
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mStatusViewContainer = findViewById(R.id.status_view_container);
- mLogoutView = findViewById(R.id.logout);
- if (mLogoutView != null) {
- mLogoutView.setOnClickListener(this::onLogoutClicked);
- }
mClockView = findViewById(R.id.keyguard_clock_container);
if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) {
mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext));
}
- if (mCanShowOwnerInfo) {
- mOwnerInfo = findViewById(R.id.owner_info);
- }
mKeyguardSlice = findViewById(R.id.keyguard_status_area);
mTextColor = mClockView.getCurrentTextColor();
@@ -158,7 +96,6 @@
mMediaHostContainer = findViewById(R.id.status_view_media_container);
- updateOwnerInfo();
updateDark();
}
@@ -173,60 +110,6 @@
mShowingHeader = hasHeader;
}
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- layoutOwnerInfo();
- }
-
- int getLogoutButtonHeight() {
- if (mLogoutView == null) {
- return 0;
- }
- return mLogoutView.getVisibility() == VISIBLE ? mLogoutView.getHeight() : 0;
- }
-
- int getOwnerInfoHeight() {
- if (mOwnerInfo == null) {
- return 0;
- }
- return mOwnerInfo.getVisibility() == VISIBLE ? mOwnerInfo.getHeight() : 0;
- }
-
- void updateLogoutView(boolean shouldShowLogout) {
- if (mLogoutView == null) {
- return;
- }
- mLogoutView.setVisibility(shouldShowLogout ? VISIBLE : GONE);
- // Logout button will stay in language of user 0 if we don't set that manually.
- mLogoutView.setText(mContext.getResources().getString(
- com.android.internal.R.string.global_action_logout));
- }
-
- void onDensityOrFontScaleChanged() {
- if (mOwnerInfo != null) {
- mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- getResources().getDimensionPixelSize(
- com.android.systemui.R.dimen.widget_label_font_size));
- loadBottomMargin();
- }
- }
-
- void updateOwnerInfo() {
- if (mOwnerInfo == null) return;
- String info = mLockPatternUtils.getDeviceOwnerInfo();
- if (info == null) {
- // Use the current user owner information if enabled.
- final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
- KeyguardUpdateMonitor.getCurrentUser());
- if (ownerInfoEnabled) {
- info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
- }
- }
- mOwnerInfo.setText(info);
- updateDark();
- }
-
void setDarkAmount(float darkAmount) {
if (mDarkAmount == darkAmount) {
return;
@@ -238,17 +121,6 @@
}
void updateDark() {
- boolean dark = mDarkAmount == 1;
- if (mLogoutView != null) {
- mLogoutView.setAlpha(dark ? 0 : 1);
- }
-
- if (mOwnerInfo != null) {
- boolean hasText = !TextUtils.isEmpty(mOwnerInfo.getText());
- mOwnerInfo.setVisibility(hasText ? VISIBLE : GONE);
- layoutOwnerInfo();
- }
-
final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
mKeyguardSlice.setDarkAmount(mDarkAmount);
mClockView.setTextColor(blendedTextColor);
@@ -277,13 +149,8 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("KeyguardStatusView:");
- pw.println(" mOwnerInfo: " + (mOwnerInfo == null
- ? "null" : mOwnerInfo.getVisibility() == VISIBLE));
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
- if (mLogoutView != null) {
- pw.println(" logout visible: " + (mLogoutView.getVisibility() == VISIBLE));
- }
if (mClockView != null) {
mClockView.dump(fd, pw, args);
}
@@ -297,28 +164,4 @@
mIconTopMarginWithHeader = getResources().getDimensionPixelSize(
R.dimen.widget_vertical_padding_with_header);
}
-
- private void layoutOwnerInfo() {
- if (mOwnerInfo != null && mOwnerInfo.getVisibility() != GONE) {
- // Animate owner info during wake-up transition
- mOwnerInfo.setAlpha(1f - mDarkAmount);
-
- float ratio = mDarkAmount;
- // Calculate how much of it we should crop in order to have a smooth transition
- int collapsed = mOwnerInfo.getTop() - mOwnerInfo.getPaddingTop();
- int expanded = mOwnerInfo.getBottom() + mOwnerInfo.getPaddingBottom();
- int toRemove = (int) ((expanded - collapsed) * ratio);
- setBottom(getMeasuredHeight() - toRemove);
- }
- }
-
- private void onLogoutClicked(View view) {
- int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
- try {
- mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
- mIActivityManager.stopUser(currentUserId, true /*force*/, null);
- } catch (RemoteException re) {
- Log.e(TAG, "Failed to logout user", re);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index e4cabcb..32935c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -17,7 +17,6 @@
package com.android.keyguard;
import android.graphics.Rect;
-import android.os.UserHandle;
import android.util.Slog;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -97,8 +96,6 @@
@Override
public void onInit() {
mKeyguardClockSwitchController.init();
- mView.setEnableMarquee(mKeyguardUpdateMonitor.isDeviceInteractive());
- mView.updateLogoutView(shouldShowLogout());
}
@Override
@@ -143,20 +140,6 @@
}
/**
- * Get the height of the logout button.
- */
- public int getLogoutButtonHeight() {
- return mView.getLogoutButtonHeight();
- }
-
- /**
- * Get the height of the owner information view.
- */
- public int getOwnerInfoHeight() {
- return mView.getOwnerInfoHeight();
- }
-
- /**
* Set keyguard status view alpha.
*/
public void setAlpha(float alpha) {
@@ -254,11 +237,6 @@
mKeyguardClockSwitchController.refresh();
}
- private boolean shouldShowLogout() {
- return mKeyguardUpdateMonitor.isLogoutEnabled()
- && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
- }
-
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
@@ -269,7 +247,6 @@
@Override
public void onDensityOrFontScaleChanged() {
mKeyguardClockSwitchController.onDensityOrFontScaleChanged();
- mView.onDensityOrFontScaleChanged();
}
};
@@ -277,8 +254,6 @@
@Override
public void onLockScreenModeChanged(int mode) {
mKeyguardSliceViewController.updateLockScreenMode(mode);
- mView.setCanShowOwnerInfo(false);
- mView.updateLogoutView(false);
}
@Override
@@ -301,31 +276,12 @@
if (showing) {
if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
refreshTime();
- mView.updateOwnerInfo();
- mView.updateLogoutView(shouldShowLogout());
}
}
@Override
- public void onStartedWakingUp() {
- mView.setEnableMarquee(true);
- }
-
- @Override
- public void onFinishedGoingToSleep(int why) {
- mView.setEnableMarquee(false);
- }
-
- @Override
public void onUserSwitchComplete(int userId) {
mKeyguardClockSwitchController.refreshFormat();
- mView.updateOwnerInfo();
- mView.updateLogoutView(shouldShowLogout());
- }
-
- @Override
- public void onLogoutEnabledChanged() {
- mView.updateLogoutView(shouldShowLogout());
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 9c5a40c..186917f 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -46,6 +46,9 @@
ACTION_FINGERPRINT_WAKE =
"com.android.systemui.latency.ACTION_FINGERPRINT_WAKE";
private static final String
+ ACTION_FACE_WAKE =
+ "com.android.systemui.latency.ACTION_FACE_WAKE";
+ private static final String
ACTION_TURN_ON_SCREEN =
"com.android.systemui.latency.ACTION_TURN_ON_SCREEN";
private final BiometricUnlockController mBiometricUnlockController;
@@ -70,13 +73,16 @@
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_FINGERPRINT_WAKE);
+ filter.addAction(ACTION_FACE_WAKE);
filter.addAction(ACTION_TURN_ON_SCREEN);
mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_FINGERPRINT_WAKE.equals(action)) {
- fakeWakeAndUnlock();
+ fakeWakeAndUnlock(BiometricSourceType.FINGERPRINT);
+ } else if (ACTION_FACE_WAKE.equals(action)) {
+ fakeWakeAndUnlock(BiometricSourceType.FACE);
} else if (ACTION_TURN_ON_SCREEN.equals(action)) {
fakeTurnOnScreen();
}
@@ -93,10 +99,9 @@
SystemClock.uptimeMillis(), WAKE_REASON_UNKNOWN, "android.policy:LATENCY_TESTS");
}
- private void fakeWakeAndUnlock() {
- mBiometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
+ private void fakeWakeAndUnlock(BiometricSourceType type) {
+ mBiometricUnlockController.onBiometricAcquired(type);
mBiometricUnlockController.onBiometricAuthenticated(
- KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT,
- true /* isStrongBiometric */);
+ KeyguardUpdateMonitor.getCurrentUser(), type, true /* isStrongBiometric */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 01fbe39..75373ab 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -110,7 +110,8 @@
visibility = GONE
}
})
- vibrate()
+ // TODO (b/185124905): custom haptic TBD
+ // vibrate()
animatorSet.start()
visibility = VISIBLE
rippleInProgress = true
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 67dcc8d..dd339ab 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -211,6 +211,12 @@
}
}
+ void onAcquiredGood() {
+ if (mEnrollHelper != null) {
+ mEnrollHelper.animateIfLastStep();
+ }
+ }
+
void onEnrollmentHelp() {
if (mEnrollHelper != null) {
mEnrollHelper.onEnrollmentHelp();
@@ -260,6 +266,11 @@
}
mGoodCaptureReceived = true;
mView.stopIllumination();
+ if (mServerRequest != null) {
+ mServerRequest.onAcquiredGood();
+ } else {
+ Log.e(TAG, "Null serverRequest when onAcquiredGood");
+ }
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index 6b6e0f1..ea69b1d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -47,7 +47,6 @@
@NonNull private final Drawable mMovingTargetFpIcon;
@NonNull private final Paint mSensorOutlinePaint;
@NonNull private final Paint mBlueFill;
- @NonNull private final Paint mBlueStroke;
@Nullable private RectF mSensorRect;
@Nullable private UdfpsEnrollHelper mEnrollHelper;
@@ -76,12 +75,6 @@
mBlueFill.setColor(context.getColor(R.color.udfps_moving_target_fill));
mBlueFill.setStyle(Paint.Style.FILL);
- mBlueStroke = new Paint(0 /* flags */);
- mBlueStroke.setAntiAlias(true);
- mBlueStroke.setColor(context.getColor(R.color.udfps_moving_target_stroke));
- mBlueStroke.setStyle(Paint.Style.STROKE);
- mBlueStroke.setStrokeWidth(12);
-
mMovingTargetFpIcon = context.getResources().getDrawable(R.drawable.ic_fingerprint, null);
mMovingTargetFpIcon.setTint(Color.WHITE);
mMovingTargetFpIcon.mutate();
@@ -146,6 +139,10 @@
}
}
+ void onLastStepAcquired() {
+ mProgressDrawable.onLastStepAcquired();
+ }
+
@Override
public void draw(@NonNull Canvas canvas) {
mProgressDrawable.draw(canvas);
@@ -163,7 +160,6 @@
canvas.scale(mCurrentScale, mCurrentScale,
mSensorRect.centerX(), mSensorRect.centerY());
canvas.drawOval(mSensorRect, mBlueFill);
- canvas.drawOval(mSensorRect, mBlueStroke);
}
mMovingTargetFpIcon.draw(canvas);
@@ -188,7 +184,6 @@
super.setAlpha(alpha);
mSensorOutlinePaint.setAlpha(alpha);
mBlueFill.setAlpha(alpha);
- mBlueStroke.setAlpha(alpha);
mMovingTargetFpIcon.setAlpha(alpha);
invalidateSelf();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 470fb8c..412b776 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -21,8 +21,11 @@
import android.content.Context;
import android.graphics.PointF;
import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.media.AudioAttributes;
import android.os.Build;
import android.os.UserHandle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.util.TypedValue;
@@ -47,8 +50,15 @@
// Enroll with two center touches before going to guided enrollment
private static final int NUM_CENTER_TOUCHES = 2;
+ private static final AudioAttributes VIBRATION_SONFICATION_ATTRIBUTES =
+ new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .build();
+
interface Listener {
void onEnrollmentProgress(int remaining, int totalSteps);
+ void onLastStepAcquired();
}
@NonNull private final Context mContext;
@@ -56,6 +66,9 @@
private final int mEnrollReason;
private final boolean mAccessibilityEnabled;
@NonNull private final List<PointF> mGuidedEnrollmentPoints;
+ @NonNull private final Vibrator mVibrator;
+ @NonNull private final VibrationEffect mEffectClick =
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
private int mTotalSteps = -1;
private int mRemainingSteps = -1;
@@ -69,6 +82,7 @@
public UdfpsEnrollHelper(@NonNull Context context, int reason) {
mContext = context;
mEnrollReason = reason;
+ mVibrator = context.getSystemService(Vibrator.class);
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
mAccessibilityEnabled = am.isEnabled();
@@ -127,6 +141,7 @@
if (remaining != mRemainingSteps) {
mLocationsEnrolled++;
+ vibrateSuccess();
}
mRemainingSteps = remaining;
@@ -178,4 +193,20 @@
.get(index % mGuidedEnrollmentPoints.size());
return new PointF(originalPoint.x * scale, originalPoint.y * scale);
}
+
+ void animateIfLastStep() {
+ if (mListener == null) {
+ Log.e(TAG, "animateIfLastStep, null listener");
+ return;
+ }
+
+ if (mRemainingSteps <= 2 && mRemainingSteps >= 0) {
+ mListener.onLastStepAcquired();
+ vibrateSuccess();
+ }
+ }
+
+ private void vibrateSuccess() {
+ mVibrator.vibrate(mEffectClick, VIBRATION_SONFICATION_ATTRIBUTES);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 5c9e52f..4195009 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -23,6 +23,7 @@
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import android.util.TypedValue;
import androidx.annotation.NonNull;
@@ -46,6 +47,8 @@
@Nullable private ValueAnimator mProgressAnimator;
private float mProgress;
+ private int mRotation; // After last step, rotate the progress bar once
+ private boolean mLastStepAcquired;
public UdfpsEnrollProgressBarDrawable(@NonNull Context context,
@NonNull UdfpsEnrollDrawable parent) {
@@ -81,13 +84,34 @@
// Add one so that the first steps actually changes progress, but also so that the last
// step ends at 1.0
final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1);
+ setEnrollmentProgress(progress);
+ }
+
+ private void setEnrollmentProgress(float progress) {
+ if (mLastStepAcquired) {
+ return;
+ }
+
+ long animationDuration = 150;
+
+ if (progress == 1.f) {
+ animationDuration = 400;
+ final ValueAnimator rotationAnimator = ValueAnimator.ofInt(0, 400);
+ rotationAnimator.setDuration(animationDuration);
+ rotationAnimator.addUpdateListener(animation -> {
+ Log.d(TAG, "Rotation: " + mRotation);
+ mRotation = (int) animation.getAnimatedValue();
+ mParent.invalidateSelf();
+ });
+ rotationAnimator.start();
+ }
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
mProgressAnimator.cancel();
}
mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
- mProgressAnimator.setDuration(150);
+ mProgressAnimator.setDuration(animationDuration);
mProgressAnimator.addUpdateListener(animation -> {
mProgress = (float) animation.getAnimatedValue();
// Use the parent to invalidate, since it's the one that's attached as the view's
@@ -99,12 +123,17 @@
mProgressAnimator.start();
}
+ void onLastStepAcquired() {
+ setEnrollmentProgress(1.f);
+ mLastStepAcquired = true;
+ }
+
@Override
public void draw(@NonNull Canvas canvas) {
canvas.save();
// Progress starts from the top, instead of the right
- canvas.rotate(-90, getBounds().centerX(), getBounds().centerY());
+ canvas.rotate(-90 + mRotation, getBounds().centerX(), getBounds().centerY());
// Progress bar "background track"
final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 3b30220..7f4d7fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -63,8 +63,10 @@
}
void onEnrollmentProgress(int remaining, int totalSteps) {
- mHandler.post(() -> {
- mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
- });
+ mHandler.post(() -> mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps));
+ }
+
+ void onLastStepAcquired() {
+ mHandler.post(mFingerprintDrawable::onLastStepAcquired);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 953448d..91cc149 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -32,7 +32,17 @@
private final int mEnrollProgressBarRadius;
@NonNull private final UdfpsEnrollHelper mEnrollHelper;
@NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener =
- mView::onEnrollmentProgress;
+ new UdfpsEnrollHelper.Listener() {
+ @Override
+ public void onEnrollmentProgress(int remaining, int totalSteps) {
+ mView.onEnrollmentProgress(remaining, totalSteps);
+ }
+
+ @Override
+ public void onLastStepAcquired() {
+ mView.onLastStepAcquired();
+ }
+ };
protected UdfpsEnrollViewController(
@NonNull UdfpsEnrollView view,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 9a32412..750d42c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -62,7 +62,6 @@
private boolean mQsExpanded;
private boolean mFaceDetectRunning;
private boolean mHintShown;
- private boolean mTransitioningFromHome;
private int mStatusBarState;
/**
@@ -112,11 +111,11 @@
mStatusBarState = mStatusBarStateController.getState();
mQsExpanded = mKeyguardViewManager.isQsExpanded();
mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN;
+ mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing();
updateAlpha();
updatePauseAuth();
mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing();
}
@Override
@@ -127,7 +126,6 @@
mStatusBarStateController.removeCallback(mStateListener);
mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
- mTransitioningFromHome = false;
mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
if (mCancelDelayedHintRunnable != null) {
@@ -141,7 +139,6 @@
super.dump(fd, pw, args);
pw.println("mShowingUdfpsBouncer=" + mShowingUdfpsBouncer);
pw.println("mFaceDetectRunning=" + mFaceDetectRunning);
- pw.println("mTransitioningFromHomeToKeyguard=" + mTransitioningFromHome);
pw.println("mStatusBarState=" + StatusBarState.toShortString(mStatusBarState));
pw.println("mQsExpanded=" + mQsExpanded);
pw.println("mIsBouncerVisible=" + mIsBouncerVisible);
@@ -195,10 +192,6 @@
return true;
}
- if (mTransitioningFromHome && mKeyguardViewMediator.isAnimatingScreenOff()) {
- return true;
- }
-
if (mQsExpanded) {
return true;
}
@@ -277,17 +270,6 @@
public void onDozeAmountChanged(float linear, float eased) {
if (linear != 0) showUdfpsBouncer(false);
mView.onDozeAmountChanged(linear, eased);
- if (linear == 1f) {
- // transition has finished
- mTransitioningFromHome = false;
- }
- updatePauseAuth();
- }
-
- @Override
- public void onStatePreChange(int oldState, int newState) {
- mTransitioningFromHome = oldState == StatusBarState.SHADE
- && newState == StatusBarState.KEYGUARD;
updatePauseAuth();
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 821a171..af70892 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -399,6 +399,12 @@
val baseLayout = inflater.inflate(
R.layout.controls_base_item, lastRow, false) as ViewGroup
lastRow.addView(baseLayout)
+
+ // Use ConstraintLayout in the future... for now, manually adjust margins
+ if (lastRow.getChildCount() == 1) {
+ val lp = baseLayout.getLayoutParams() as ViewGroup.MarginLayoutParams
+ lp.setMarginStart(0)
+ }
val cvh = ControlViewHolder(
baseLayout,
controlsController.get(),
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 4418696..ff3cb21 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -62,6 +62,7 @@
private final DozeParameters mDozeParameters;
private final DozeLog mDozeLog;
private final Lazy<StatusBarStateController> mStatusBarStateController;
+ private final TunerService mTunerService;
private boolean mKeyguardShowing;
private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
@@ -102,8 +103,15 @@
mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
mDozeLog = dozeLog;
- tunerService.addTunable(this, Settings.Secure.DOZE_ALWAYS_ON);
+ mTunerService = tunerService;
mStatusBarStateController = statusBarStateController;
+
+ mTunerService.addTunable(this, Settings.Secure.DOZE_ALWAYS_ON);
+ }
+
+ @Override
+ public void destroy() {
+ mTunerService.removeTunable(this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a159984..56efafb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1651,14 +1651,19 @@
Trace.endSection();
}
- /** Hide the keyguard and let {@code runner} handle the animation. */
+ /**
+ * Hide the keyguard and let {@code runner} handle the animation.
+ *
+ * This method should typically be called after {@link ViewMediatorCallback#keyguardDonePending}
+ * was called, when we are ready to hide the keyguard.
+ */
public void hideWithAnimation(IRemoteAnimationRunner runner) {
- if (!mShowing) {
+ if (!mKeyguardDonePending) {
return;
}
mKeyguardExitAnimationRunner = runner;
- hideLocked();
+ mViewMediatorCallback.readyForKeyguardDone();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index d698142..4201411 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -71,12 +71,14 @@
*/
public class MediaControlPanel {
private static final String TAG = "MediaControlPanel";
+
private static final float DISABLED_ALPHA = 0.38f;
private static final String EXTRAS_SMARTSPACE_INTENT =
"com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
- private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
private static final int MEDIA_RECOMMENDATION_ITEMS_PER_ROW = 3;
private static final int MEDIA_RECOMMENDATION_MAX_NUM = 6;
+ private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name";
+ private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
@@ -292,6 +294,12 @@
});
}
+ // Accessibility label
+ mPlayerViewHolder.getPlayer().setContentDescription(
+ mContext.getString(
+ R.string.controls_media_playing_item_description,
+ data.getSong(), data.getArtist(), data.getApp()));
+
ImageView albumView = mPlayerViewHolder.getAlbumView();
boolean hasArtwork = data.getArtwork() != null;
if (hasArtwork) {
@@ -330,7 +338,6 @@
}
// Song name
-
TextView titleText = mPlayerViewHolder.getTitleText();
titleText.setText(data.getSong());
@@ -497,8 +504,8 @@
mInstanceId = data.getTargetId().hashCode();
mBackgroundColor = data.getBackgroundColor();
- mRecommendationViewHolder.getRecommendations()
- .setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
+ TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations();
+ recommendationCard.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
List<SmartspaceAction> mediaRecommendationList = data.getRecommendations();
if (mediaRecommendationList == null || mediaRecommendationList.isEmpty()) {
@@ -522,15 +529,17 @@
icon.setColorFilter(getGrayscaleFilter());
ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
headerLogoImageView.setImageDrawable(icon);
- // Set up media source app's label text. Fallback to "Play" if the found label is empty.
+ // Set up media source app's label text.
CharSequence appLabel = packageManager.getApplicationLabel(applicationInfo);
if (appLabel.length() != 0) {
TextView headerTitleText = mRecommendationViewHolder.getCardText();
headerTitleText.setText(appLabel);
}
- // Set up media card's tap action if applicable.
- setSmartspaceRecItemOnClickListener(
- mRecommendationViewHolder.getRecommendations(), data.getCardAction());
+ // Set up media rec card's tap action if applicable.
+ setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction());
+ // Set up media rec card's accessibility label.
+ recommendationCard.setContentDescription(
+ mContext.getString(R.string.controls_media_smartspace_rec_description, appLabel));
List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems();
List<Integer> mediaCoverItemsResIds = mRecommendationViewHolder.getMediaCoverItemsResIds();
@@ -554,6 +563,21 @@
// Set up the media item's click listener if applicable.
setSmartspaceRecItemOnClickListener(mediaCoverImageView, recommendation);
+ // Set up the accessibility label for the media item.
+ String artistName = recommendation.getExtras()
+ .getString(KEY_SMARTSPACE_ARTIST_NAME, "");
+ if (artistName.isEmpty()) {
+ mediaCoverImageView.setContentDescription(
+ mContext.getString(
+ R.string.controls_media_smartspace_rec_item_no_artist_description,
+ recommendation.getTitle(), appLabel));
+ } else {
+ mediaCoverImageView.setContentDescription(
+ mContext.getString(
+ R.string.controls_media_smartspace_rec_item_description,
+ recommendation.getTitle(), artistName, appLabel));
+ }
+
if (uiComponentIndex < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) {
setVisibleAndAlpha(collapsedSet,
mediaCoverItemsResIds.get(uiComponentIndex), true);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index d8b342a..3e5e140 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1447,10 +1447,14 @@
private void updateAssistantEntrypoints() {
mAssistantAvailable = mAssistManagerLazy.get()
.getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
+ boolean longPressDefault = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault);
mLongPressHomeEnabled = Settings.Secure.getInt(mContentResolver,
- Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, 1) != 0;
+ Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0) != 0;
+ boolean gestureDefault = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_assistTouchGestureEnabledDefault);
mAssistantTouchGestureEnabled = Settings.Secure.getInt(mContentResolver,
- Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, 1) != 0;
+ Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0) != 0;
if (mOverviewProxyService.getProxy() != null) {
try {
mOverviewProxyService.getProxy().onAssistantAvailable(mAssistantAvailable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 01a6684..ad4886c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -42,6 +42,7 @@
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.animation.UniqueObjectHostView;
import java.util.ArrayList;
import java.util.List;
@@ -384,12 +385,17 @@
private void switchSecurityFooter() {
if (mSecurityFooter != null) {
if (mContext.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE && mHeaderContainer != null
- && !mSecurityFooter.getParent().equals(mHeaderContainer)) {
+ == Configuration.ORIENTATION_LANDSCAPE && mHeaderContainer != null) {
// Adding the security view to the header, that enables us to avoid scrolling
switchToParent(mSecurityFooter, mHeaderContainer, 0);
} else {
- switchToParent(mSecurityFooter, this, -1);
+ // Where should this go? If there's media, right before it. Otherwise, at the end.
+ View mediaView = findViewByPredicate(v -> v instanceof UniqueObjectHostView);
+ int index = -1;
+ if (mediaView != null) {
+ index = indexOfChild(mediaView);
+ }
+ switchToParent(mSecurityFooter, this, index);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index f668722..08a68bc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -68,7 +68,9 @@
private Space mDatePrivacySeparator;
private View mClockIconsSeparator;
private boolean mShowClockIconsSeparator;
- private ViewGroup mRightLayout;
+ private View mRightLayout;
+ private View mDateContainer;
+ private View mPrivacyContainer;
private BatteryMeterView mBatteryRemainingIcon;
private StatusIconContainer mIconContainer;
@@ -129,6 +131,8 @@
mSecurityHeaderView = findViewById(R.id.header_text_container);
mClockIconsSeparator = findViewById(R.id.separator);
mRightLayout = findViewById(R.id.rightLayout);
+ mDateContainer = findViewById(R.id.date_container);
+ mPrivacyContainer = findViewById(R.id.privacy_container);
mClockView = findViewById(R.id.clock);
mDatePrivacySeparator = findViewById(R.id.space);
@@ -179,6 +183,7 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateResources();
+ setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
@Override
@@ -187,6 +192,18 @@
updateResources();
}
+ private void setDatePrivacyContainersWidth(boolean landscape) {
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mDateContainer.getLayoutParams();
+ lp.width = landscape ? WRAP_CONTENT : 0;
+ lp.weight = landscape ? 0f : 1f;
+ mDateContainer.setLayoutParams(lp);
+
+ lp = (LinearLayout.LayoutParams) mPrivacyContainer.getLayoutParams();
+ lp.width = landscape ? WRAP_CONTENT : 0;
+ lp.weight = landscape ? 0f : 1f;
+ mPrivacyContainer.setLayoutParams(lp);
+ }
+
void updateResources() {
Resources resources = mContext.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index c2a6255..69d49d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -24,7 +24,6 @@
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.NextAlarmController
import java.util.Locale
import javax.inject.Inject
@@ -38,7 +37,6 @@
statusBarStateController: StatusBarStateController,
activityStarter: ActivityStarter,
qsLogger: QSLogger,
- private val featureFlags: FeatureFlags,
private val userTracker: UserTracker,
nextAlarmController: NextAlarmController
) : QSTileImpl<QSTile.State>(
@@ -65,10 +63,6 @@
nextAlarmController.observe(this, callback)
}
- override fun isAvailable(): Boolean {
- return featureFlags.isAlarmTileAvailable
- }
-
override fun newTileState(): QSTile.State {
return QSTile.State().apply {
handlesLongClick = false
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 7fab0f5..0e4434b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -49,7 +49,6 @@
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
@@ -71,7 +70,6 @@
private final PackageManager mPackageManager;
private final SecureSettings mSecureSettings;
private final QuickAccessWalletController mController;
- private final FeatureFlags mFeatureFlags;
private WalletCard mSelectedCard;
@VisibleForTesting Drawable mCardViewDrawable;
@@ -89,15 +87,13 @@
KeyguardStateController keyguardStateController,
PackageManager packageManager,
SecureSettings secureSettings,
- QuickAccessWalletController quickAccessWalletController,
- FeatureFlags featureFlags) {
+ QuickAccessWalletController quickAccessWalletController) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = quickAccessWalletController;
mKeyguardStateController = keyguardStateController;
mPackageManager = packageManager;
mSecureSettings = secureSettings;
- mFeatureFlags = featureFlags;
}
@@ -192,8 +188,7 @@
@Override
public boolean isAvailable() {
- return mFeatureFlags.isQuickAccessWalletEnabled()
- && mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
+ return mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
&& !mPackageManager.hasSystemFeature(FEATURE_CHROME_OS)
&& mSecureSettings.getString(NFC_PAYMENT_DEFAULT_COMPONENT) != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 0df69a0..9fa4609 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -204,6 +204,12 @@
return;
}
NotificationEntry entry = alertEntry.mEntry;
+
+ // If the notification is animating, we will remove it at the end of the animation.
+ if (entry != null && entry.isExpandAnimationRunning()) {
+ return;
+ }
+
mAlertEntries.remove(key);
onAlertEntryRemoved(alertEntry);
entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index 071e947..ce796d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -107,6 +107,8 @@
it.println("minBlurRadius: $minBlurRadius")
it.println("maxBlurRadius: $maxBlurRadius")
it.println("supportsBlursOnWindows: ${supportsBlursOnWindows()}")
+ it.println("CROSS_WINDOW_BLUR_SUPPORTED: $CROSS_WINDOW_BLUR_SUPPORTED")
+ it.println("isHighEndGfx: ${ActivityManager.isHighEndGfx()}")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index c7b6e67..7e67619 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -65,18 +65,10 @@
return mFlagReader.isEnabled(R.bool.flag_monet);
}
- public boolean isQuickAccessWalletEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_wallet);
- }
-
public boolean isPMLiteEnabled() {
return mFlagReader.isEnabled(R.bool.flag_pm_lite);
}
- public boolean isAlarmTileAvailable() {
- return mFlagReader.isEnabled(R.bool.flag_alarm_tile);
- }
-
public boolean isChargingRippleEnabled() {
return mFlagReader.isEnabled(R.bool.flag_charging_ripple);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index bce39ce..c248670 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -7,6 +7,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController
+import com.android.systemui.statusbar.policy.HeadsUpUtil
import kotlin.math.ceil
import kotlin.math.max
@@ -22,8 +23,8 @@
return NotificationLaunchAnimatorController(
notificationShadeWindowViewController,
notificationListContainer,
- notification,
- headsUpManager
+ headsUpManager,
+ notification
)
}
}
@@ -36,10 +37,11 @@
class NotificationLaunchAnimatorController(
private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
private val notificationListContainer: NotificationListContainer,
- private val notification: ExpandableNotificationRow,
- private val headsUpManager: HeadsUpManagerPhone
+ private val headsUpManager: HeadsUpManagerPhone,
+ private val notification: ExpandableNotificationRow
) : ActivityLaunchAnimator.Controller {
- private val notificationKey = notification.entry.sbn.key
+ private val notificationEntry = notification.entry
+ private val notificationKey = notificationEntry.sbn.key
override var launchContainer: ViewGroup
get() = notification.rootView as ViewGroup
@@ -82,6 +84,7 @@
override fun onIntentStarted(willAnimate: Boolean) {
notificationShadeWindowViewController.setExpandAnimationRunning(willAnimate)
+ notificationEntry.isExpandAnimationRunning = willAnimate
if (!willAnimate) {
removeHun(animate = true)
@@ -93,6 +96,7 @@
return
}
+ HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(notification, animate)
headsUpManager.removeNotification(notificationKey, true /* releaseImmediately */, animate)
}
@@ -100,6 +104,7 @@
// TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
// here?
notificationShadeWindowViewController.setExpandAnimationRunning(false)
+ notificationEntry.isExpandAnimationRunning = false
removeHun(animate = true)
}
@@ -116,6 +121,7 @@
notification.isExpandAnimationRunning = false
notificationShadeWindowViewController.setExpandAnimationRunning(false)
+ notificationEntry.isExpandAnimationRunning = false
notificationListContainer.setExpandingNotification(null)
applyParams(null)
removeHun(animate = false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 5f93f480..9f82152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -179,6 +179,7 @@
private boolean mIsAlerting;
public boolean mRemoteEditImeVisible;
+ private boolean mExpandAnimationRunning;
/**
* @param sbn the StatusBarNotification from system server
@@ -952,6 +953,16 @@
return mIsAlerting;
}
+ /** Set whether this notification is currently used to animate a launch. */
+ public void setExpandAnimationRunning(boolean expandAnimationRunning) {
+ mExpandAnimationRunning = expandAnimationRunning;
+ }
+
+ /** Whether this notification is currently used to animate a launch. */
+ public boolean isExpandAnimationRunning() {
+ return mExpandAnimationRunning;
+ }
+
/** Information about a suggestion that is being edited. */
public static class EditedSuggestionInfo {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index b18f191..4136624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -61,12 +61,6 @@
private static final float HORIZONTAL_ANIMATION_END = 0.2f;
/**
- * At which point from [0,1] does the alpha animation end (or start when
- * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated.
- */
- private static final float ALPHA_ANIMATION_END = 0.0f;
-
- /**
* At which point from [0,1] does the horizontal collapse animation start (or start when
* expanding)? 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
*/
@@ -497,10 +491,10 @@
float targetValue;
if (isAppearing) {
- mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
+ mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN;
targetValue = 1.0f;
} else {
- mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN;
+ mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
targetValue = 0.0f;
}
mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
@@ -584,19 +578,21 @@
}
private void updateAppearRect() {
- float inverseFraction = (1.0f - mAppearAnimationFraction);
- float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction);
- float translateYTotalAmount = translationFraction * mAnimationTranslationY;
- mAppearAnimationTranslation = translateYTotalAmount;
+ float interpolatedFraction = mCurrentAppearInterpolator.getInterpolation(
+ mAppearAnimationFraction);
+ mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
final int actualHeight = getActualHeight();
- float bottom = actualHeight * mAppearAnimationFraction;
+ float bottom = actualHeight * interpolatedFraction;
- setOutlineRect(0, mAppearAnimationTranslation,
- getWidth(), bottom + mAppearAnimationTranslation);
+ setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
+ bottom + mAppearAnimationTranslation);
}
- private float getAppearAnimationFraction() {
- return mAppearAnimationFraction >= 0 ? mAppearAnimationFraction : 1;
+ private float getInterpolatedAppearAnimationFraction() {
+ if (mAppearAnimationFraction >= 0) {
+ return mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
+ }
+ return 1.0f;
}
private void updateAppearAnimationAlpha() {
@@ -629,18 +625,14 @@
@Override
public float getCurrentBackgroundRadiusTop() {
- float fraction = getAppearAnimationFraction();
- return isHeadsUpAnimatingAway() || isHeadsUp()
- ? mOutlineRadius * fraction
- : super.getCurrentBackgroundRadiusTop();
+ float fraction = getInterpolatedAppearAnimationFraction();
+ return MathUtils.lerp(0, super.getCurrentBackgroundRadiusTop(), fraction);
}
@Override
public float getCurrentBackgroundRadiusBottom() {
- float fraction = getAppearAnimationFraction();
- return isHeadsUpAnimatingAway() || isHeadsUp()
- ? mOutlineRadius * fraction
- : super.getCurrentBackgroundRadiusBottom();
+ float fraction = getInterpolatedAppearAnimationFraction();
+ return MathUtils.lerp(0, super.getCurrentBackgroundRadiusBottom(), fraction);
}
private void applyBackgroundRoundness(float topRadius, float bottomRadius) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 3728388..5134c62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -249,9 +249,18 @@
@Override
public boolean setTopRoundness(float topRoundness, boolean animate) {
if (mTopRoundness != topRoundness) {
+ float diff = Math.abs(topRoundness - mTopRoundness);
mTopRoundness = topRoundness;
+ boolean shouldAnimate = animate;
+ if (PropertyAnimator.isAnimating(this, TOP_ROUNDNESS) && diff > 0.5f) {
+ // Fail safe:
+ // when we've been animating previously and we're now getting an update in the
+ // other direction, make sure to animate it too, otherwise, the localized updating
+ // may make the start larger than 1.0.
+ shouldAnimate = true;
+ }
PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
- ROUNDNESS_PROPERTIES, animate);
+ ROUNDNESS_PROPERTIES, shouldAnimate);
return true;
}
return false;
@@ -286,9 +295,18 @@
@Override
public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
if (mBottomRoundness != bottomRoundness) {
+ float diff = Math.abs(bottomRoundness - mBottomRoundness);
mBottomRoundness = bottomRoundness;
+ boolean shouldAnimate = animate;
+ if (PropertyAnimator.isAnimating(this, BOTTOM_ROUNDNESS) && diff > 0.5f) {
+ // Fail safe:
+ // when we've been animating previously and we're now getting an update in the
+ // other direction, make sure to animate it too, otherwise, the localized updating
+ // may make the start larger than 1.0.
+ shouldAnimate = true;
+ }
PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
- ROUNDNESS_PROPERTIES, animate);
+ ROUNDNESS_PROPERTIES, shouldAnimate);
return true;
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/HeadsUpAppearInterpolator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/HeadsUpAppearInterpolator.java
deleted file mode 100644
index 24e1f32..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/HeadsUpAppearInterpolator.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2015 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.notification.stack;
-
-import android.graphics.Path;
-import android.view.animation.PathInterpolator;
-
-/**
- * An interpolator specifically designed for the appear animation of heads up notifications.
- */
-public class HeadsUpAppearInterpolator extends PathInterpolator {
-
- private static float X1 = 250f;
- private static float X2 = 200f;
- private static float XTOT = (X1 + X2);;
-
- public HeadsUpAppearInterpolator() {
- super(getAppearPath());
- }
-
- private static Path getAppearPath() {
- Path path = new Path();
- path.moveTo(0, 0);
- float y1 = 90f;
- float y2 = 80f;
- path.cubicTo(X1 * 0.8f / XTOT, y1 / y2,
- X1 * 0.8f / XTOT, y1 / y2,
- X1 / XTOT, y1 / y2);
- path.cubicTo((X1 + X2 * 0.4f) / XTOT, y1 / y2,
- (X1 + X2 * 0.2f) / XTOT, 1.0f,
- 1.0f , 1.0f);
- return path;
- }
-
- public static float getFractionUntilOvershoot() {
- return X1 / XTOT;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 4b49e3a..23aefd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -18,7 +18,6 @@
import android.content.res.Resources;
import android.util.MathUtils;
-import android.view.View;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
@@ -199,7 +198,7 @@
mExpanded = expandedHeight != 0.0f;
mAppearFraction = appearFraction;
if (mTrackedHeadsUp != null) {
- updateView(mTrackedHeadsUp, true);
+ updateView(mTrackedHeadsUp, false /* animate */);
}
}
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 a6bba93..dec9888 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
@@ -548,7 +548,11 @@
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
- mNotificationRoundnessManager.updateView(entry.getRow(), true /* animate */);
+ ExpandableNotificationRow row = entry.getRow();
+ // update the roundedness posted, because we might be animating away the
+ // headsup soon, so no need to set the roundedness to 0 and then back to 1.
+ row.post(() -> mNotificationRoundnessManager.updateView(row,
+ true /* animate */));
}
@Override
@@ -557,7 +561,9 @@
NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
mView.setNumHeadsUp(numEntries);
mView.setTopHeadsUpEntry(topEntry);
- mNotificationRoundnessManager.updateView(entry.getRow(), false /* animate */);
+ generateHeadsUpAnimation(entry, isHeadsUp);
+ ExpandableNotificationRow row = entry.getRow();
+ mNotificationRoundnessManager.updateView(row, true /* animate */);
}
};
@@ -1234,7 +1240,7 @@
return mView.getFirstChildNotGone();
}
- public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+ private void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
mView.generateHeadsUpAnimation(entry, isHeadsUp);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 83dc6df..4fd2064 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -21,7 +21,6 @@
import android.animation.ValueAnimator;
import android.util.Property;
import android.view.View;
-import android.view.animation.Interpolator;
import com.android.keyguard.KeyguardSliceView;
import com.android.systemui.R;
@@ -50,11 +49,8 @@
public static final int ANIMATION_DURATION_SWIPE = 260;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
- public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 550;
- public static final int ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED
- = (int) (ANIMATION_DURATION_HEADS_UP_APPEAR
- * HeadsUpAppearInterpolator.getFractionUntilOvershoot());
- public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 300;
+ public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
+ public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
public static final int ANIMATION_DURATION_PULSE_APPEAR =
KeyguardSliceView.DEFAULT_ANIM_DURATION;
public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
@@ -64,8 +60,6 @@
public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
private static final int MAX_STAGGER_COUNT = 5;
- private static final HeadsUpAppearInterpolator HEADS_UP_APPEAR_INTERPOLATOR =
- new HeadsUpAppearInterpolator();
private final int mGoToFullShadeAppearingTranslation;
private final int mPulsingAppearingTranslation;
@@ -115,14 +109,6 @@
public boolean wasAdded(View view) {
return mNewAddChildren.contains(view);
}
-
- @Override
- public Interpolator getCustomInterpolator(View child, Property property) {
- if (mHeadsUpAppearChildren.contains(child) && View.TRANSLATION_Y.equals(property)) {
- return HEADS_UP_APPEAR_INTERPOLATOR;
- }
- return null;
- }
};
}
@@ -422,7 +408,7 @@
if (event.headsUpFromBottom) {
mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
} else {
- changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED,
+ changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
true /* isHeadsUpAppear */);
}
mHeadsUpAppearChildren.add(changingView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index df86e20..6d82a45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -591,7 +591,7 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- HeadsUpUtil.setIsClickedHeadsUpNotification(child, false);
+ HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(child, false);
child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
child.setTag(TAG_START_TRANSLATION_Y, null);
child.setTag(TAG_END_TRANSLATION_Y, null);
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 4d8e7de3..8e3aed4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -368,8 +368,8 @@
updateLeftAffordanceIcon();
lp = mWalletButton.getLayoutParams();
- lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
- lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
+ lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_wallet_width);
+ lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_wallet_width);
mWalletButton.setLayoutParams(lp);
mIndicationPadding = getResources().getDimensionPixelSize(
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 336cbdc..fda24c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1126,10 +1126,7 @@
mKeyguardBottomArea.setStatusBar(mStatusBar);
mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
mKeyguardBottomArea.setFalsingManager(mFalsingManager);
-
- if (mFeatureFlags.isQuickAccessWalletEnabled()) {
- mKeyguardBottomArea.initWallet(mQuickAccessWalletController);
- }
+ mKeyguardBottomArea.initWallet(mQuickAccessWalletController);
}
private void updateMaxDisplayedNotifications(boolean recompute) {
@@ -1322,8 +1319,7 @@
mNotificationStackScrollLayoutController.getHeight()
- minPadding
- shelfSize
- - bottomPadding
- - mKeyguardStatusViewController.getLogoutButtonHeight();
+ - bottomPadding;
int count = 0;
ExpandableView previousView = null;
@@ -2233,6 +2229,7 @@
left = 0;
right = getView().getRight() + mDisplayRightInset;
} else if (qsPanelBottomY > 0) { // so bounds are empty on lockscreen
+ mAmbientState.setNotificationScrimTop(mSplitShadeNotificationsTopPadding);
top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding);
bottom = mNotificationStackScrollLayoutController.getHeight();
left = mNotificationStackScrollLayoutController.getLeft();
@@ -4152,11 +4149,6 @@
entry.setHeadsUpIsVisible();
}
}
-
- @Override
- public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, isHeadsUp);
- }
}
private class HeightListener implements QS.HeightListener {
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 06aedaa..09779d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2118,7 +2118,12 @@
return;
}
- mKeyguardViewMediator.hideWithAnimation(runner);
+ // We post to the main thread for 2 reasons:
+ // 1. KeyguardViewMediator is not thread-safe.
+ // 2. To ensure that ViewMediatorCallback#keyguardDonePending is called before
+ // ViewMediatorCallback#readyForKeyguardDone. The wrong order could occur when doing
+ // dismissKeyguardThenExecute { hideKeyguardWithAnimation(runner) }.
+ mMainThreadHandler.post(() -> mKeyguardViewMediator.hideWithAnimation(runner));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index c0957c0..29bb1f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -26,6 +26,7 @@
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.KeyEvent;
@@ -768,9 +769,13 @@
private void wakeAndUnlockDejank() {
if (mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
&& LatencyTracker.isEnabled(mContext)) {
- DejankUtils.postAfterTraversal(() ->
+ BiometricSourceType type = mBiometricUnlockController.getBiometricType();
+ DejankUtils.postAfterTraversal(() -> {
LatencyTracker.getInstance(mContext).onActionEnd(
- LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK));
+ type == BiometricSourceType.FACE
+ ? LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK
+ : LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK);
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index b2ab307..14e513a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -28,8 +28,8 @@
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
- statusBar.onLaunchAnimationEnd(isExpandingFullyAbove)
statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(false)
+ statusBar.onLaunchAnimationEnd(isExpandingFullyAbove)
}
override fun onLaunchAnimationProgress(
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 f5dd195..7b7c17d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -282,15 +282,6 @@
boolean showOverLockscreen) {
mLogger.logHandleClickAfterKeyguardDismissed(entry.getKey());
- // TODO: Some of this code may be able to move to NotificationEntryManager.
- String key = row.getEntry().getSbn().getKey();
- if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
- // Release the HUN notification to the shade.
- if (mPresenter.isPresenterFullyCollapsed()) {
- HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
- }
- }
-
final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
entry, row, controller, intent,
isActivityIntent, animate);
@@ -337,7 +328,7 @@
// bypass work challenge
if (mStatusBarRemoteInputCallback.startWorkChallengeIfNecessary(userId,
intent.getIntentSender(), notificationKey)) {
- removeHUN(row);
+ removeHunAfterClick(row);
// Show work challenge, do not run PendingIntent and
// remove notification
collapseOnMainThread();
@@ -357,7 +348,7 @@
final boolean canBubble = entry.canBubble();
if (canBubble) {
mLogger.logExpandingBubble(notificationKey);
- removeHUN(row);
+ removeHunAfterClick(row);
expandBubbleStackOnMainThread(entry);
} else {
startNotificationIntent(intent, fillInIntent, entry, row, animate, isActivityIntent);
@@ -508,9 +499,14 @@
}, null, false /* afterKeyguardGone */);
}
- private void removeHUN(ExpandableNotificationRow row) {
+ private void removeHunAfterClick(ExpandableNotificationRow row) {
String key = row.getEntry().getSbn().getKey();
if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
+ // Release the HUN notification to the shade.
+ if (mPresenter.isPresenterFullyCollapsed()) {
+ HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(row, true);
+ }
+
// In most cases, when FLAG_AUTO_CANCEL is set, the notification will
// become canceled shortly by NoMan, but we can't assume that.
mHeadsUpManager.removeNotification(key, true /* releaseImmediately */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
index 1e3c123c..1212585 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
@@ -31,7 +31,7 @@
* @param view The view to be set the flag to.
* @param clicked True to set as clicked. False to not-clicked.
*/
- public static void setIsClickedHeadsUpNotification(View view, boolean clicked) {
+ public static void setNeedsHeadsUpDisappearAnimationAfterClick(View view, boolean clicked) {
view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index 02a07e4..de5a363 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -37,20 +37,16 @@
private final SparseArray<Boolean> mCallStrengthConfigs = new SparseArray<>();
private final SparseArray<Boolean> mNoCallingConfigs = new SparseArray<>();
private final CarrierConfigManager mCarrierConfigManager;
- private final boolean mDefaultCallStrengthConfig;
- private final boolean mDefaultNoCallingConfig;
+ private boolean mDefaultCallStrengthConfigLoaded;
+ private boolean mDefaultCallStrengthConfig;
+ private boolean mDefaultNoCallingConfigLoaded;
+ private boolean mDefaultNoCallingConfig;
@Inject
public CarrierConfigTracker(Context context) {
mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
context.registerReceiver(
this, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
- mDefaultCallStrengthConfig =
- CarrierConfigManager.getDefaultConfig().getBoolean(
- CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL);
- mDefaultNoCallingConfig =
- CarrierConfigManager.getDefaultConfig().getBoolean(
- CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL);
}
@Override
@@ -81,6 +77,12 @@
if (mCallStrengthConfigs.indexOfKey(subId) >= 0) {
return mCallStrengthConfigs.get(subId);
}
+ if (!mDefaultCallStrengthConfigLoaded) {
+ mDefaultCallStrengthConfig =
+ CarrierConfigManager.getDefaultConfig().getBoolean(
+ CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL);
+ mDefaultCallStrengthConfigLoaded = true;
+ }
return mDefaultCallStrengthConfig;
}
@@ -91,6 +93,12 @@
if (mNoCallingConfigs.indexOfKey(subId) >= 0) {
return mNoCallingConfigs.get(subId);
}
+ if (!mDefaultNoCallingConfigLoaded) {
+ mDefaultNoCallingConfig =
+ CarrierConfigManager.getDefaultConfig().getBoolean(
+ CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL);
+ mDefaultNoCallingConfigLoaded = true;
+ }
return mDefaultNoCallingConfig;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 236954b..2dcc43c 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -19,8 +19,8 @@
import static android.provider.Settings.ACTION_LOCKSCREEN_SETTINGS;
import android.content.Intent;
-import android.graphics.Color;
import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.Bundle;
import android.os.Handler;
import android.service.quickaccesswallet.QuickAccessWalletClient;
@@ -34,6 +34,9 @@
import androidx.annotation.NonNull;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -63,7 +66,10 @@
private final Handler mHandler;
private final FalsingManager mFalsingManager;
private final UserTracker mUserTracker;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final StatusBarKeyguardViewManager mKeyguardViewManager;
+
+ private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
private WalletScreenController mWalletScreenController;
private QuickAccessWalletClient mWalletClient;
private boolean mHasRegisteredListener;
@@ -77,6 +83,7 @@
@Main Handler handler,
FalsingManager falsingManager,
UserTracker userTracker,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
StatusBarKeyguardViewManager keyguardViewManager) {
mKeyguardStateController = keyguardStateController;
mKeyguardDismissUtil = keyguardDismissUtil;
@@ -85,6 +92,7 @@
mHandler = handler;
mFalsingManager = falsingManager;
mUserTracker = userTracker;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardViewManager = keyguardViewManager;
}
@@ -116,7 +124,17 @@
mHandler,
mUserTracker,
mFalsingManager,
+ mKeyguardUpdateMonitor,
mKeyguardStateController);
+ mKeyguardUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onBiometricRunningStateChanged(
+ boolean running,
+ BiometricSourceType biometricSourceType) {
+ Log.d(TAG, "Biometric running state has changed.");
+ mWalletScreenController.queryWalletCards();
+ }
+ };
walletView.getAppButton().setOnClickListener(
v -> {
@@ -146,7 +164,9 @@
// Click the action button to re-render the screen when the device is unlocked.
walletView.setDeviceLockedActionOnClickListener(
v -> {
+ Log.d(TAG, "Wallet action button is clicked.");
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ Log.d(TAG, "False tap detected on wallet action button.");
return;
}
@@ -164,13 +184,17 @@
mHasRegisteredListener = true;
}
mKeyguardStateController.addCallback(mWalletScreenController);
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
}
@Override
protected void onResume() {
super.onResume();
mWalletScreenController.queryWalletCards();
- mKeyguardViewManager.requestFp(true, Color.BLACK);
+ mKeyguardViewManager.requestFp(
+ true,
+ Utils.getColorAttrDefaultColor(
+ this, com.android.internal.R.attr.colorAccentPrimary));
mKeyguardViewManager.requestFace(true);
}
@@ -195,7 +219,6 @@
public void onWalletServiceEvent(WalletServiceEvent event) {
switch (event.getEventType()) {
case WalletServiceEvent.TYPE_NFC_PAYMENT_STARTED:
- finish();
break;
case WalletServiceEvent.TYPE_WALLET_CARDS_UPDATED:
mWalletScreenController.queryWalletCards();
@@ -224,6 +247,9 @@
@Override
protected void onDestroy() {
mKeyguardStateController.removeCallback(mWalletScreenController);
+ if (mKeyguardUpdateMonitorCallback != null) {
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
+ }
mWalletScreenController.onDismissed();
mWalletClient.removeWalletServiceEventListener(this);
mHasRegisteredListener = false;
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 96a3087..ab8ad77 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -39,6 +39,7 @@
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -66,6 +67,7 @@
private final ActivityStarter mActivityStarter;
private final Executor mExecutor;
private final Handler mHandler;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardStateController mKeyguardStateController;
private final Runnable mSelectionRunnable = this::selectCard;
private final SharedPreferences mPrefs;
@@ -85,6 +87,7 @@
Handler handler,
UserTracker userTracker,
FalsingManager falsingManager,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardStateController keyguardStateController) {
mContext = context;
mWalletClient = walletClient;
@@ -92,6 +95,7 @@
mExecutor = executor;
mHandler = handler;
mFalsingManager = falsingManager;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardStateController = keyguardStateController;
mPrefs = userTracker.getUserContext().getSharedPreferences(TAG, Context.MODE_PRIVATE);
mWalletView = walletView;
@@ -134,8 +138,13 @@
Log.w(TAG, "Invalid selected card index, showing empty state.");
showEmptyStateView();
} else {
+ boolean isUdfpsEnabled = mKeyguardUpdateMonitor.isUdfpsEnrolled()
+ && mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
mWalletView.showCardCarousel(
- data, selectedIndex, !mKeyguardStateController.isUnlocked());
+ data,
+ selectedIndex,
+ !mKeyguardStateController.isUnlocked(),
+ isUdfpsEnabled);
}
}
removeMinHeightAndRecordHeightOnLayout();
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
index 4f83abf..8412a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
@@ -63,6 +63,7 @@
private final ViewGroup mEmptyStateView;
private CharSequence mCenterCardText;
private boolean mIsDeviceLocked = false;
+ private boolean mIsUdfpsEnabled = false;
private OnClickListener mDeviceLockedActionOnClickListener;
public WalletView(Context context) {
@@ -108,7 +109,7 @@
mCardLabel.setText(centerCardText);
mIcon.setImageDrawable(centerCardIcon);
}
- renderActionButton(centerCard, mIsDeviceLocked);
+ renderActionButton(centerCard, mIsDeviceLocked, mIsUdfpsEnabled);
if (TextUtils.equals(centerCardText, getLabelText(nextCard))) {
mCardLabel.setAlpha(1f);
} else {
@@ -128,15 +129,19 @@
* @param isDeviceLocked indicates whether the device is locked.
*/
void showCardCarousel(
- List<WalletCardViewInfo> data, int selectedIndex, boolean isDeviceLocked) {
+ List<WalletCardViewInfo> data,
+ int selectedIndex,
+ boolean isDeviceLocked,
+ boolean isUdfpsEnabled) {
boolean shouldAnimate =
mCardCarousel.setData(data, selectedIndex, mIsDeviceLocked != isDeviceLocked);
mIsDeviceLocked = isDeviceLocked;
+ mIsUdfpsEnabled = isUdfpsEnabled;
mCardCarouselContainer.setVisibility(VISIBLE);
mErrorView.setVisibility(GONE);
mEmptyStateView.setVisibility(GONE);
mIcon.setImageDrawable(getHeaderIcon(mContext, data.get(selectedIndex)));
- renderActionButton(data.get(selectedIndex), isDeviceLocked);
+ renderActionButton(data.get(selectedIndex), isDeviceLocked, mIsUdfpsEnabled);
if (shouldAnimate) {
animateViewsShown(mIcon, mCardLabel, mActionButton);
}
@@ -240,13 +245,14 @@
return icon;
}
- private void renderActionButton(WalletCardViewInfo walletCard, boolean isDeviceLocked) {
+ private void renderActionButton(
+ WalletCardViewInfo walletCard, boolean isDeviceLocked, boolean isUdfpsEnabled) {
CharSequence actionButtonText = getActionButtonText(walletCard);
- if (isDeviceLocked) {
+ if (!isUdfpsEnabled && isDeviceLocked) {
mActionButton.setVisibility(VISIBLE);
mActionButton.setText(R.string.wallet_action_button_label_unlock);
mActionButton.setOnClickListener(mDeviceLockedActionOnClickListener);
- } else if (actionButtonText != null) {
+ } else if (!isDeviceLocked && actionButtonText != null) {
mActionButton.setText(actionButtonText);
mActionButton.setVisibility(VISIBLE);
mActionButton.setOnClickListener(v -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
index a435768..32b1f43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
@@ -16,7 +16,6 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.NextAlarmController
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
@@ -47,8 +46,6 @@
@Mock
private lateinit var qsLogger: QSLogger
@Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
private lateinit var userTracker: UserTracker
@Mock
private lateinit var nextAlarmController: NextAlarmController
@@ -79,7 +76,6 @@
statusBarStateController,
activityStarter,
qsLogger,
- featureFlags,
userTracker,
nextAlarmController
)
@@ -90,14 +86,7 @@
}
@Test
- fun testNotAvailableFeatureFlag() {
- `when`(featureFlags.isAlarmTileAvailable).thenReturn(false)
- assertThat(tile.isAvailable).isFalse()
- }
-
- @Test
- fun testAvailableFeatureFlag() {
- `when`(featureFlags.isAlarmTileAvailable).thenReturn(true)
+ fun testAvailable() {
assertThat(tile.isAvailable).isTrue()
}
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 09b0427..e4a9aac 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
@@ -69,7 +69,6 @@
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
@@ -118,8 +117,6 @@
private SecureSettings mSecureSettings;
@Mock
private QuickAccessWalletController mController;
- @Mock
- private FeatureFlags mFeatureFlags;
@Captor
ArgumentCaptor<Intent> mIntentCaptor;
@Captor
@@ -139,7 +136,6 @@
doNothing().when(mSpiedContext).startActivity(any(Intent.class));
when(mHost.getContext()).thenReturn(mSpiedContext);
when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
- when(mFeatureFlags.isQuickAccessWalletEnabled()).thenReturn(true);
when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL);
when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);
@@ -158,8 +154,7 @@
mKeyguardStateController,
mPackageManager,
mSecureSettings,
- mController,
- mFeatureFlags);
+ mController);
}
@Test
@@ -168,12 +163,6 @@
}
@Test
- public void testIsAvailable_featureFlagIsOff() {
- when(mFeatureFlags.isQuickAccessWalletEnabled()).thenReturn(false);
- assertFalse(mTile.isAvailable());
- }
-
- @Test
public void testWalletServiceUnavailable_recreateWalletClient() {
when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
new file mode 100644
index 0000000..c74437f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -0,0 +1,99 @@
+package com.android.systemui.statusbar.notification
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
+import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController
+import com.android.systemui.statusbar.policy.HeadsUpUtil
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
+ @Mock lateinit var notificationShadeWindowViewController: NotificationShadeWindowViewController
+ @Mock lateinit var notificationListContainer: NotificationListContainer
+ @Mock lateinit var headsUpManager: HeadsUpManagerPhone
+
+ private lateinit var notificationTestHelper: NotificationTestHelper
+ private lateinit var notification: ExpandableNotificationRow
+ private lateinit var controller: NotificationLaunchAnimatorController
+
+ private val notificationKey: String
+ get() = notification.entry.sbn.key
+
+ @get:Rule val rule = MockitoJUnit.rule()
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ notificationTestHelper =
+ NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ notification = notificationTestHelper.createRow()
+ controller = NotificationLaunchAnimatorController(
+ notificationShadeWindowViewController,
+ notificationListContainer,
+ headsUpManager,
+ notification
+ )
+ }
+
+ private fun flagNotificationAsHun() {
+ `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(true)
+ }
+
+ @Test
+ fun testHunIsRemovedIfWeDontAnimateLaunch() {
+ flagNotificationAsHun()
+ controller.onIntentStarted(willAnimate = false)
+
+ assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
+ assertFalse(notification.entry.isExpandAnimationRunning)
+ verify(headsUpManager).removeNotification(
+ notificationKey, true /* releaseImmediately */, true /* animate */)
+ }
+
+ @Test
+ fun testHunIsRemovedWhenAnimationIsCancelled() {
+ flagNotificationAsHun()
+ controller.onLaunchAnimationCancelled()
+
+ assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
+ assertFalse(notification.entry.isExpandAnimationRunning)
+ verify(headsUpManager).removeNotification(
+ notificationKey, true /* releaseImmediately */, true /* animate */)
+ }
+
+ @Test
+ fun testHunIsRemovedWhenAnimationEnds() {
+ flagNotificationAsHun()
+ controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+
+ assertFalse(HeadsUpUtil.isClickedHeadsUpNotification(notification))
+ assertFalse(notification.entry.isExpandAnimationRunning)
+ verify(headsUpManager).removeNotification(
+ notificationKey, true /* releaseImmediately */, false /* animate */)
+ }
+
+ @Test
+ fun testNotificationIsExpandingDuringAnimation() {
+ controller.onIntentStarted(willAnimate = true)
+
+ assertTrue(notification.entry.isExpandAnimationRunning)
+ }
+}
\ No newline at end of file
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 01623d6..e6c740b 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
@@ -34,7 +34,6 @@
import android.graphics.drawable.Icon;
import android.os.Handler;
import android.service.quickaccesswallet.GetWalletCardsError;
-import android.service.quickaccesswallet.GetWalletCardsRequest;
import android.service.quickaccesswallet.GetWalletCardsResponse;
import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.service.quickaccesswallet.QuickAccessWalletService;
@@ -44,6 +43,7 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -89,15 +89,13 @@
@Mock
FalsingManager mFalsingManager;
@Mock
+ KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
KeyguardStateController mKeyguardStateController;
@Captor
ArgumentCaptor<Intent> mIntentCaptor;
@Captor
- ArgumentCaptor<GetWalletCardsRequest> mRequestCaptor;
- @Captor
ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor;
- @Captor
- ArgumentCaptor<QuickAccessWalletClient.WalletServiceEventListener> mListenerCaptor;
private WalletScreenController mController;
private TestableLooper mTestableLooper;
@@ -114,6 +112,8 @@
when(mWalletClient.getServiceLabel()).thenReturn(SERVICE_LABEL);
when(mWalletClient.createWalletIntent()).thenReturn(mWalletIntent);
when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
mController = new WalletScreenController(
mContext,
mWalletView,
@@ -123,10 +123,61 @@
new Handler(mTestableLooper.getLooper()),
mUserTracker,
mFalsingManager,
+ mKeyguardUpdateMonitor,
mKeyguardStateController);
}
@Test
+ public void queryCards_deviceLocked_udfpsEnabled_hideUnlockButton() {
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+ GetWalletCardsResponse response =
+ new GetWalletCardsResponse(
+ Collections.singletonList(createWalletCard(mContext)), 0);
+
+ mController.queryWalletCards();
+ mTestableLooper.processAllMessages();
+
+ verify(mWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture());
+
+ QuickAccessWalletClient.OnWalletCardsRetrievedCallback callback =
+ mCallbackCaptor.getValue();
+
+ assertEquals(mController, callback);
+
+ callback.onWalletCardsRetrieved(response);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(VISIBLE, mWalletView.getCardCarouselContainer().getVisibility());
+ assertEquals(GONE, mWalletView.getActionButton().getVisibility());
+ }
+
+ @Test
+ public void queryCards_deviceLocked_udfpsNotEnabled_showUnlockButton() {
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+ GetWalletCardsResponse response =
+ new GetWalletCardsResponse(
+ Collections.singletonList(createWalletCard(mContext)), 0);
+
+ mController.queryWalletCards();
+ mTestableLooper.processAllMessages();
+
+ verify(mWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture());
+
+ QuickAccessWalletClient.OnWalletCardsRetrievedCallback callback =
+ mCallbackCaptor.getValue();
+
+ assertEquals(mController, callback);
+
+ callback.onWalletCardsRetrieved(response);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(VISIBLE, mWalletView.getCardCarouselContainer().getVisibility());
+ assertEquals(VISIBLE, mWalletView.getActionButton().getVisibility());
+ }
+
+ @Test
public void queryCards_hasCards_showCarousel_activeCard() {
GetWalletCardsResponse response =
new GetWalletCardsResponse(
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values/strings.xml
index a65fd43..49010da 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- [CHAR_LIMIT=NONE] Developer Settings: Label for the option that masks the display cutout, i.e. avoid apps in cutout region.-->
- <string name="display_cutout_emulation_overlay">Hide (avoid apps in cutout region)</string>
+ <string name="display_cutout_emulation_overlay">Render apps below cutout area</string>
</resources>
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 86b5287..c841fa3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5996,13 +5996,23 @@
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String callingPackage, String name, int userId,
boolean stable) {
- return mCpHelper.getContentProvider(caller, callingPackage, name, userId, stable);
+ traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "getContentProvider: ", name);
+ try {
+ return mCpHelper.getContentProvider(caller, callingPackage, name, userId, stable);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
}
@Override
public ContentProviderHolder getContentProviderExternal(
String name, int userId, IBinder token, String tag) {
- return mCpHelper.getContentProviderExternal(name, userId, token, tag);
+ traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "getContentProviderExternal: ", name);
+ try {
+ return mCpHelper.getContentProviderExternal(name, userId, token, tag);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
}
/**
@@ -6017,18 +6027,57 @@
@Deprecated
@Override
public void removeContentProviderExternal(String name, IBinder token) {
- removeContentProviderExternalAsUser(name, token, UserHandle.getCallingUserId());
+ traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "removeContentProviderExternal: ", name);
+ try {
+ removeContentProviderExternalAsUser(name, token, UserHandle.getCallingUserId());
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
}
@Override
public void removeContentProviderExternalAsUser(String name, IBinder token, int userId) {
- mCpHelper.removeContentProviderExternalAsUser(name, token, userId);
+ traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "removeContentProviderExternalAsUser: ", name);
+ try {
+ mCpHelper.removeContentProviderExternalAsUser(name, token, userId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
}
@Override
public final void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) {
- mCpHelper.publishContentProviders(caller, providers);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ final int maxLength = 256;
+ final StringBuilder sb = new StringBuilder(maxLength);
+ sb.append("publishContentProviders: ");
+ if (providers != null) {
+ boolean first = true;
+ for (int i = 0, size = providers.size(); i < size; i++) {
+ final ContentProviderHolder holder = providers.get(i);
+ if (holder != null && holder.info != null && holder.info.authority != null) {
+ final int len = holder.info.authority.length();
+ if (sb.length() + len > maxLength) {
+ sb.append("[[TRUNCATED]]");
+ break;
+ }
+ if (!first) {
+ sb.append(';');
+ } else {
+ first = false;
+ }
+ sb.append(holder.info.authority);
+ }
+ }
+ }
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
+ }
+ try {
+ mCpHelper.publishContentProviders(caller, providers);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
}
@Override
@@ -7842,7 +7891,9 @@
incrementalMetrics != null ? incrementalMetrics.getMillisSinceLastReadError()
: -1,
incrementalMetrics != null ? incrementalMetrics.getLastReadErrorNumber()
- : 0
+ : 0,
+ incrementalMetrics != null ? incrementalMetrics.getTotalDelayedReadsDurationMillis()
+ : -1
);
final int relaunchReason = r == null ? RELAUNCH_REASON_NONE
@@ -16176,6 +16227,14 @@
public PendingIntent getPendingIntentActivityAsApp(
int requestCode, @NonNull Intent intent, int flags, Bundle options,
String ownerPkg, int ownerUid) {
+ return getPendingIntentActivityAsApp(requestCode, new Intent[] { intent }, flags,
+ options, ownerPkg, ownerUid);
+ }
+
+ @Override
+ public PendingIntent getPendingIntentActivityAsApp(
+ int requestCode, @NonNull Intent[] intents, int flags, Bundle options,
+ String ownerPkg, int ownerUid) {
// system callers must explicitly set mutability state
final boolean flagImmutableSet = (flags & PendingIntent.FLAG_IMMUTABLE) != 0;
final boolean flagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0;
@@ -16185,15 +16244,21 @@
}
final Context context = ActivityManagerService.this.mContext;
- String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
- intent.migrateExtraStreamToClipData(context);
- intent.prepareToLeaveProcess(context);
+ final ContentResolver resolver = context.getContentResolver();
+ final int len = intents.length;
+ final String[] resolvedTypes = new String[len];
+ for (int i = 0; i < len; i++) {
+ final Intent intent = intents[i];
+ resolvedTypes[i] = intent.resolveTypeIfNeeded(resolver);
+ intent.migrateExtraStreamToClipData(context);
+ intent.prepareToLeaveProcess(context);
+ }
IIntentSender target =
ActivityManagerService.this.getIntentSenderWithFeatureAsApp(
INTENT_SENDER_ACTIVITY, ownerPkg,
context.getAttributionTag(), null, null, requestCode,
- new Intent[] { intent },
- resolvedType != null ? new String[] { resolvedType } : null,
+ intents,
+ resolvedTypes,
flags, options, UserHandle.getUserId(ownerUid), ownerUid);
return target != null ? new PendingIntent(target) : null;
}
@@ -17092,4 +17157,10 @@
SystemClock.sleep(durationMs);
}
}
+
+ static void traceBegin(long traceTag, String methodName, String subInfo) {
+ if (Trace.isTagEnabled(traceTag)) {
+ Trace.traceBegin(traceTag, methodName + subInfo);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index ab1da80..1611395 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -58,6 +58,7 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -707,20 +708,28 @@
mService.enforceNotIsolatedCaller("removeContentProvider");
final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mService) {
- ContentProviderConnection conn;
- try {
- conn = (ContentProviderConnection) connection;
- } catch (ClassCastException e) {
- String msg = "removeContentProvider: " + connection
- + " not a ContentProviderConnection";
- Slog.w(TAG, msg);
- throw new IllegalArgumentException(msg);
+ ContentProviderConnection conn;
+ try {
+ conn = (ContentProviderConnection) connection;
+ } catch (ClassCastException e) {
+ String msg = "removeContentProvider: " + connection
+ + " not a ContentProviderConnection";
+ Slog.w(TAG, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (conn == null) {
+ throw new NullPointerException("connection is null");
+ }
+ ActivityManagerService.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "removeContentProvider: ",
+ (conn.provider != null && conn.provider.info != null
+ ? conn.provider.info.authority : ""));
+ try {
+ synchronized (mService) {
+ decProviderCountLocked(conn, null, null, stable, true, true);
}
- if (conn == null) {
- throw new NullPointerException("connection is null");
- }
- decProviderCountLocked(conn, null, null, stable, true, true);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -781,8 +790,15 @@
throw new NullPointerException("connection is null");
}
- conn.adjustCounts(stable, unstable);
- return !conn.dead;
+ ActivityManagerService.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "refContentProvider: ",
+ (conn.provider != null && conn.provider.info != null
+ ? conn.provider.info.authority : ""));
+ try {
+ conn.adjustCounts(stable, unstable);
+ return !conn.dead;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
}
void unstableProviderDied(IBinder connection) {
@@ -798,50 +814,60 @@
throw new NullPointerException("connection is null");
}
- // Safely retrieve the content provider associated with the connection.
- IContentProvider provider;
- synchronized (mService) {
- provider = conn.provider.provider;
- }
+ ActivityManagerService.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "unstableProviderDied: ",
+ (conn.provider != null && conn.provider.info != null
+ ? conn.provider.info.authority : ""));
- if (provider == null) {
- // Um, yeah, we're way ahead of you.
- return;
- }
-
- // Make sure the caller is being honest with us.
- if (provider.asBinder().pingBinder()) {
- // Er, no, still looks good to us.
+ try {
+ // Safely retrieve the content provider associated with the connection.
+ IContentProvider provider;
synchronized (mService) {
- Slog.w(TAG, "unstableProviderDied: caller " + Binder.getCallingUid()
- + " says " + conn + " died, but we don't agree");
- return;
+ provider = conn.provider.provider;
}
- }
- // Well look at that! It's dead!
- synchronized (mService) {
- if (conn.provider.provider != provider) {
- // But something changed... good enough.
+ if (provider == null) {
+ // Um, yeah, we're way ahead of you.
return;
}
- ProcessRecord proc = conn.provider.proc;
- if (proc == null || proc.getThread() == null) {
- // Seems like the process is already cleaned up.
- return;
+ // Make sure the caller is being honest with us.
+ if (provider.asBinder().pingBinder()) {
+ // Er, no, still looks good to us.
+ synchronized (mService) {
+ Slog.w(TAG, "unstableProviderDied: caller " + Binder.getCallingUid()
+ + " says " + conn + " died, but we don't agree");
+ return;
+ }
}
- // As far as we're concerned, this is just like receiving a
- // death notification... just a bit prematurely.
- mService.reportUidInfoMessageLocked(TAG, "Process " + proc.processName
- + " (pid " + proc.getPid() + ") early provider death", proc.info.uid);
- final long token = Binder.clearCallingIdentity();
- try {
- mService.appDiedLocked(proc, "unstable content provider");
- } finally {
- Binder.restoreCallingIdentity(token);
+ // Well look at that! It's dead!
+ synchronized (mService) {
+ if (conn.provider.provider != provider) {
+ // But something changed... good enough.
+ return;
+ }
+
+ ProcessRecord proc = conn.provider.proc;
+ if (proc == null || proc.getThread() == null) {
+ // Seems like the process is already cleaned up.
+ return;
+ }
+
+ // As far as we're concerned, this is just like receiving a
+ // death notification... just a bit prematurely.
+ mService.reportUidInfoMessageLocked(TAG, "Process " + proc.processName
+ + " (pid " + proc.getPid() + ") early provider death",
+ proc.info.uid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mService.appDiedLocked(proc, "unstable content provider");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -855,13 +881,21 @@
return;
}
- final ProcessRecord host = conn.provider.proc;
- if (host == null) {
- Slog.w(TAG, "Failed to find hosting ProcessRecord");
- return;
- }
+ ActivityManagerService.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "appNotRespondingViaProvider: ",
+ (conn.provider != null && conn.provider.info != null
+ ? conn.provider.info.authority : ""));
+ try {
+ final ProcessRecord host = conn.provider.proc;
+ if (host == null) {
+ Slog.w(TAG, "Failed to find hosting ProcessRecord");
+ return;
+ }
- mService.mAnrHelper.appNotResponding(host, "ContentProvider not responding");
+ mService.mAnrHelper.appNotResponding(host, "ContentProvider not responding");
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 508bd06..b2266f6 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -470,7 +470,9 @@
incrementalMetrics != null ? incrementalMetrics.getMillisSinceLastReadError()
: -1,
incrementalMetrics != null ? incrementalMetrics.getLastReadErrorNumber()
- : 0);
+ : 0,
+ incrementalMetrics != null ? incrementalMetrics.getTotalDelayedReadsDurationMillis()
+ : -1);
final ProcessRecord parentPr = parentProcess != null
? (ProcessRecord) parentProcess.mOwner : null;
mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName,
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 96db1ad..75367d2 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -647,9 +647,9 @@
final CompatibilityOverrideConfig changeConfig = new CompatibilityOverrideConfig(
overrides);
try {
- mPlatformCompat.setOverridesOnReleaseBuilds(changeConfig, packageName);
+ mPlatformCompat.putOverridesOnReleaseBuilds(changeConfig, packageName);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to call IPlatformCompat#setOverridesOnReleaseBuilds", e);
+ Slog.e(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e);
}
} finally {
Binder.restoreCallingIdentity(uid);
@@ -671,9 +671,9 @@
final CompatibilityOverrideConfig changeConfig = new CompatibilityOverrideConfig(
overrides);
try {
- mPlatformCompat.setOverridesOnReleaseBuilds(changeConfig, packageName);
+ mPlatformCompat.putOverridesOnReleaseBuilds(changeConfig, packageName);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to call IPlatformCompat#setOverridesOnReleaseBuilds", e);
+ Slog.e(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e);
}
} finally {
Binder.restoreCallingIdentity(uid);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 2a1c055..55128be 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -4502,8 +4502,12 @@
Slog.i("AppOpsDebug", "tag " + attributionTag + " found in "
+ packageName);
} else {
+ ArrayList<String> tagList = new ArrayList<>();
+ for (int i = 0; i < pkg.getAttributions().size(); i++) {
+ tagList.add(pkg.getAttributions().get(i).tag);
+ }
Slog.i("AppOpsDebug", "tag " + attributionTag + " missing from "
- + packageName);
+ + packageName + ", tags: " + tagList);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 125cfd2..edd30bc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -27,6 +27,7 @@
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -46,6 +47,7 @@
private static final String TAG = "FingerprintEnrollClient";
+ @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
@Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private final ISidefpsController mSidefpsController;
@@ -58,12 +60,15 @@
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
@NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull FingerprintSensorPropertiesInternal sensorProps,
@Nullable IUdfpsOverlayController udfpsOvelayController,
@Nullable ISidefpsController sidefpsController,
int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
+ // UDFPS enroll vibrations are handled in SystemUI
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
- true /* shouldVibrate */);
+ !sensorProps.isAnyUdfpsType() /* shouldVibrate */);
+ mSensorProps = sensorProps;
mUdfpsOverlayController = udfpsOvelayController;
mSidefpsController = sidefpsController;
mMaxTemplatesPerUser = maxTemplatesPerUser;
@@ -90,7 +95,8 @@
public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
// For UDFPS, notify SysUI that the illumination can be turned off.
// See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE
- if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) {
+ if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD
+ && mSensorProps.isAnyUdfpsType()) {
UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 293b9e4..096c311 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -340,6 +340,7 @@
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+ mSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index eba53fb..2cd68b0 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -207,15 +207,6 @@
}
@Override
- public void setOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides,
- String packageName) {
- // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods.
- checkCompatChangeOverrideOverridablePermission();
- checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
- mCompatConfig.addOverrides(overrides, packageName);
- }
-
- @Override
public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
checkCompatChangeOverridePermission();
Map<Long, PackageOverride> overridesMap = new HashMap<>();
@@ -230,6 +221,15 @@
}
@Override
+ public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides,
+ String packageName) {
+ // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods.
+ checkCompatChangeOverrideOverridablePermission();
+ checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
+ mCompatConfig.addOverrides(overrides, packageName);
+ }
+
+ @Override
public int enableTargetSdkChanges(String packageName, int targetSdkVersion) {
checkCompatChangeOverridePermission();
int numChanges =
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index edc9d7c..204ebfc 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -47,7 +47,7 @@
mUniqueId = componentName.flattenToShortString();
}
- public void setCallback(MediaRoute2ProviderServiceProxy.Callback callback) {
+ public void setCallback(Callback callback) {
mCallback = callback;
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index ab38dca..21f61ca 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -16,6 +16,10 @@
package com.android.server.media;
+import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
@@ -43,6 +47,7 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -64,6 +69,7 @@
private Connection mActiveConnection;
private boolean mConnectionReady;
+ private boolean mIsManagerScanning;
private RouteDiscoveryPreference mLastDiscoveryPreference = null;
@GuardedBy("mLock")
@@ -86,6 +92,13 @@
pw.println(prefix + " mConnectionReady=" + mConnectionReady);
}
+ public void setManagerScanning(boolean managerScanning) {
+ if (mIsManagerScanning != managerScanning) {
+ mIsManagerScanning = managerScanning;
+ updateBinding();
+ }
+ }
+
@Override
public void requestCreateSession(long requestId, String packageName, String routeId,
Bundle sessionHints) {
@@ -209,7 +222,8 @@
// Bind when there is a discovery preference or an active route session.
return (mLastDiscoveryPreference != null
&& !mLastDiscoveryPreference.getPreferredFeatures().isEmpty())
- || !getSessionInfos().isEmpty();
+ || !getSessionInfos().isEmpty()
+ || mIsManagerScanning;
}
return false;
}
@@ -311,13 +325,12 @@
}
}
- private void onProviderStateUpdated(Connection connection,
- MediaRoute2ProviderInfo providerInfo) {
+ private void onProviderUpdated(Connection connection, MediaRoute2ProviderInfo providerInfo) {
if (mActiveConnection != connection) {
return;
}
if (DEBUG) {
- Slog.d(TAG, this + ": State changed ");
+ Slog.d(TAG, this + ": updated");
}
setAndNotifyProviderState(providerInfo);
}
@@ -350,40 +363,44 @@
mCallback.onSessionCreated(this, requestId, newSession);
}
- private void onSessionUpdated(Connection connection, RoutingSessionInfo updatedSession) {
+ private int findSessionByIdLocked(RoutingSessionInfo session) {
+ for (int i = 0; i < mSessionInfos.size(); i++) {
+ if (TextUtils.equals(mSessionInfos.get(i).getId(), session.getId())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+
+ private void onSessionsUpdated(Connection connection, List<RoutingSessionInfo> sessions) {
if (mActiveConnection != connection) {
return;
}
- if (updatedSession == null) {
- Slog.w(TAG, "onSessionUpdated: Ignoring null session sent from "
- + mComponentName);
- return;
- }
- updatedSession = assignProviderIdForSession(updatedSession);
-
- boolean found = false;
+ int targetIndex = 0;
synchronized (mLock) {
- for (int i = 0; i < mSessionInfos.size(); i++) {
- if (mSessionInfos.get(i).getId().equals(updatedSession.getId())) {
- mSessionInfos.set(i, updatedSession);
- found = true;
- break;
+ for (RoutingSessionInfo session : sessions) {
+ if (session == null) continue;
+ session = assignProviderIdForSession(session);
+
+ int sourceIndex = findSessionByIdLocked(session);
+ if (sourceIndex < 0) {
+ mSessionInfos.add(targetIndex++, session);
+ dispatchSessionCreated(REQUEST_ID_NONE, session);
+ } else if (sourceIndex < targetIndex) {
+ Slog.w(TAG, "Ignoring duplicate session ID: " + session.getId());
+ } else {
+ mSessionInfos.set(sourceIndex, session);
+ Collections.swap(mSessionInfos, sourceIndex, targetIndex++);
+ dispatchSessionUpdated(session);
}
}
-
- if (!found) {
- for (RoutingSessionInfo releasingSession : mReleasingSessions) {
- if (TextUtils.equals(releasingSession.getId(), updatedSession.getId())) {
- return;
- }
- }
- Slog.w(TAG, "onSessionUpdated: Matching session info not found");
- return;
+ for (int i = mSessionInfos.size() - 1; i >= targetIndex; i--) {
+ RoutingSessionInfo releasedSession = mSessionInfos.remove(i);
+ dispatchSessionReleased(releasedSession);
}
}
-
- mCallback.onSessionUpdated(this, updatedSession);
}
private void onSessionReleased(Connection connection, RoutingSessionInfo releaedSession) {
@@ -424,6 +441,21 @@
mCallback.onSessionReleased(this, releaedSession);
}
+ private void dispatchSessionCreated(long requestId, RoutingSessionInfo session) {
+ mHandler.sendMessage(
+ obtainMessage(mCallback::onSessionCreated, this, requestId, session));
+ }
+
+ private void dispatchSessionUpdated(RoutingSessionInfo session) {
+ mHandler.sendMessage(
+ obtainMessage(mCallback::onSessionUpdated, this, session));
+ }
+
+ private void dispatchSessionReleased(RoutingSessionInfo session) {
+ mHandler.sendMessage(
+ obtainMessage(mCallback::onSessionReleased, this, session));
+ }
+
private RoutingSessionInfo assignProviderIdForSession(RoutingSessionInfo sessionInfo) {
return new RoutingSessionInfo.Builder(sessionInfo)
.setOwnerPackageName(mComponentName.getPackageName())
@@ -436,7 +468,7 @@
return;
}
- if (requestId == MediaRoute2ProviderService.REQUEST_ID_NONE) {
+ if (requestId == REQUEST_ID_NONE) {
Slog.w(TAG, "onRequestFailed: Ignoring requestId REQUEST_ID_NONE");
return;
}
@@ -561,16 +593,16 @@
mHandler.post(() -> onConnectionDied(Connection.this));
}
- void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo) {
- mHandler.post(() -> onProviderStateUpdated(Connection.this, providerInfo));
+ void postProviderUpdated(MediaRoute2ProviderInfo providerInfo) {
+ mHandler.post(() -> onProviderUpdated(Connection.this, providerInfo));
}
void postSessionCreated(long requestId, RoutingSessionInfo sessionInfo) {
mHandler.post(() -> onSessionCreated(Connection.this, requestId, sessionInfo));
}
- void postSessionUpdated(RoutingSessionInfo sessionInfo) {
- mHandler.post(() -> onSessionUpdated(Connection.this, sessionInfo));
+ void postSessionsUpdated(List<RoutingSessionInfo> sessionInfo) {
+ mHandler.post(() -> onSessionsUpdated(Connection.this, sessionInfo));
}
void postSessionReleased(RoutingSessionInfo sessionInfo) {
@@ -595,10 +627,10 @@
}
@Override
- public void updateState(MediaRoute2ProviderInfo providerInfo) {
+ public void notifyProviderUpdated(MediaRoute2ProviderInfo providerInfo) {
Connection connection = mConnectionRef.get();
if (connection != null) {
- connection.postProviderStateUpdated(providerInfo);
+ connection.postProviderUpdated(providerInfo);
}
}
@@ -611,10 +643,10 @@
}
@Override
- public void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
+ public void notifySessionsUpdated(List<RoutingSessionInfo> sessionInfo) {
Connection connection = mConnectionRef.get();
if (connection != null) {
- connection.postSessionUpdated(sessionInfo);
+ connection.postSessionsUpdated(sessionInfo);
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 1dbc8a9..168ca55 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2156,6 +2156,8 @@
List<RouterRecord> routerRecords = getRouterRecords();
List<ManagerRecord> managerRecords = getManagerRecords();
+ boolean shouldBindProviders = false;
+
if (service.mPowerManager.isInteractive()) {
boolean isManagerScanning = managerRecords.stream().anyMatch(manager ->
manager.mIsScanning && service.mActivityManager
@@ -2166,6 +2168,7 @@
discoveryPreferences = routerRecords.stream()
.map(record -> record.mDiscoveryPreference)
.collect(Collectors.toList());
+ shouldBindProviders = true;
} else {
discoveryPreferences = routerRecords.stream().filter(record ->
service.mActivityManager.getPackageImportance(record.mPackageName)
@@ -2175,6 +2178,13 @@
}
}
+ for (MediaRoute2Provider provider : mRouteProviders) {
+ if (provider instanceof MediaRoute2ProviderServiceProxy) {
+ ((MediaRoute2ProviderServiceProxy) provider)
+ .setManagerScanning(shouldBindProviders);
+ }
+ }
+
synchronized (service.mLock) {
RouteDiscoveryPreference newPreference =
new RouteDiscoveryPreference.Builder(discoveryPreferences).build();
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index b135e88..9370b14 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -698,14 +698,13 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- return injectCreatePendingIntent(mContext.createPackageContextAsUser(packageName,
- 0, user), 0 /* requestCode */, intents, FLAG_MUTABLE, opts, user);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Cannot create pending intent from shortcut " + shortcutId, e);
+ return injectCreatePendingIntent(0 /* requestCode */, intents,
+ FLAG_MUTABLE, opts, packageName, mPackageManagerInternal.getPackageUid(
+ packageName, PackageManager.MATCH_DIRECT_BOOT_AUTO,
+ user.getIdentifier()));
} finally {
Binder.restoreCallingIdentity(ident);
}
- return null;
}
@Override
@@ -812,10 +811,10 @@
}
@VisibleForTesting
- PendingIntent injectCreatePendingIntent(Context context, int requestCode,
- @NonNull Intent[] intents, int flags, Bundle options, UserHandle user) {
- return PendingIntent.getActivitiesAsUser(context, requestCode, intents, flags, options,
- user);
+ PendingIntent injectCreatePendingIntent(int requestCode, @NonNull Intent[] intents,
+ int flags, Bundle options, String ownerPackage, int ownerUserId) {
+ return mActivityManagerInternal.getPendingIntentActivityAsApp(requestCode, intents,
+ flags, options, ownerPackage, ownerUserId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 1f0a8ca..0b63b7d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -653,13 +653,20 @@
}
if (params.isStaged && !isCalledBySystemOrShell(callingUid)) {
- if (mBypassNextStagedInstallerCheck) {
- mBypassNextStagedInstallerCheck = false;
- } else if (!isStagedInstallerAllowed(requestedInstallerPackageName)) {
+ if (!mBypassNextStagedInstallerCheck
+ && !isStagedInstallerAllowed(requestedInstallerPackageName)) {
throw new SecurityException("Installer not allowed to commit staged install");
}
}
+ if (isApex && !isCalledBySystemOrShell(callingUid)) {
+ if (!mBypassNextStagedInstallerCheck
+ && !isStagedInstallerAllowed(requestedInstallerPackageName)) {
+ throw new SecurityException(
+ "Installer not allowed to commit non-staged APEX install");
+ }
+ }
+ mBypassNextStagedInstallerCheck = false;
if (!params.isMultiPackage) {
// Only system components can circumvent runtime permissions when installing.
if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f00b3a0..c812fc8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2335,11 +2335,11 @@
List<CrossProfileIntentFilter> matchingFilters =
getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
// Check for results that need to skip the current profile.
- ResolveInfo xpResolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent,
- resolvedType, flags, userId);
- if (xpResolveInfo != null) {
+ ResolveInfo skipProfileInfo = querySkipCurrentProfileIntents(matchingFilters,
+ intent, resolvedType, flags, userId);
+ if (skipProfileInfo != null) {
List<ResolveInfo> xpResult = new ArrayList<>(1);
- xpResult.add(xpResolveInfo);
+ xpResult.add(skipProfileInfo);
return new QueryIntentActivitiesResult(
applyPostResolutionFilter(
filterIfNotSystemUser(xpResult, userId), instantAppPkgName,
@@ -2354,54 +2354,55 @@
false /*skipPackageCheck*/, flags);
// Check for cross profile results.
boolean hasNonNegativePriorityResult = hasNonNegativePriority(result);
- xpResolveInfo = queryCrossProfileIntents(
+ CrossProfileDomainInfo specificXpInfo = queryCrossProfileIntents(
matchingFilters, intent, resolvedType, flags, userId,
hasNonNegativePriorityResult);
- if (xpResolveInfo != null && isUserEnabled(xpResolveInfo.targetUserId)) {
- boolean isVisibleToUser = filterIfNotSystemUser(
- Collections.singletonList(xpResolveInfo), userId).size() > 0;
- if (isVisibleToUser) {
- result.add(xpResolveInfo);
- sortResult = true;
- }
- }
if (intent.hasWebURI()) {
- CrossProfileDomainInfo xpDomainInfo = null;
+ CrossProfileDomainInfo generalXpInfo = null;
final UserInfo parent = getProfileParent(userId);
if (parent != null) {
- xpDomainInfo = getCrossProfileDomainPreferredLpr(intent, resolvedType,
+ generalXpInfo = getCrossProfileDomainPreferredLpr(intent, resolvedType,
flags, userId, parent.id);
}
- if (xpDomainInfo != null) {
- if (xpResolveInfo != null) {
- // If we didn't remove it, the cross-profile ResolveInfo would be twice
- // in the result.
- result.remove(xpResolveInfo);
- }
- if (result.size() == 0 && !addInstant) {
+
+ // Generalized cross profile intents take precedence over specific.
+ // Note that this is the opposite of the intuitive order.
+ CrossProfileDomainInfo prioritizedXpInfo =
+ generalXpInfo != null ? generalXpInfo : specificXpInfo;
+
+ if (!addInstant) {
+ if (result.isEmpty() && prioritizedXpInfo != null) {
// No result in current profile, but found candidate in parent user.
// And we are not going to add ephemeral app, so we can return the
// result straight away.
- result.add(xpDomainInfo.resolveInfo);
+ result.add(prioritizedXpInfo.resolveInfo);
+ return new QueryIntentActivitiesResult(
+ applyPostResolutionFilter(result, instantAppPkgName,
+ allowDynamicSplits, filterCallingUid, resolveForStart,
+ userId, intent));
+ } else if (result.size() <= 1 && prioritizedXpInfo == null) {
+ // No result in parent user and <= 1 result in current profile, and we
+ // are not going to add ephemeral app, so we can return the result
+ // without further processing.
return new QueryIntentActivitiesResult(
applyPostResolutionFilter(result, instantAppPkgName,
allowDynamicSplits, filterCallingUid, resolveForStart,
userId, intent));
}
- } else if (result.size() <= 1 && !addInstant) {
- // No result in parent user and <= 1 result in current profile, and we
- // are not going to add ephemeral app, so we can return the result without
- // further processing.
- return new QueryIntentActivitiesResult(
- applyPostResolutionFilter(result, instantAppPkgName,
- allowDynamicSplits, filterCallingUid, resolveForStart, userId,
- intent));
}
+
// We have more than one candidate (combining results from current and parent
// profile), so we need filtering and sorting.
result = filterCandidatesWithDomainPreferredActivitiesLPr(
- intent, flags, result, xpDomainInfo, userId);
+ intent, flags, result, prioritizedXpInfo, userId);
sortResult = true;
+ } else {
+ // If not web Intent, just add result to candidate set and let ResolverActivity
+ // figure it out.
+ if (specificXpInfo != null) {
+ result.add(specificXpInfo.resolveInfo);
+ sortResult = true;
+ }
}
} else {
final PackageSetting setting =
@@ -2833,15 +2834,17 @@
if (ps == null) {
continue;
}
- if (result == null) {
- result = new CrossProfileDomainInfo();
- result.resolveInfo = createForwardingResolveInfoUnchecked(
- new WatchedIntentFilter(), sourceUserId, parentUserId);
- }
- result.highestApprovalLevel = Math.max(mDomainVerificationManager
- .approvalLevelForDomain(ps, intent, resultTargetUser, flags,
- parentUserId), result.highestApprovalLevel);
+ int approvalLevel = mDomainVerificationManager
+ .approvalLevelForDomain(ps, intent, flags, parentUserId);
+
+ if (result == null) {
+ result = new CrossProfileDomainInfo(createForwardingResolveInfoUnchecked(
+ new WatchedIntentFilter(), sourceUserId, parentUserId), approvalLevel);
+ } else {
+ result.highestApprovalLevel =
+ Math.max(approvalLevel, result.highestApprovalLevel);
+ }
}
if (result != null && result.highestApprovalLevel
<= DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
@@ -3088,8 +3091,8 @@
final String packageName = info.activityInfo.packageName;
final PackageSetting ps = mSettings.getPackageLPr(packageName);
if (ps.getInstantApp(userId)) {
- if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent,
- instantApps, flags, userId)) {
+ if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, flags,
+ userId)) {
if (DEBUG_INSTANT) {
Slog.v(TAG, "Instant app approved for intent; pkg: "
+ packageName);
@@ -3416,28 +3419,59 @@
}
/**
- * If the filter's target user can handle the intent and is enabled: returns a ResolveInfo
- * that
- * will forward the intent to the filter's target user.
- * Otherwise, returns null.
+ * If the filter's target user can handle the intent and is enabled: a [ResolveInfo] that
+ * will forward the intent to the filter's target user, along with the highest approval of
+ * any handler in the target user. Otherwise, returns null.
*/
- private ResolveInfo createForwardingResolveInfo(CrossProfileIntentFilter filter,
- Intent intent,
- String resolvedType, int flags, int sourceUserId) {
+ @Nullable
+ private CrossProfileDomainInfo createForwardingResolveInfo(
+ @NonNull CrossProfileIntentFilter filter, @NonNull Intent intent,
+ @Nullable String resolvedType, int flags, int sourceUserId) {
int targetUserId = filter.getTargetUserId();
+ if (!isUserEnabled(targetUserId)) {
+ return null;
+ }
+
List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent,
resolvedType, flags, targetUserId);
- if (resultTargetUser != null && isUserEnabled(targetUserId)) {
- // If all the matches in the target profile are suspended, return null.
- for (int i = resultTargetUser.size() - 1; i >= 0; i--) {
- if ((resultTargetUser.get(i).activityInfo.applicationInfo.flags
- & ApplicationInfo.FLAG_SUSPENDED) == 0) {
- return createForwardingResolveInfoUnchecked(filter,
- sourceUserId, targetUserId);
- }
+ if (CollectionUtils.isEmpty(resultTargetUser)) {
+ return null;
+ }
+
+ ResolveInfo forwardingInfo = null;
+ for (int i = resultTargetUser.size() - 1; i >= 0; i--) {
+ ResolveInfo targetUserResolveInfo = resultTargetUser.get(i);
+ if ((targetUserResolveInfo.activityInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_SUSPENDED) == 0) {
+ forwardingInfo = createForwardingResolveInfoUnchecked(filter, sourceUserId,
+ targetUserId);
+ break;
}
}
- return null;
+
+ if (forwardingInfo == null) {
+ // If all the matches in the target profile are suspended, return null.
+ return null;
+ }
+
+ int highestApprovalLevel = DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
+
+ int size = resultTargetUser.size();
+ for (int i = 0; i < size; i++) {
+ ResolveInfo riTargetUser = resultTargetUser.get(i);
+ if (riTargetUser.handleAllWebDataURI) {
+ continue;
+ }
+ String packageName = riTargetUser.activityInfo.packageName;
+ PackageSetting ps = mSettings.getPackageLPr(packageName);
+ if (ps == null) {
+ continue;
+ }
+ highestApprovalLevel = Math.max(highestApprovalLevel, mDomainVerificationManager
+ .approvalLevelForDomain(ps, intent, flags, targetUserId));
+ }
+
+ return new CrossProfileDomainInfo(forwardingInfo, highestApprovalLevel);
}
public ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter,
@@ -3476,34 +3510,59 @@
}
// Return matching ResolveInfo in target user if any.
- private ResolveInfo queryCrossProfileIntents(
+ @Nullable
+ private CrossProfileDomainInfo queryCrossProfileIntents(
List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
int flags, int sourceUserId, boolean matchInCurrentProfile) {
- if (matchingFilters != null) {
- // Two {@link CrossProfileIntentFilter}s can have the same targetUserId and
- // match the same intent. For performance reasons, it is better not to
- // run queryIntent twice for the same userId
- SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray();
- int size = matchingFilters.size();
- for (int i = 0; i < size; i++) {
- CrossProfileIntentFilter filter = matchingFilters.get(i);
- int targetUserId = filter.getTargetUserId();
- boolean skipCurrentProfile =
- (filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0;
- boolean skipCurrentProfileIfNoMatchFound =
- (filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0;
- if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId)
- && (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) {
- // Checking if there are activities in the target user that can handle the
- // intent.
- ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent,
- resolvedType, flags, sourceUserId);
- if (resolveInfo != null) return resolveInfo;
- alreadyTriedUserIds.put(targetUserId, true);
+ if (matchingFilters == null) {
+ return null;
+ }
+ // Two {@link CrossProfileIntentFilter}s can have the same targetUserId and
+ // match the same intent. For performance reasons, it is better not to
+ // run queryIntent twice for the same userId
+ SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray();
+
+ CrossProfileDomainInfo resultInfo = null;
+
+ int size = matchingFilters.size();
+ for (int i = 0; i < size; i++) {
+ CrossProfileIntentFilter filter = matchingFilters.get(i);
+ int targetUserId = filter.getTargetUserId();
+ boolean skipCurrentProfile =
+ (filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0;
+ boolean skipCurrentProfileIfNoMatchFound =
+ (filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0;
+ if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId)
+ && (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) {
+ // Checking if there are activities in the target user that can handle the
+ // intent.
+ CrossProfileDomainInfo info = createForwardingResolveInfo(filter, intent,
+ resolvedType, flags, sourceUserId);
+ if (info != null) {
+ resultInfo = info;
+ break;
}
+ alreadyTriedUserIds.put(targetUserId, true);
}
}
- return null;
+
+ if (resultInfo == null) {
+ return null;
+ }
+
+ ResolveInfo forwardingResolveInfo = resultInfo.resolveInfo;
+ if (!isUserEnabled(forwardingResolveInfo.targetUserId)) {
+ return null;
+ }
+
+ List<ResolveInfo> filteredResult =
+ filterIfNotSystemUser(Collections.singletonList(forwardingResolveInfo),
+ sourceUserId);
+ if (filteredResult.isEmpty()) {
+ return null;
+ }
+
+ return resultInfo;
}
private ResolveInfo querySkipCurrentProfileIntents(
@@ -3516,10 +3575,10 @@
if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) {
// Checking if there are activities in the target user that can handle the
// intent.
- ResolveInfo resolveInfo = createForwardingResolveInfo(filter, intent,
+ CrossProfileDomainInfo info = createForwardingResolveInfo(filter, intent,
resolvedType, flags, sourceUserId);
- if (resolveInfo != null) {
- return resolveInfo;
+ if (info != null) {
+ return info.resolveInfo;
}
}
}
@@ -4029,8 +4088,8 @@
if (ps != null) {
// only check domain verification status if the app is not a browser
if (!info.handleAllWebDataURI) {
- if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent,
- resolvedActivities, flags, userId)) {
+ if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, flags,
+ userId)) {
if (DEBUG_INSTANT) {
Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName
+ ", approved");
@@ -8167,7 +8226,7 @@
}
if (best == null || cur.priority > best.priority) {
- if (cur.getComponentInfo().enabled) {
+ if (isComponentEffectivelyEnabled(cur.getComponentInfo(), UserHandle.USER_SYSTEM)) {
best = cur;
} else {
Slog.w(TAG, "Domain verification agent found but not enabled");
@@ -9497,21 +9556,19 @@
@Override
public boolean isProtectedBroadcast(String actionName) {
- // allow instant applications
- synchronized (mProtectedBroadcasts) {
- if (mProtectedBroadcasts.contains(actionName)) {
+ if (actionName != null) {
+ // TODO: remove these terrible hacks
+ if (actionName.startsWith("android.net.netmon.lingerExpired")
+ || actionName.startsWith("com.android.server.sip.SipWakeupTimer")
+ || actionName.startsWith("com.android.internal.telephony.data-reconnect")
+ || actionName.startsWith("android.net.netmon.launchCaptivePortalApp")) {
return true;
- } else if (actionName != null) {
- // TODO: remove these terrible hacks
- if (actionName.startsWith("android.net.netmon.lingerExpired")
- || actionName.startsWith("com.android.server.sip.SipWakeupTimer")
- || actionName.startsWith("com.android.internal.telephony.data-reconnect")
- || actionName.startsWith("android.net.netmon.launchCaptivePortalApp")) {
- return true;
- }
}
}
- return false;
+ // allow instant applications
+ synchronized (mProtectedBroadcasts) {
+ return mProtectedBroadcasts.contains(actionName);
+ }
}
@Override
@@ -10131,7 +10188,7 @@
final String packageName = ri.activityInfo.packageName;
final PackageSetting ps = mSettings.getPackageLPr(packageName);
if (ps != null && hasAnyDomainApproval(mDomainVerificationManager, ps,
- intent, query, flags, userId)) {
+ intent, flags, userId)) {
return ri;
}
}
@@ -10188,10 +10245,10 @@
*/
private static boolean hasAnyDomainApproval(
@NonNull DomainVerificationManagerInternal manager, @NonNull PackageSetting pkgSetting,
- @NonNull Intent intent, @NonNull List<ResolveInfo> candidates,
- @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) {
- return manager.approvalLevelForDomain(pkgSetting, intent, candidates, resolveInfoFlags,
- userId) > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
+ @NonNull Intent intent, @PackageManager.ResolveInfoFlags int resolveInfoFlags,
+ @UserIdInt int userId) {
+ return manager.approvalLevelForDomain(pkgSetting, intent, resolveInfoFlags, userId)
+ > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
}
/**
@@ -10624,7 +10681,20 @@
private static class CrossProfileDomainInfo {
/* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
ResolveInfo resolveInfo;
- int highestApprovalLevel = DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
+ int highestApprovalLevel;
+
+ CrossProfileDomainInfo(ResolveInfo resolveInfo, int highestApprovalLevel) {
+ this.resolveInfo = resolveInfo;
+ this.highestApprovalLevel = highestApprovalLevel;
+ }
+
+ @Override
+ public String toString() {
+ return "CrossProfileDomainInfo{"
+ + "resolveInfo=" + resolveInfo
+ + ", highestApprovalLevel=" + highestApprovalLevel
+ + '}';
+ }
}
private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
@@ -15143,9 +15213,10 @@
if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Instrumentation: " + r);
}
- if (!pkg.getProtectedBroadcasts().isEmpty()) {
+ final List<String> protectedBroadcasts = pkg.getProtectedBroadcasts();
+ if (!protectedBroadcasts.isEmpty()) {
synchronized (mProtectedBroadcasts) {
- mProtectedBroadcasts.addAll(pkg.getProtectedBroadcasts());
+ mProtectedBroadcasts.addAll(protectedBroadcasts);
}
}
@@ -16915,6 +16986,7 @@
@Override
public void setInstallerPackageName(String targetPackage, String installerPackageName) {
final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(callingUid);
if (getInstantAppPackageName(callingUid) != null) {
return;
}
@@ -16923,14 +16995,16 @@
PackageSetting targetPackageSetting = mSettings.getPackageLPr(targetPackage);
if (targetPackageSetting == null
|| shouldFilterApplicationLocked(
- targetPackageSetting, callingUid, UserHandle.getUserId(callingUid))) {
+ targetPackageSetting, callingUid, callingUserId)) {
throw new IllegalArgumentException("Unknown target package: " + targetPackage);
}
PackageSetting installerPackageSetting;
if (installerPackageName != null) {
installerPackageSetting = mSettings.getPackageLPr(installerPackageName);
- if (installerPackageSetting == null) {
+ if (installerPackageSetting == null
+ || shouldFilterApplicationLocked(
+ installerPackageSetting, callingUid, callingUserId)) {
throw new IllegalArgumentException("Unknown installer package: "
+ installerPackageName);
}
@@ -22266,7 +22340,7 @@
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(userId)) {
+ if (StorageManager.isUserKeyUnlocked(userId)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(userId)) {
flags = StorageManager.FLAG_STORAGE_DE;
@@ -24113,6 +24187,42 @@
}
}
+ /**
+ * @return true if the runtime app user enabled state, runtime component user enabled state,
+ * install-time app manifest enabled state, and install-time component manifest enabled state
+ * are all effectively enabled for the given component. Or if the component cannot be found,
+ * returns false.
+ */
+ private boolean isComponentEffectivelyEnabled(@NonNull ComponentInfo componentInfo,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ try {
+ String packageName = componentInfo.packageName;
+ int appEnabledSetting =
+ mSettings.getApplicationEnabledSettingLPr(packageName, userId);
+ if (appEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) {
+ if (!componentInfo.applicationInfo.enabled) {
+ return false;
+ }
+ } else if (appEnabledSetting != COMPONENT_ENABLED_STATE_ENABLED) {
+ return false;
+ }
+
+ int componentEnabledSetting = mSettings.getComponentEnabledSettingLPr(
+ componentInfo.getComponentName(), userId);
+ if (componentEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) {
+ return componentInfo.isEnabled();
+ } else if (componentEnabledSetting != COMPONENT_ENABLED_STATE_ENABLED) {
+ return false;
+ }
+
+ return true;
+ } catch (PackageManager.NameNotFoundException ignored) {
+ return false;
+ }
+ }
+ }
+
@Override
public void enterSafeMode() {
enforceSystemOrRoot("Only the system can request entering safe mode");
@@ -25136,7 +25246,7 @@
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
for (UserInfo user : mUserManager.getUsers(false /* includeDying */)) {
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ if (StorageManager.isUserKeyUnlocked(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE;
@@ -25476,7 +25586,7 @@
StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ if (StorageManager.isUserKeyUnlocked(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE;
@@ -25490,7 +25600,7 @@
// Note: this code block is executed with the Installer lock
// already held, since it's invoked as a side-effect of
// executeBatchLI()
- if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ if (StorageManager.isUserKeyUnlocked(user.id)) {
// Prepare app data on external storage; currently this is used to
// setup any OBB dirs that were created by the installer correctly.
int uid = UserHandle.getUid(user.id, UserHandle.getAppId(pkg.getUid()));
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 4ebf476..f9c63a9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3772,9 +3772,6 @@
pw.println(" get-oem-permissions TARGET-PACKAGE");
pw.println(" Prints all OEM permissions for a package.");
pw.println("");
- pw.println(" set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
- pw.println(" get-app-link [--user USER_ID] PACKAGE");
- pw.println("");
pw.println(" trim-caches DESIRED_FREE_SPACE [internal|UUID]");
pw.println(" Trim cache files to reach the given free space.");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b0f8ee1..92b6a08 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -5495,7 +5495,8 @@
final int result = checkPermission(mContext, permission, attributionSource, message,
forDataDelivery, startDataDelivery, fromDatasource, attributedOp);
// Finish any started op if some step in the attribution chain failed.
- if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
+ if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
+ && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
if (attributedOp == AppOpsManager.OP_NONE) {
finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
attributionSource.asState(), fromDatasource);
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 65e4e95..262734f 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -389,7 +389,6 @@
*/
@ApprovalLevel
int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
- @NonNull List<ResolveInfo> candidates,
@PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId);
/**
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index b1b4e2a..ba64d25 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -1717,7 +1717,6 @@
@Override
public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
- @NonNull List<ResolveInfo> candidates,
@PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) {
String packageName = pkgSetting.getName();
if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) {
@@ -1783,9 +1782,26 @@
return APPROVAL_LEVEL_NONE;
}
- if (!pkgUserState.installed || !pkgUserState.isPackageEnabled(pkg)) {
+ if (!pkgUserState.installed) {
if (DEBUG_APPROVAL) {
- debugApproval(packageName, debugObject, userId, false, "package not enabled");
+ debugApproval(packageName, debugObject, userId, false,
+ "package not installed for user");
+ }
+ return APPROVAL_LEVEL_NONE;
+ }
+
+ if (!pkgUserState.isPackageEnabled(pkg)) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, debugObject, userId, false,
+ "package not enabled for user");
+ }
+ return APPROVAL_LEVEL_NONE;
+ }
+
+ if (pkgUserState.suspended) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, debugObject, userId, false,
+ "package suspended for user");
}
return APPROVAL_LEVEL_NONE;
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
index 5fcf411..ba4a62c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
@@ -9,7 +9,10 @@
]
},
{
- "name": "CtsDomainVerificationDeviceTestCases"
+ "name": "CtsDomainVerificationDeviceStandaloneTestCases"
+ },
+ {
+ "name": "CtsDomainVerificationDeviceMultiUserTestCases"
},
{
"name": "CtsDomainVerificationHostTestCases"
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index a6c93de..70b5c62 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -64,6 +64,11 @@
"android:activity_recognition_allow_listed_tags";
private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";";
+ private static ArraySet<String> sExpectedTags = new ArraySet<>(new String[] {
+ "awareness_provider", "activity_recognition_provider", "network_location_provider",
+ "network_location_calibration", "fused_location_provider", "geofencer_provider"});
+
+
@NonNull
private final Object mLock = new Object();
@@ -222,8 +227,14 @@
if (resolvedCode != code) {
if (isDatasourceAttributionTag(uid, packageName, attributionTag,
mLocationTags)) {
+ if (packageName.equals("com.google.android.gms")
+ && !sExpectedTags.contains(attributionTag)) {
+ Log.i("AppOpsDebugRemapping", "remapping " + packageName + " location "
+ + "for tag " + attributionTag);
+ }
return resolvedCode;
- } else if (packageName.equals("com.google.android.gms")) {
+ } else if (packageName.equals("com.google.android.gms")
+ && sExpectedTags.contains(attributionTag)) {
Log.i("AppOpsDebugRemapping", "NOT remapping " + packageName + " code "
+ code + " for tag " + attributionTag);
}
@@ -232,10 +243,14 @@
if (resolvedCode != code) {
if (isDatasourceAttributionTag(uid, packageName, attributionTag,
mActivityRecognitionTags)) {
- Log.i("AppOpsDebugRemapping", "remapping " + packageName + " code "
- + code + " to " + resolvedCode + " for tag " + attributionTag);
+ if (packageName.equals("com.google.android.gms")
+ && !sExpectedTags.contains(attributionTag)) {
+ Log.i("AppOpsDebugRemapping", "remapping " + packageName + " "
+ + "activity recognition for tag " + attributionTag);
+ }
return resolvedCode;
- } else if (packageName.equals("com.google.android.gms")) {
+ } else if (packageName.equals("com.google.android.gms")
+ && sExpectedTags.contains(attributionTag)) {
Log.i("AppOpsDebugRemapping", "NOT remapping " + packageName
+ " code " + code + " for tag " + attributionTag);
}
@@ -351,13 +366,15 @@
if (appIdTags != null) {
final ArraySet<String> packageTags = appIdTags.get(packageName);
if (packageTags != null && packageTags.contains(attributionTag)) {
- if (packageName.equals("com.google.android.gms")) {
+ if (packageName.equals("com.google.android.gms")
+ && !sExpectedTags.contains(attributionTag)) {
Log.i("AppOpsDebugRemapping", packageName + " tag "
+ attributionTag + " in " + packageTags);
}
return true;
}
- if (packageName.equals("com.google.android.gms")) {
+ if (packageName.equals("com.google.android.gms")
+ && sExpectedTags.contains(attributionTag)) {
Log.i("AppOpsDebugRemapping", packageName + " tag " + attributionTag
+ " NOT in " + packageTags);
}
diff --git a/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java b/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
index bf54bd5..9b0ef15 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseBooleanMatrix.java
@@ -67,7 +67,8 @@
* <ul>
* <li> The matrix does not automatically shrink but there is a compress() method that
* will recover unused space.
- * <li> Equality is a very, very expesive operation.
+ * <li> Equality is a very, very expensive operation because it must walk the matrices
+ * beimg compared element by element.
* </ul>
*/
@@ -79,10 +80,9 @@
static final int STEP = 64;
/**
- * There are 8 bits in a byte. The constant is defined here only to make it easy to
- * find in the code.
+ * The number of bits in the mValues array element.
*/
- private static final int BYTE = 8;
+ private static final int PACKING = 32;
/**
* Constants that index into the string array returned by matrixToString. The primary
@@ -123,7 +123,7 @@
/**
* The boolean array. This array is always {@code mOrder x mOrder} in size.
*/
- private byte[] mValues;
+ private int[] mValues;
/**
* A convenience function called when the elements are added to or removed from the storage.
@@ -157,10 +157,10 @@
throw new RuntimeException("mOrder is " + mOrder + " initCap is " + initialCapacity);
}
- mInUse = new boolean[mOrder];
+ mInUse = ArrayUtils.newUnpaddedBooleanArray(mOrder);
mKeys = ArrayUtils.newUnpaddedIntArray(mOrder);
mMap = ArrayUtils.newUnpaddedIntArray(mOrder);
- mValues = new byte[mOrder * mOrder / 8];
+ mValues = ArrayUtils.newUnpaddedIntArray(mOrder * mOrder / PACKING);
mSize = 0;
}
@@ -301,8 +301,8 @@
*/
private boolean valueAtInternal(int row, int col) {
int element = row * mOrder + col;
- int offset = element / BYTE;
- int mask = 1 << (element % BYTE);
+ int offset = element / PACKING;
+ int mask = 1 << (element % PACKING);
return (mValues[offset] & mask) != 0;
}
@@ -324,8 +324,8 @@
*/
private void setValueAtInternal(int row, int col, boolean value) {
int element = row * mOrder + col;
- int offset = element / BYTE;
- byte mask = (byte) (1 << (element % BYTE));
+ int offset = element / PACKING;
+ int mask = 1 << (element % PACKING);
if (value) {
mValues[offset] |= mask;
} else {
@@ -377,10 +377,10 @@
mSize++;
// Initialize the row and column corresponding to the new index.
- int valueRow = mOrder / BYTE;
- int offset = newIndex / BYTE;
- byte mask = (byte) (~(1 << (newIndex % BYTE)));
- Arrays.fill(mValues, newIndex * valueRow, (newIndex + 1) * valueRow, (byte) 0);
+ int valueRow = mOrder / PACKING;
+ int offset = newIndex / PACKING;
+ int mask = ~(1 << (newIndex % PACKING));
+ Arrays.fill(mValues, newIndex * valueRow, (newIndex + 1) * valueRow, 0);
for (int n = 0; n < mSize; n++) {
mValues[n * valueRow + offset] &= mask;
}
@@ -412,25 +412,36 @@
* Expand the 2D array. This also extends the free list.
*/
private void growMatrix() {
- resizeValues(mOrder + STEP);
+ resizeMatrix(mOrder + STEP);
}
/**
* Resize the values array to the new dimension.
*/
- private void resizeValues(int newOrder) {
-
- boolean[] newInuse = Arrays.copyOf(mInUse, newOrder);
+ private void resizeMatrix(int newOrder) {
+ if (newOrder % STEP != 0) {
+ throw new IllegalArgumentException("matrix order " + newOrder
+ + " is not a multiple of " + STEP);
+ }
int minOrder = Math.min(mOrder, newOrder);
- byte[] newValues = new byte[newOrder * newOrder / BYTE];
+ boolean[] newInUse = ArrayUtils.newUnpaddedBooleanArray(newOrder);
+ System.arraycopy(mInUse, 0, newInUse, 0, minOrder);
+ int[] newMap = ArrayUtils.newUnpaddedIntArray(newOrder);
+ System.arraycopy(mMap, 0, newMap, 0, minOrder);
+ int[] newKeys = ArrayUtils.newUnpaddedIntArray(newOrder);
+ System.arraycopy(mKeys, 0, newKeys, 0, minOrder);
+
+ int[] newValues = ArrayUtils.newUnpaddedIntArray(newOrder * newOrder / PACKING);
for (int i = 0; i < minOrder; i++) {
- int row = mOrder * i / BYTE;
- int newRow = newOrder * i / BYTE;
- System.arraycopy(mValues, row, newValues, newRow, minOrder / BYTE);
+ int row = mOrder * i / PACKING;
+ int newRow = newOrder * i / PACKING;
+ System.arraycopy(mValues, row, newValues, newRow, minOrder / PACKING);
}
- mInUse = newInuse;
+ mInUse = newInUse;
+ mMap = newMap;
+ mKeys = newKeys;
mValues = newValues;
mOrder = newOrder;
}
@@ -482,21 +493,21 @@
int src = mMap[srcIndex];
mInUse[src] = false;
mMap[srcIndex] = dst;
- System.arraycopy(mValues, src * mOrder / BYTE,
- mValues, dst * mOrder / BYTE,
- mOrder / BYTE);
- int srcOffset = (src / BYTE);
- byte srcMask = (byte) (1 << (src % BYTE));
- int dstOffset = (dst / BYTE);
- byte dstMask = (byte) (1 << (dst % BYTE));
+ System.arraycopy(mValues, src * mOrder / PACKING,
+ mValues, dst * mOrder / PACKING,
+ mOrder / PACKING);
+ int srcOffset = (src / PACKING);
+ int srcMask = 1 << (src % PACKING);
+ int dstOffset = (dst / PACKING);
+ int dstMask = 1 << (dst % PACKING);
for (int i = 0; i < mOrder; i++) {
if ((mValues[srcOffset] & srcMask) == 0) {
mValues[dstOffset] &= ~dstMask;
} else {
mValues[dstOffset] |= dstMask;
}
- srcOffset += mOrder / BYTE;
- dstOffset += mOrder / BYTE;
+ srcOffset += mOrder / PACKING;
+ dstOffset += mOrder / PACKING;
}
}
}
@@ -508,7 +519,7 @@
pack();
int unused = (mOrder - mSize) / STEP;
if (unused > 0) {
- resizeValues(mOrder - (unused * STEP));
+ resizeMatrix(mOrder - (unused * STEP));
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index a781520..2b6a838 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -102,7 +102,6 @@
import com.android.server.apphibernation.AppHibernationService;
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
/**
@@ -216,7 +215,7 @@
/** whether the process of the launching activity didn't have any active activity. */
final boolean mProcessSwitch;
/** The activities that should be drawn. */
- final LinkedList<ActivityRecord> mPendingDrawActivities = new LinkedList<>();
+ final ArrayList<ActivityRecord> mPendingDrawActivities = new ArrayList<>(2);
/** The latest activity to have been launched. */
@NonNull ActivityRecord mLastLaunchedActivity;
@@ -328,6 +327,17 @@
return mPendingDrawActivities.isEmpty();
}
+ /** Only keep the records which can be drawn. */
+ void updatePendingDraw() {
+ for (int i = mPendingDrawActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = mPendingDrawActivities.get(i);
+ if (!r.mVisibleRequested) {
+ if (DEBUG_METRICS) Slog.i(TAG, "Discard pending draw " + r);
+ mPendingDrawActivities.remove(i);
+ }
+ }
+ }
+
/**
* @return {@code true} if the transition info should be sent to MetricsLogger, StatsLog, or
* LaunchObserver.
@@ -701,6 +711,7 @@
info.mCurrentTransitionDelayMs = info.calculateDelay(timestampNs);
info.mReason = activityToReason.valueAt(index);
info.mLoggedTransitionStarting = true;
+ info.updatePendingDraw();
if (info.allDrawn()) {
done(false /* abort */, info, "notifyTransitionStarting - all windows drawn",
timestampNs);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9c43dd3..660cd37b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -308,6 +308,7 @@
import android.view.animation.Animation;
import android.window.IRemoteTransition;
import android.window.SizeConfigurationBuckets;
+import android.window.SplashScreen;
import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -6182,6 +6183,14 @@
}
private boolean shouldUseEmptySplashScreen(ActivityRecord sourceRecord) {
+ if (mPendingOptions != null) {
+ final int optionsStyle = mPendingOptions.getSplashScreenStyle();
+ if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_EMPTY) {
+ return true;
+ } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) {
+ return false;
+ }
+ }
if (sourceRecord == null) {
sourceRecord = searchCandidateLaunchingActivity();
}
diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java
index 7b4b23e..31e2ede 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -268,7 +268,9 @@
matrix.postRotate(90);
}
matrix.postTranslate(dx, dy);
- t.setMatrix(pinnedTask.getSurfaceControl(), matrix, new float[9]);
+ final SurfaceControl leash = pinnedTask.getSurfaceControl();
+ t.setMatrix(leash, matrix, new float[9])
+ .setCornerRadius(leash, pipTx.mCornerRadius);
Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy);
return;
}
diff --git a/services/core/jni/stats/SurfaceFlingerPuller.cpp b/services/core/jni/stats/SurfaceFlingerPuller.cpp
index 0e28da7..8873673 100644
--- a/services/core/jni/stats/SurfaceFlingerPuller.cpp
+++ b/services/core/jni/stats/SurfaceFlingerPuller.cpp
@@ -158,7 +158,8 @@
atom.total_jank_frames_sf_prediction_error(),
atom.total_jank_frames_app_buffer_stuffing(),
atom.display_refresh_rate_bucket(), atom.render_rate_bucket(),
- frameRateVote.value(), appDeadlineMisses.value());
+ frameRateVote.value(), appDeadlineMisses.value(),
+ atom.game_mode());
}
return AStatsManager_PULL_SUCCESS;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dddf3df..228bc0e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16016,9 +16016,9 @@
return Collections.emptyList();
}
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
-
- final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS)
+ || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL));
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
index 755795d..3bcbcbdb 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
@@ -25,11 +25,18 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.PackageIdentifier;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
import androidx.test.core.app.ApplicationProvider;
@@ -43,13 +50,15 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
import java.util.Collections;
+import java.util.Map;
/** This tests AppSearchImpl when it's running with a platform-backed VisibilityStore. */
public class AppSearchImplPlatformTest {
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
- private MockPackageManager mMockPackageManager = new MockPackageManager();
+ private final Map<UserHandle, PackageManager> mMockPackageManagers = new ArrayMap<>();
private Context mContext;
private AppSearchImpl mAppSearchImpl;
private int mGlobalQuerierUid;
@@ -57,20 +66,28 @@
@Before
public void setUp() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
- mContext =
- new ContextWrapper(context) {
+ mContext = new ContextWrapper(context) {
+ @Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return new ContextWrapper(super.createContextAsUser(user, flags)) {
@Override
public PackageManager getPackageManager() {
- return mMockPackageManager.getMockPackageManager();
+ return getMockPackageManager(user);
}
};
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return createContextAsUser(getUser(), /*flags=*/ 0).getPackageManager();
+ }
+ };
// Give ourselves global query permissions
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
mContext,
- mContext.getUserId(),
/*logger=*/ null);
mGlobalQuerierUid =
@@ -85,14 +102,19 @@
int uidFoo = 1;
// Make sure foo package will pass package manager checks.
- mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
- mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
+ .thenReturn(uidFoo);
+ when(mockPackageManager.hasSigningCertificate(
+ packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
+ .thenReturn(true);
// Make sure we have global query privileges and "foo" doesn't
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
+ when(mockPackageManager.checkPermission(
+ READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName()))
+ .thenReturn(PERMISSION_GRANTED);
+ when(mockPackageManager.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo))
+ .thenReturn(PERMISSION_DENIED);
// Set schema1
String prefix = PrefixUtil.createPrefix("package", "database");
@@ -145,19 +167,16 @@
/*schemaVersion=*/ 0);
// Check that "schema1" still has the same visibility settings
- SystemUtil.runWithShellPermissionIdentity(
- () -> {
- assertThat(
- mAppSearchImpl
- .getVisibilityStoreLocked()
- .isSchemaSearchableByCaller(
- "package",
- "database",
- prefix + "schema1",
- mContext.getPackageName(),
- mGlobalQuerierUid))
- .isFalse();
- },
+ SystemUtil.runWithShellPermissionIdentity(() -> assertThat(
+ mAppSearchImpl
+ .getVisibilityStoreLocked()
+ .isSchemaSearchableByCaller(
+ "package",
+ "database",
+ prefix + "schema1",
+ mContext.getPackageName(),
+ mGlobalQuerierUid))
+ .isFalse(),
READ_GLOBAL_APP_SEARCH_DATA);
assertThat(
@@ -172,19 +191,16 @@
.isTrue();
// "schema2" has default visibility settings
- SystemUtil.runWithShellPermissionIdentity(
- () -> {
- assertThat(
- mAppSearchImpl
- .getVisibilityStoreLocked()
- .isSchemaSearchableByCaller(
- "package",
- "database",
- prefix + "schema2",
- mContext.getPackageName(),
- mGlobalQuerierUid))
- .isTrue();
- },
+ SystemUtil.runWithShellPermissionIdentity(() -> assertThat(
+ mAppSearchImpl
+ .getVisibilityStoreLocked()
+ .isSchemaSearchableByCaller(
+ "package",
+ "database",
+ prefix + "schema2",
+ mContext.getPackageName(),
+ mGlobalQuerierUid))
+ .isTrue(),
READ_GLOBAL_APP_SEARCH_DATA);
assertThat(
@@ -207,14 +223,19 @@
int uidFoo = 1;
// Make sure foo package will pass package manager checks.
- mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
- mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
+ .thenReturn(uidFoo);
+ when(mockPackageManager.hasSigningCertificate(
+ packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
+ .thenReturn(true);
// Make sure we have global query privileges and "foo" doesn't
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
+ when(mockPackageManager.checkPermission(
+ READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName()))
+ .thenReturn(PERMISSION_GRANTED);
+ when(mockPackageManager.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo))
+ .thenReturn(PERMISSION_DENIED);
String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
@@ -320,8 +341,10 @@
@Test
public void testSetSchema_defaultPlatformVisible() throws Exception {
// Make sure we have global query privileges
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager.checkPermission(
+ READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName()))
+ .thenReturn(PERMISSION_GRANTED);
String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
@@ -348,8 +371,10 @@
@Test
public void testSetSchema_platformHidden() throws Exception {
// Make sure we have global query privileges
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager.checkPermission(
+ READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName()))
+ .thenReturn(PERMISSION_GRANTED);
String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
@@ -378,8 +403,9 @@
String packageName = "com.package";
// Make sure package doesn't global query privileges
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, packageName, PERMISSION_DENIED);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager.checkPermission(
+ READ_GLOBAL_APP_SEARCH_DATA, packageName)).thenReturn(PERMISSION_DENIED);
String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
@@ -410,12 +436,16 @@
int uidFoo = 1;
// Make sure foo package will pass package manager checks.
- mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
- mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
+ .thenReturn(uidFoo);
+ when(mockPackageManager.hasSigningCertificate(
+ packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
+ .thenReturn(true);
// Make sure foo doesn't have global query privileges
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
+ when(mockPackageManager.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo))
+ .thenReturn(PERMISSION_DENIED);
String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
@@ -439,4 +469,14 @@
uidFoo))
.isTrue();
}
+
+ @NonNull
+ private PackageManager getMockPackageManager(@NonNull UserHandle user) {
+ PackageManager pm = mMockPackageManagers.get(user);
+ if (pm == null) {
+ pm = Mockito.mock(PackageManager.class);
+ mMockPackageManagers.put(user, pm);
+ }
+ return pm;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/MockPackageManager.java b/services/tests/servicestests/src/com/android/server/appsearch/MockPackageManager.java
deleted file mode 100644
index 60e1a8f..0000000
--- a/services/tests/servicestests/src/com/android/server/appsearch/MockPackageManager.java
+++ /dev/null
@@ -1,94 +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.
- */
-
-// TODO(b/169883602): This is purposely a different package from the path so that AppSearchImplTest
-// can use it without an extra import. This should be moved into a proper package once
-// AppSearchImpl-VisibilityStore's dependencies are refactored.
-package com.android.server.appsearch.external.localstorage;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.content.pm.PackageManager;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Mock to help test package name, UID, and certificate verification. */
-public class MockPackageManager {
-
- @Mock private PackageManager mMockPackageManager;
-
- public MockPackageManager() {
- MockitoAnnotations.initMocks(this);
- }
-
- @NonNull
- public PackageManager getMockPackageManager() {
- return mMockPackageManager;
- }
-
- /** Mock a checkPermission call. */
- public void mockCheckPermission(String permission, String packageName, int permissionResult) {
- when(mMockPackageManager.checkPermission(permission, packageName))
- .thenReturn(permissionResult);
- }
-
- /** Mock a NameNotFoundException if the package name isn't installed. */
- public void mockThrowsNameNotFoundException(String packageName) {
- try {
- when(mMockPackageManager.getPackageUidAsUser(eq(packageName), /*userId=*/ anyInt()))
- .thenThrow(new PackageManager.NameNotFoundException());
- when(mMockPackageManager.getPackageUidAsUser(
- eq(packageName), /*flags=*/ anyInt(), /*userId=*/ anyInt()))
- .thenThrow(new PackageManager.NameNotFoundException());
- } catch (PackageManager.NameNotFoundException e) {
- // Shouldn't ever happen since we're mocking the exception
- e.printStackTrace();
- }
- }
-
- /** Mocks that {@code uid} contains the {@code packageName} */
- public void mockGetPackageUidAsUser(String packageName, @UserIdInt int callerUserId, int uid) {
- try {
- when(mMockPackageManager.getPackageUidAsUser(eq(packageName), eq(callerUserId)))
- .thenReturn(uid);
- when(mMockPackageManager.getPackageUidAsUser(
- eq(packageName), /*flags=*/ anyInt(), eq(callerUserId)))
- .thenReturn(uid);
- } catch (PackageManager.NameNotFoundException e) {
- // Shouldn't ever happen since we're mocking the method.
- e.printStackTrace();
- }
- }
-
- /** Mocks that {@code packageName} has been signed with {@code sha256Cert}. */
- public void mockAddSigningCertificate(String packageName, byte[] sha256Cert) {
- when(mMockPackageManager.hasSigningCertificate(
- packageName, sha256Cert, PackageManager.CERT_INPUT_SHA256))
- .thenReturn(true);
- }
-
- /** Mocks that {@code packageName} has NOT been signed with {@code sha256Cert}. */
- public void mockRemoveSigningCertificate(String packageName, byte[] sha256Cert) {
- when(mMockPackageManager.hasSigningCertificate(
- packageName, sha256Cert, PackageManager.CERT_INPUT_SHA256))
- .thenReturn(false);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
index d9cfc54..6ac4d13 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
@@ -25,10 +25,17 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
import android.app.appsearch.PackageIdentifier;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
import androidx.test.core.app.ApplicationProvider;
@@ -43,39 +50,47 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
import java.util.Collections;
+import java.util.Map;
public class VisibilityStoreTest {
-
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
- private MockPackageManager mMockPackageManager = new MockPackageManager();
+ private final Map<UserHandle, PackageManager> mMockPackageManagers = new ArrayMap<>();
private Context mContext;
- private AppSearchImpl mAppSearchImpl;
private VisibilityStore mVisibilityStore;
private int mUid;
@Before
public void setUp() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
- mContext =
- new ContextWrapper(context) {
+ mContext = new ContextWrapper(context) {
+ @Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return new ContextWrapper(super.createContextAsUser(user, flags)) {
@Override
public PackageManager getPackageManager() {
- return mMockPackageManager.getMockPackageManager();
+ return getMockPackageManager(user);
}
};
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return createContextAsUser(getUser(), /*flags=*/ 0).getPackageManager();
+ }
+ };
// Give ourselves global query permissions
- mAppSearchImpl =
+ AppSearchImpl appSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
mContext,
- mContext.getUserId(),
/*logger=*/ null);
mUid = mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
- mVisibilityStore = mAppSearchImpl.getVisibilityStoreLocked();
+ mVisibilityStore = appSearchImpl.getVisibilityStoreLocked();
}
/**
@@ -103,8 +118,10 @@
@Test
public void testSetVisibility_platformSurfaceable() throws Exception {
// Make sure we have global query privileges
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager
+ .checkPermission(READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName()))
+ .thenReturn(PERMISSION_GRANTED);
mVisibilityStore.setVisibility(
"package",
@@ -210,10 +227,13 @@
int uidNotFooOrBar = 3;
// Make sure none of them have global query privileges
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, packageNameBar, PERMISSION_DENIED);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager
+ .checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo))
+ .thenReturn(PERMISSION_DENIED);
+ when(mockPackageManager
+ .checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameBar))
+ .thenReturn(PERMISSION_DENIED);
// By default, a schema isn't package accessible.
assertThat(
@@ -237,32 +257,43 @@
ImmutableList.of(new PackageIdentifier(packageNameBar, sha256CertBar))));
// Should fail if PackageManager doesn't see that it has the proper certificate
- mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
- mMockPackageManager.mockRemoveSigningCertificate(packageNameFoo, sha256CertFoo);
+ when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
+ .thenReturn(uidFoo);
+ when(mockPackageManager.hasSigningCertificate(
+ packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
+ .thenReturn(false);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
.isFalse();
// Should fail if PackageManager doesn't think the package belongs to the uid
- mMockPackageManager.mockGetPackageUidAsUser(
- packageNameFoo, mContext.getUserId(), uidNotFooOrBar);
- mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
+ when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
+ .thenReturn(uidNotFooOrBar);
+ when(mockPackageManager.hasSigningCertificate(
+ packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
+ .thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
.isFalse();
// But if uid and certificate match, then we should have access
- mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
- mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
+ when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
+ .thenReturn(uidFoo);
+ when(mockPackageManager.hasSigningCertificate(
+ packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
+ .thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
.isTrue();
- mMockPackageManager.mockGetPackageUidAsUser(packageNameBar, mContext.getUserId(), uidBar);
- mMockPackageManager.mockAddSigningCertificate(packageNameBar, sha256CertBar);
+ when(mockPackageManager.getPackageUid(eq(packageNameBar), /*flags=*/ anyInt()))
+ .thenReturn(uidBar);
+ when(mockPackageManager.hasSigningCertificate(
+ packageNameBar, sha256CertBar, PackageManager.CERT_INPUT_SHA256))
+ .thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaBar", packageNameBar, uidBar))
@@ -278,15 +309,21 @@
"prefix/schemaFoo",
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))));
- mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
- mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
+ when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
+ .thenReturn(uidFoo);
+ when(mockPackageManager.hasSigningCertificate(
+ packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
+ .thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
.isTrue();
- mMockPackageManager.mockGetPackageUidAsUser(packageNameBar, mContext.getUserId(), uidBar);
- mMockPackageManager.mockAddSigningCertificate(packageNameBar, sha256CertBar);
+ when(mockPackageManager.getPackageUid(eq(packageNameBar), /*flags=*/ anyInt()))
+ .thenReturn(uidBar);
+ when(mockPackageManager.hasSigningCertificate(
+ packageNameBar, sha256CertBar, PackageManager.CERT_INPUT_SHA256))
+ .thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
"package", "database", "prefix/schemaBar", packageNameBar, uidBar))
@@ -302,11 +339,13 @@
int uidFoo = 1;
// Pretend we can't find the Foo package.
- mMockPackageManager.mockThrowsNameNotFoundException(packageNameFoo);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
+ .thenThrow(new PackageManager.NameNotFoundException());
// Make sure "foo" doesn't have global query privileges
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
+ when(mockPackageManager.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo))
+ .thenReturn(PERMISSION_DENIED);
// Grant package access
mVisibilityStore.setVisibility(
@@ -332,10 +371,12 @@
int uidFoo = 1;
// Set it up such that the test package has global query privileges, but "foo" doesn't.
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
- mMockPackageManager.mockCheckPermission(
- READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager.checkPermission(
+ READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName()))
+ .thenReturn(PERMISSION_GRANTED);
+ when(mockPackageManager.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo))
+ .thenReturn(PERMISSION_DENIED);
mVisibilityStore.setVisibility(
/*packageName=*/ "",
@@ -354,8 +395,11 @@
mUid))
.isTrue();
- mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
- mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
+ when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
+ .thenReturn(uidFoo);
+ when(mockPackageManager.hasSigningCertificate(
+ packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
+ .thenReturn(true);
assertThat(
mVisibilityStore.isSchemaSearchableByCaller(
/*packageName=*/ "",
@@ -365,4 +409,14 @@
uidFoo))
.isTrue();
}
+
+ @NonNull
+ private PackageManager getMockPackageManager(@NonNull UserHandle user) {
+ PackageManager pm = mMockPackageManagers.get(user);
+ if (pm == null) {
+ pm = Mockito.mock(PackageManager.class);
+ mMockPackageManagers.put(user, pm);
+ }
+ return pm;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index 031532b..5a8c44c 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -86,7 +86,6 @@
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
context,
- VisibilityStore.NO_OP_USER_ID,
/*logger=*/ null);
}
@@ -494,9 +493,7 @@
// Setup the index
Context context = ApplicationProvider.getApplicationContext();
File appsearchDir = mTemporaryFolder.newFolder();
- AppSearchImpl appSearchImpl =
- AppSearchImpl.create(
- appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
+ AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
// Insert schema
List<AppSearchSchema> schemas =
@@ -557,9 +554,7 @@
// Initialize AppSearchImpl. This should cause a reset.
appSearchImpl.close();
- appSearchImpl =
- AppSearchImpl.create(
- appsearchDir, context, VisibilityStore.NO_OP_USER_ID, testLogger);
+ appSearchImpl = AppSearchImpl.create(appsearchDir, context, testLogger);
// Check recovery state
InitializeStats initStats = testLogger.mInitializeStats;
@@ -1688,7 +1683,6 @@
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
context,
- VisibilityStore.NO_OP_USER_ID,
/*logger=*/ null);
// Initial check that we could do something at first.
@@ -1836,9 +1830,7 @@
// Setup the index
Context context = ApplicationProvider.getApplicationContext();
File appsearchDir = mTemporaryFolder.newFolder();
- AppSearchImpl appSearchImpl =
- AppSearchImpl.create(
- appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
+ AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
@@ -1864,8 +1856,7 @@
// That document should be visible even from another instance.
AppSearchImpl appSearchImpl2 =
- AppSearchImpl.create(
- appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
+ AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
getResult =
appSearchImpl2.getDocument(
"package", "database", "namespace1", "id1", Collections.emptyMap());
@@ -1877,9 +1868,7 @@
// Setup the index
Context context = ApplicationProvider.getApplicationContext();
File appsearchDir = mTemporaryFolder.newFolder();
- AppSearchImpl appSearchImpl =
- AppSearchImpl.create(
- appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
+ AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
@@ -1929,8 +1918,7 @@
// Only the second document should be retrievable from another instance.
AppSearchImpl appSearchImpl2 =
- AppSearchImpl.create(
- appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
+ AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
expectThrows(
AppSearchException.class,
() ->
@@ -1951,9 +1939,7 @@
// Setup the index
Context context = ApplicationProvider.getApplicationContext();
File appsearchDir = mTemporaryFolder.newFolder();
- AppSearchImpl appSearchImpl =
- AppSearchImpl.create(
- appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
+ AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
@@ -2011,8 +1997,7 @@
// Only the second document should be retrievable from another instance.
AppSearchImpl appSearchImpl2 =
- AppSearchImpl.create(
- appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
+ AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
expectThrows(
AppSearchException.class,
() ->
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
index d1ca759..f0a6ef1 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
@@ -40,7 +40,6 @@
import com.android.server.appsearch.proto.QueryStatsProto;
import com.android.server.appsearch.proto.ScoringSpecProto;
import com.android.server.appsearch.proto.TermMatchType;
-import com.android.server.appsearch.visibilitystore.VisibilityStore;
import org.junit.Before;
import org.junit.Rule;
@@ -64,7 +63,6 @@
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
context,
- VisibilityStore.NO_OP_USER_ID,
/*logger=*/ null);
mLogger = new TestLogger();
}
@@ -292,7 +290,6 @@
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
context,
- VisibilityStore.NO_OP_USER_ID,
mLogger);
InitializeStats iStats = mLogger.mInitializeStats;
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
index 734f05a..7e1639e 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
@@ -22,6 +22,7 @@
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.content.Context;
@@ -29,46 +30,46 @@
import android.content.pm.PackageManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.SparseIntArray;
import androidx.test.core.app.ApplicationProvider;
-import com.android.server.appsearch.external.localstorage.MockPackageManager;
import com.android.server.appsearch.external.localstorage.stats.CallStats;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mockito;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Map;
public class PlatformLoggerTest {
private static final int TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100;
private static final int TEST_DEFAULT_SAMPLING_INTERVAL = 10;
private static final String TEST_PACKAGE_NAME = "packageName";
- private MockPackageManager mMockPackageManager = new MockPackageManager();
+
+ private final Map<UserHandle, PackageManager> mMockPackageManagers = new ArrayMap<>();
private Context mContext;
@Before
public void setUp() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
- mContext =
- new ContextWrapper(context) {
+ mContext = new ContextWrapper(context) {
+ @Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return new ContextWrapper(super.createContextAsUser(user, flags)) {
@Override
public PackageManager getPackageManager() {
- return mMockPackageManager.getMockPackageManager();
+ return getMockPackageManager(user);
}
};
- }
-
- static int calculateHashCodeMd5withBigInteger(@NonNull String str) throws
- NoSuchAlgorithmException, UnsupportedEncodingException {
- MessageDigest md = MessageDigest.getInstance("MD5");
- md.update(str.getBytes(/*charsetName=*/ "UTF-8"));
- byte[] digest = md.digest();
- return new BigInteger(digest).intValue();
+ }
+ };
}
@Test
@@ -294,39 +295,51 @@
TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
TEST_DEFAULT_SAMPLING_INTERVAL,
/*samplingIntervals=*/ new SparseIntArray()));
- mMockPackageManager.mockGetPackageUidAsUser(testPackageName, mContext.getUserId(), testUid);
+ PackageManager mockPackageManager = getMockPackageManager(mContext.getUser());
+ when(mockPackageManager.getPackageUid(testPackageName, /*flags=*/0)).thenReturn(testUid);
- //
// First time, no cache
- //
PlatformLogger.ExtraStats extraStats = logger.createExtraStatsLocked(testPackageName,
CallStats.CALL_TYPE_PUT_DOCUMENT);
-
- verify(mMockPackageManager.getMockPackageManager(), times(1)).getPackageUidAsUser(
- eq(testPackageName), /*userId=*/ anyInt());
+ verify(mockPackageManager, times(1))
+ .getPackageUid(eq(testPackageName), /*flags=*/ anyInt());
assertThat(extraStats.mPackageUid).isEqualTo(testUid);
- //
// Second time, we have cache
- //
extraStats = logger.createExtraStatsLocked(testPackageName,
CallStats.CALL_TYPE_PUT_DOCUMENT);
// Count is still one since we will use the cache
- verify(mMockPackageManager.getMockPackageManager(), times(1)).getPackageUidAsUser(
- eq(testPackageName), /*userId=*/ anyInt());
+ verify(mockPackageManager, times(1))
+ .getPackageUid(eq(testPackageName), /*flags=*/ anyInt());
assertThat(extraStats.mPackageUid).isEqualTo(testUid);
- //
// Remove the cache and try again
- //
assertThat(logger.removeCachedUidForPackage(testPackageName)).isEqualTo(testUid);
extraStats = logger.createExtraStatsLocked(testPackageName,
CallStats.CALL_TYPE_PUT_DOCUMENT);
// count increased by 1 since cache is cleared
- verify(mMockPackageManager.getMockPackageManager(), times(2)).getPackageUidAsUser(
- eq(testPackageName), /*userId=*/ anyInt());
+ verify(mockPackageManager, times(2))
+ .getPackageUid(eq(testPackageName), /*flags=*/ anyInt());
assertThat(extraStats.mPackageUid).isEqualTo(testUid);
}
+
+ private static int calculateHashCodeMd5withBigInteger(@NonNull String str)
+ throws NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ md.update(str.getBytes(StandardCharsets.UTF_8));
+ byte[] digest = md.digest();
+ return new BigInteger(digest).intValue();
+ }
+
+ @NonNull
+ private PackageManager getMockPackageManager(@NonNull UserHandle user) {
+ PackageManager pm = mMockPackageManagers.get(user);
+ if (pm == null) {
+ pm = Mockito.mock(PackageManager.class);
+ mMockPackageManagers.put(user, pm);
+ }
+ return pm;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index f6baa6bb..7df2dd6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -635,8 +635,8 @@
}
@Override
- PendingIntent injectCreatePendingIntent(Context context, int requestCode,
- @NonNull Intent[] intents, int flags, Bundle options, UserHandle user) {
+ PendingIntent injectCreatePendingIntent(int requestCode, @NonNull Intent[] intents,
+ int flags, Bundle options, String ownerPackage, int ownerUserId) {
return new PendingIntent(mock(IIntentSender.class));
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index beff386..82c459c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -353,8 +353,22 @@
mTrampolineActivity.setVisibility(false);
notifyWindowsDrawn(mTopActivity);
- assertWithMessage("Trampoline activity is invisble so there should be no undrawn windows")
+ assertWithMessage("Trampoline activity is invisible so there should be no undrawn windows")
.that(mLaunchingState.allDrawn()).isTrue();
+
+ // Since the activity is drawn, the launch event should be reported.
+ notifyTransitionStarting(mTopActivity);
+ verifyOnActivityLaunchFinished(mTopActivity);
+ mLaunchTopByTrampoline = false;
+ clearInvocations(mLaunchObserver);
+
+ // Another round without setting visibility of the trampoline activity.
+ onActivityLaunchedTrampoline();
+ notifyWindowsDrawn(mTopActivity);
+ // If the transition can start, the invisible activities should be discarded and the launch
+ // event be reported successfully.
+ notifyTransitionStarting(mTopActivity);
+ verifyOnActivityLaunchFinished(mTopActivity);
}
@Test
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
index 415f055..41ee6b5 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerService.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -294,6 +294,11 @@
synchronized (mLock) {
dumpLocked("", pw);
+ final int userId = UserHandle.getCallingUserId();
+ final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.dumpLocked(" ", fd, pw);
+ }
}
}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index 2b01cdf..16a2d8d 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -45,12 +45,17 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
+import com.android.internal.os.TransferPipe;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.List;
final class TranslationManagerServiceImpl extends
@@ -69,6 +74,9 @@
@GuardedBy("mLock")
private TranslationServiceInfo mTranslationServiceInfo;
+ @GuardedBy("mLock")
+ private WeakReference<ActivityTokens> mLastActivityTokens;
+
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final TranslationServiceRemoteCallback mRemoteServiceCallback =
@@ -178,12 +186,35 @@
taskTopActivityTokens.getApplicationThread().updateUiTranslationState(
taskTopActivityTokens.getActivityToken(), state, sourceSpec, targetSpec,
viewIds, uiTranslationSpec);
+ mLastActivityTokens = new WeakReference<>(taskTopActivityTokens);
} catch (RemoteException e) {
Slog.w(TAG, "Update UiTranslationState fail: " + e);
}
invokeCallbacks(state, sourceSpec, targetSpec);
}
+ @GuardedBy("mLock")
+ public void dumpLocked(String prefix, FileDescriptor fd, PrintWriter pw) {
+ if (mLastActivityTokens != null) {
+ ActivityTokens activityTokens = mLastActivityTokens.get();
+ if (activityTokens == null) {
+ return;
+ }
+ try (TransferPipe tp = new TransferPipe()) {
+ activityTokens.getApplicationThread().dumpActivity(tp.getWriteFd(),
+ activityTokens.getActivityToken(), prefix,
+ new String[]{"--translation"});
+ tp.go(fd);
+ } catch (IOException e) {
+ pw.println(prefix + "Failure while dumping the activity: " + e);
+ } catch (RemoteException e) {
+ pw.println(prefix + "Got a RemoteException while dumping the activity");
+ }
+ } else {
+ pw.print(prefix); pw.println("No requested UiTranslation Activity.");
+ }
+ }
+
private void invokeCallbacks(
int state, TranslationSpec sourceSpec, TranslationSpec targetSpec) {
Bundle res = new Bundle();
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 4d5b6ac..88efe1f 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1069,6 +1069,13 @@
*/
public static final int NO_DEFAULT_DATA = 0x10008;
+ /**
+ * Data service is temporarily unavailable.
+ *
+ * @hide
+ */
+ public static final int SERVICE_TEMPORARILY_UNAVAILABLE = 0x10009;
+
private static final Map<Integer, String> sFailCauseMap;
static {
sFailCauseMap = new HashMap<>();
@@ -1500,6 +1507,7 @@
sFailCauseMap.put(HANDOVER_FAILED, "HANDOVER_FAILED");
sFailCauseMap.put(DUPLICATE_CID, "DUPLICATE_CID");
sFailCauseMap.put(NO_DEFAULT_DATA, "NO_DEFAULT_DATA");
+ sFailCauseMap.put(SERVICE_TEMPORARILY_UNAVAILABLE, "SERVICE_TEMPORARILY_UNAVAILABLE");
}
private DataFailCause() {
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 363e47a..d082715 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -63,6 +63,11 @@
public static final int RESULT_ERROR_BUSY = 3;
/** Request sent in illegal state */
public static final int RESULT_ERROR_ILLEGAL_STATE = 4;
+ /**
+ * Service is temporarily unavailable. Frameworks should retry the request again.
+ * @hide
+ */
+ public static final int RESULT_ERROR_TEMPORARILY_UNAVAILABLE = 5;
private final IDataServiceCallback mCallback;