Merge "Grant HotwordDetectionService RECORD_AUDIO and CAPTURE_AUDIO_HOTWORD." 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/AppSearchConfig.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java
index 6e81afc..d5271a6 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java
@@ -44,6 +44,8 @@
* @hide
*/
public final class AppSearchConfig implements AutoCloseable {
+ private static volatile AppSearchConfig sConfig;
+
/**
* It would be used as default min time interval between samples in millis if there is no value
* set for {@link AppSearchConfig#KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS} in DeviceConfig.
@@ -101,12 +103,16 @@
updateCachedValues(properties);
};
+ private AppSearchConfig() {
+ }
+
/**
* Creates an instance of {@link AppSearchConfig}.
*
* @param executor used to fetch and cache the flag values from DeviceConfig during creation or
* config change.
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
public static AppSearchConfig create(@NonNull Executor executor) {
Objects.requireNonNull(executor);
@@ -115,7 +121,23 @@
return configManager;
}
- private AppSearchConfig() {
+ /**
+ * Gets an instance of {@link AppSearchConfig} to be used.
+ *
+ * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
+ * existing instance will be returned.
+ */
+ @NonNull
+ public static AppSearchConfig getInstance(@NonNull Executor executor) {
+ Objects.requireNonNull(executor);
+ if (sConfig == null) {
+ synchronized (AppSearchConfig.class) {
+ if (sConfig == null) {
+ sConfig = create(executor);
+ }
+ }
+ }
+ return sConfig;
}
/**
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..a3b89b0f 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);
@@ -219,7 +224,7 @@
if (ImplInstanceManager.getAppSearchDir(userHandle).exists()) {
// Only clear the package's data if AppSearch exists for this user.
PlatformLogger logger = mLoggerInstanceManager.getOrCreatePlatformLogger(mContext,
- userHandle);
+ userHandle, AppSearchConfig.getInstance(EXECUTOR));
AppSearchImpl impl = mImplInstanceManager.getOrCreateAppSearchImpl(mContext,
userHandle, logger);
//TODO(b/145759910) clear visibility setting for package.
@@ -1142,7 +1147,8 @@
try {
verifyUserUnlocked(callingUser);
logger = mLoggerInstanceManager.getOrCreatePlatformLogger(
- mContext, callingUser);
+ mContext, callingUser,
+ AppSearchConfig.getInstance(EXECUTOR));
mImplInstanceManager.getOrCreateAppSearchImpl(mContext, callingUser, logger);
++operationSuccessCount;
invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
@@ -1183,13 +1189,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 +1259,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.
@@ -1291,7 +1314,8 @@
try {
verifyUserUnlocked(userHandle);
PlatformLogger logger = mLoggerInstanceManager.getOrCreatePlatformLogger(
- mContext, userHandle);
+ mContext, userHandle,
+ AppSearchConfig.getInstance(EXECUTOR));
AppSearchImpl impl = mImplInstanceManager.getOrCreateAppSearchImpl(
mContext, userHandle, logger);
stats.dataSize += impl.getStorageInfoForPackage(packageName).getSizeBytes();
@@ -1319,7 +1343,8 @@
return;
}
PlatformLogger logger = mLoggerInstanceManager.getOrCreatePlatformLogger(
- mContext, userHandle);
+ mContext, userHandle,
+ AppSearchConfig.getInstance(EXECUTOR));
AppSearchImpl impl = mImplInstanceManager.getOrCreateAppSearchImpl(
mContext, userHandle, logger);
for (int i = 0; i < packagesForUid.length; i++) {
@@ -1348,7 +1373,8 @@
return;
}
PlatformLogger logger = mLoggerInstanceManager.getOrCreatePlatformLogger(
- mContext, userHandle);
+ mContext, userHandle,
+ AppSearchConfig.getInstance(EXECUTOR));
AppSearchImpl impl =
mImplInstanceManager.getOrCreateAppSearchImpl(mContext, userHandle, logger);
for (int i = 0; i < packagesForUser.size(); i++) {
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/LoggerInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java
index cb65e42..ea00f50 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java
@@ -20,9 +20,9 @@
import android.content.Context;
import android.os.UserHandle;
import android.util.ArrayMap;
-import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.appsearch.AppSearchConfig;
import com.android.server.appsearch.AppSearchManagerService;
import java.util.Map;
@@ -34,12 +34,6 @@
* <p>These instances are managed per unique device-user.
*/
public final class LoggerInstanceManager {
- // TODO(b/173532925) flags to control those three
- // So probably we can't pass those three in the constructor but need to fetch the latest value
- // every time we need them in the logger.
- private static final int MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100;
- private static final int DEFAULT_SAMPLING_RATIO = 10;
-
private static volatile LoggerInstanceManager sLoggerInstanceManager;
@GuardedBy("mInstancesLocked")
@@ -70,23 +64,19 @@
/**
* Gets an instance of PlatformLogger for the given user, or creates one if none exists.
*
- * @param context The context
- * @param userHandle The multi-user handle of the device user calling AppSearch
+ * @param context The context
+ * @param userHandle The multi-user handle of the device user calling AppSearch
* @return An initialized {@link PlatformLogger} for this user
*/
@NonNull
public PlatformLogger getOrCreatePlatformLogger(
- @NonNull Context context, @NonNull UserHandle userHandle) {
+ @NonNull Context context, @NonNull UserHandle userHandle,
+ @NonNull AppSearchConfig config) {
Objects.requireNonNull(userHandle);
synchronized (mInstancesLocked) {
PlatformLogger instance = mInstancesLocked.get(userHandle);
if (instance == null) {
- instance = new PlatformLogger(context, userHandle, new PlatformLogger.Config(
- MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
- DEFAULT_SAMPLING_RATIO,
- // TODO(b/173532925) re-enable sampling ratios for different stats types
- // once we have P/H flag manager setup in ag/13977824
- /*samplingRatios=*/ new SparseIntArray()));
+ instance = new PlatformLogger(context, userHandle, config);
mInstancesLocked.put(userHandle, instance);
}
return instance;
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..26a16a1 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;
@@ -30,12 +29,14 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.appsearch.AppSearchConfig;
import com.android.server.appsearch.external.localstorage.AppSearchLogger;
import com.android.server.appsearch.external.localstorage.stats.CallStats;
import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
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;
@@ -60,15 +61,15 @@
// User we're logging for.
private final UserHandle mUserHandle;
- // Configuration for the logger
- private final Config mConfig;
+ // Manager holding the configuration flags
+ private final AppSearchConfig mConfig;
private final Random mRng = new Random();
private final Object mLock = new Object();
/**
* SparseArray to track how many stats we skipped due to
- * {@link Config#mMinTimeIntervalBetweenSamplesMillis}.
+ * {@link AppSearchConfig#getCachedMinTimeIntervalBetweenSamplesMillis()}.
*
* <p> We can have correct extrapolated number by adding those counts back when we log
* the same type of stats next time. E.g. the true count of an event could be estimated as:
@@ -98,53 +99,6 @@
private long mLastPushTimeMillisLocked = 0;
/**
- * Class to configure the {@link PlatformLogger}
- */
- public static final class Config {
- // Minimum time interval (in millis) since last message logged to Westworld before
- // logging again.
- private final long mMinTimeIntervalBetweenSamplesMillis;
-
- // Default sampling interval for all types of stats
- private final int mDefaultSamplingInterval;
-
- /**
- * Sampling intervals for different types of stats
- *
- * <p>This SparseArray is passed by client and is READ-ONLY. The key to that SparseArray is
- * {@link CallStats.CallType}
- *
- * <p>If sampling interval is missing for certain stats type,
- * {@link Config#mDefaultSamplingInterval} will be used.
- *
- * <p>E.g. sampling interval=10 means that one out of every 10 stats was logged. If sampling
- * interval is 1, we will log each sample and it acts as if the sampling is disabled.
- */
- @NonNull
- private final SparseIntArray mSamplingIntervals;
-
- /**
- * Configuration for {@link PlatformLogger}
- *
- * @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds
- * required for two consecutive stats logged
- * @param defaultSamplingInterval default sampling interval
- * @param samplingIntervals SparseArray to customize sampling interval for
- * different stat types
- */
- public Config(long minTimeIntervalBetweenSamplesMillis,
- int defaultSamplingInterval,
- @NonNull SparseIntArray samplingIntervals) {
- // TODO(b/173532925) Probably we can get rid of those three after we have p/h flags
- // for them.
- // e.g. we can just call DeviceConfig.get(SAMPLING_INTERVAL_FOR_PUT_DOCUMENTS).
- mMinTimeIntervalBetweenSamplesMillis = minTimeIntervalBetweenSamplesMillis;
- mDefaultSamplingInterval = defaultSamplingInterval;
- mSamplingIntervals = samplingIntervals;
- }
- }
-
- /**
* Helper class to hold platform specific stats for Westworld.
*/
static final class ExtraStats {
@@ -166,7 +120,8 @@
* Westworld constructor
*/
public PlatformLogger(
- @NonNull Context context, @NonNull UserHandle userHandle, @NonNull Config config) {
+ @NonNull Context context, @NonNull UserHandle userHandle,
+ @NonNull AppSearchConfig config) {
mContext = Objects.requireNonNull(context);
mUserHandle = Objects.requireNonNull(userHandle);
mConfig = Objects.requireNonNull(config);
@@ -372,7 +327,9 @@
stats.getSchemaStoreRecoveryLatencyMillis(),
stats.getDocumentStoreDataStatus(),
stats.getDocumentCount(),
- stats.getSchemaTypeCount());
+ stats.getSchemaTypeCount(),
+ stats.hasReset(),
+ stats.getResetStatusCode());
}
/**
@@ -428,9 +385,12 @@
packageUid = getPackageUidAsUserLocked(packageName);
}
- int samplingInterval = mConfig.mSamplingIntervals.get(callType,
- mConfig.mDefaultSamplingInterval);
-
+ // The sampling ratio here might be different from the one used in
+ // shouldLogForTypeLocked if there is a config change in the middle.
+ // Since it is only one sample, we can just ignore this difference.
+ // Or we can retrieve samplingRatio at beginning and pass along
+ // as function parameter, but it will make code less cleaner with some duplication.
+ int samplingInterval = getSamplingIntervalFromConfig(callType);
int skippedSampleCount = mSkippedSampleCountLocked.get(callType,
/*valueOfKeyIfNotFound=*/ 0);
mSkippedSampleCountLocked.put(callType, 0);
@@ -450,9 +410,7 @@
// rate limiting.
@VisibleForTesting
boolean shouldLogForTypeLocked(@CallStats.CallType int callType) {
- int samplingInterval = mConfig.mSamplingIntervals.get(callType,
- mConfig.mDefaultSamplingInterval);
-
+ int samplingInterval = getSamplingIntervalFromConfig(callType);
// Sampling
if (!shouldSample(samplingInterval)) {
return false;
@@ -462,7 +420,7 @@
// Check the timestamp to see if it is too close to last logged sample
long currentTimeMillis = SystemClock.elapsedRealtime();
if (mLastPushTimeMillisLocked
- > currentTimeMillis - mConfig.mMinTimeIntervalBetweenSamplesMillis) {
+ > currentTimeMillis - mConfig.getCachedMinTimeIntervalBetweenSamplesMillis()) {
int count = mSkippedSampleCountLocked.get(callType, /*valueOfKeyIfNotFound=*/ 0);
++count;
mSkippedSampleCountLocked.put(callType, count);
@@ -493,21 +451,39 @@
@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);
+ }
}
+ return 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
+ /** Returns sampling ratio for stats type specified form {@link AppSearchConfig}. */
+ private int getSamplingIntervalFromConfig(@CallStats.CallType int statsType) {
+ switch (statsType) {
+ case CallStats.CALL_TYPE_PUT_DOCUMENTS:
+ case CallStats.CALL_TYPE_GET_DOCUMENTS:
+ case CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_ID:
+ case CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH:
+ return mConfig.getCachedSamplingIntervalForBatchCallStats();
+ case CallStats.CALL_TYPE_PUT_DOCUMENT:
+ return mConfig.getCachedSamplingIntervalForPutDocumentStats();
+ case CallStats.CALL_TYPE_UNKNOWN:
+ case CallStats.CALL_TYPE_INITIALIZE:
+ case CallStats.CALL_TYPE_SET_SCHEMA:
+ case CallStats.CALL_TYPE_GET_DOCUMENT:
+ case CallStats.CALL_TYPE_REMOVE_DOCUMENT_BY_ID:
+ case CallStats.CALL_TYPE_SEARCH:
+ case CallStats.CALL_TYPE_OPTIMIZE:
+ case CallStats.CALL_TYPE_FLUSH:
+ case CallStats.CALL_TYPE_GLOBAL_SEARCH:
+ case CallStats.CALL_TYPE_REMOVE_DOCUMENT_BY_SEARCH:
+ // TODO(b/173532925) Some of them above will have dedicated sampling ratio config
+ default:
+ return mConfig.getCachedSamplingIntervalDefault();
}
- return Process.INVALID_UID;
}
//
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/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index edd6047..cd82deb 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -30,6 +30,7 @@
import android.util.Size;
import com.android.internal.graphics.ColorUtils;
+import com.android.internal.graphics.cam.Cam;
import com.android.internal.graphics.palette.CelebiQuantizer;
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
@@ -43,7 +44,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.stream.Collectors;
+import java.util.Set;
/**
* Provides information about the colors of a wallpaper.
@@ -176,7 +177,7 @@
shouldRecycle = true;
Size optimalSize = calculateOptimalSize(bitmap.getWidth(), bitmap.getHeight());
bitmap = Bitmap.createScaledBitmap(bitmap, optimalSize.getWidth(),
- optimalSize.getHeight(), true /* filter */);
+ optimalSize.getHeight(), false /* filter */);
}
final Palette palette;
@@ -189,7 +190,7 @@
} else {
palette = Palette
.from(bitmap, new CelebiQuantizer())
- .maximumColorCount(5)
+ .maximumColorCount(128)
.resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
.generate();
}
@@ -278,7 +279,7 @@
/**
* Constructs a new object from a set of colors, where hints can be specified.
*
- * @param populationByColor Map with keys of colors, and value representing the number of
+ * @param colorToPopulation Map with keys of colors, and value representing the number of
* occurrences of color in the wallpaper.
* @param colorHints A combination of color hints.
* @hide
@@ -286,20 +287,105 @@
* @see WallpaperColors#fromBitmap(Bitmap)
* @see WallpaperColors#fromDrawable(Drawable)
*/
- public WallpaperColors(@NonNull Map<Integer, Integer> populationByColor,
+ public WallpaperColors(@NonNull Map<Integer, Integer> colorToPopulation,
@ColorsHints int colorHints) {
- mAllColors = populationByColor;
+ mAllColors = colorToPopulation;
- ArrayList<Map.Entry<Integer, Integer>> mapEntries = new ArrayList(
- populationByColor.entrySet());
- mapEntries.sort((a, b) ->
- a.getValue().compareTo(b.getValue())
- );
- mMainColors = mapEntries.stream().map(entry -> Color.valueOf(entry.getKey())).collect(
- Collectors.toList());
+ final Map<Integer, Cam> colorToCam = new HashMap<>();
+ for (int color : colorToPopulation.keySet()) {
+ colorToCam.put(color, Cam.fromInt(color));
+ }
+ final double[] hueProportions = hueProportions(colorToCam, colorToPopulation);
+ final Map<Integer, Double> colorToHueProportion = colorToHueProportion(
+ colorToPopulation.keySet(), colorToCam, hueProportions);
+
+ final Map<Integer, Double> colorToScore = new HashMap<>();
+ for (Map.Entry<Integer, Double> mapEntry : colorToHueProportion.entrySet()) {
+ int color = mapEntry.getKey();
+ double proportion = mapEntry.getValue();
+ double score = score(colorToCam.get(color), proportion);
+ colorToScore.put(color, score);
+ }
+ ArrayList<Map.Entry<Integer, Double>> mapEntries = new ArrayList(colorToScore.entrySet());
+ mapEntries.sort((a, b) -> b.getValue().compareTo(a.getValue()));
+
+ List<Integer> colorsByScoreDescending = new ArrayList<>();
+ for (Map.Entry<Integer, Double> colorToScoreEntry : mapEntries) {
+ colorsByScoreDescending.add(colorToScoreEntry.getKey());
+ }
+
+ List<Integer> mainColorInts = new ArrayList<>();
+ findSeedColorLoop:
+ for (int color : colorsByScoreDescending) {
+ Cam cam = colorToCam.get(color);
+ for (int otherColor : mainColorInts) {
+ Cam otherCam = colorToCam.get(otherColor);
+ if (hueDiff(cam, otherCam) < 15) {
+ continue findSeedColorLoop;
+ }
+ }
+ mainColorInts.add(color);
+ }
+ List<Color> mainColors = new ArrayList<>();
+ for (int colorInt : mainColorInts) {
+ mainColors.add(Color.valueOf(colorInt));
+ }
+ mMainColors = mainColors;
mColorHints = colorHints;
}
+ private static double hueDiff(Cam a, Cam b) {
+ return (180f - Math.abs(Math.abs(a.getHue() - b.getHue()) - 180f));
+ }
+
+ private static double score(Cam cam, double proportion) {
+ return cam.getChroma() + (proportion * 100);
+ }
+
+ private static Map<Integer, Double> colorToHueProportion(Set<Integer> colors,
+ Map<Integer, Cam> colorToCam, double[] hueProportions) {
+ Map<Integer, Double> colorToHueProportion = new HashMap<>();
+ for (int color : colors) {
+ final int hue = wrapDegrees(Math.round(colorToCam.get(color).getHue()));
+ double proportion = 0.0;
+ for (int i = hue - 15; i < hue + 15; i++) {
+ proportion += hueProportions[wrapDegrees(i)];
+ }
+ colorToHueProportion.put(color, proportion);
+ }
+ return colorToHueProportion;
+ }
+
+ private static int wrapDegrees(int degrees) {
+ if (degrees < 0) {
+ return (degrees % 360) + 360;
+ } else if (degrees >= 360) {
+ return degrees % 360;
+ } else {
+ return degrees;
+ }
+ }
+
+ private static double[] hueProportions(@NonNull Map<Integer, Cam> colorToCam,
+ Map<Integer, Integer> colorToPopulation) {
+ final double[] proportions = new double[360];
+
+ double totalPopulation = 0;
+ for (Map.Entry<Integer, Integer> entry : colorToPopulation.entrySet()) {
+ totalPopulation += entry.getValue();
+ }
+
+ for (Map.Entry<Integer, Integer> entry : colorToPopulation.entrySet()) {
+ final int color = (int) entry.getKey();
+ final int population = colorToPopulation.get(color);
+ final Cam cam = colorToCam.get(color);
+ final int hue = wrapDegrees(Math.round(cam.getHue()));
+ proportions[hue] = proportions[hue] + ((double) population / totalPopulation);
+ }
+
+ return proportions;
+ }
+
public static final @android.annotation.NonNull Creator<WallpaperColors> CREATOR = new Creator<WallpaperColors>() {
@Override
public WallpaperColors createFromParcel(Parcel in) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8d74796..8c9e771 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6845,6 +6845,10 @@
* <p> Enabling lockdown via {@code lockdownEnabled} argument carries the risk that any failure
* of the VPN provider could break networking for all apps. This method clears any lockdown
* allowlist set by {@link #setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)}.
+ * <p> Starting from {@link android.os.Build.VERSION_CODES#S API 31} calling this method with
+ * {@code vpnPackage} set to {@code null} only removes the existing configuration if it was
+ * previously created by this admin. To remove VPN configuration created by the user use
+ * {@link UserManager#DISALLOW_CONFIG_VPN}.
*
* @param vpnPackage The package name for an installed VPN app on the device, or {@code null} to
* remove an existing always-on VPN configuration.
@@ -13112,6 +13116,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) {
@@ -13872,8 +13880,7 @@
}
/**
- * Called by device owner or profile owner of an organization-owned managed profile to return
- * whether USB data signaling is currently enabled by the admin.
+ * Returns whether USB data signaling is currently enabled by the admin. Callable by any app.
*
* @return {@code true} if USB data signaling is enabled, {@code false} otherwise.
*/
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 67f5c36..a9bec98 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -265,5 +265,4 @@
*/
public abstract void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker,
@OperationSafetyReason int reason, boolean isSafe);
-
}
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/app/people/ConversationChannel.java b/core/java/android/app/people/ConversationChannel.java
index 332e159..2bf71b0 100644
--- a/core/java/android/app/people/ConversationChannel.java
+++ b/core/java/android/app/people/ConversationChannel.java
@@ -36,8 +36,8 @@
private ShortcutInfo mShortcutInfo;
private int mUid;
- private NotificationChannel mParentNotificationChannel;
- private NotificationChannelGroup mParentNotificationChannelGroup;
+ private NotificationChannel mNotificationChannel;
+ private NotificationChannelGroup mNotificationChannelGroup;
private long mLastEventTimestamp;
private boolean mHasActiveNotifications;
private boolean mHasBirthdayToday;
@@ -61,8 +61,8 @@
boolean hasActiveNotifications) {
mShortcutInfo = shortcutInfo;
mUid = uid;
- mParentNotificationChannel = parentNotificationChannel;
- mParentNotificationChannelGroup = parentNotificationChannelGroup;
+ mNotificationChannel = parentNotificationChannel;
+ mNotificationChannelGroup = parentNotificationChannelGroup;
mLastEventTimestamp = lastEventTimestamp;
mHasActiveNotifications = hasActiveNotifications;
}
@@ -74,8 +74,8 @@
List<ConversationStatus> statuses) {
mShortcutInfo = shortcutInfo;
mUid = uid;
- mParentNotificationChannel = parentNotificationChannel;
- mParentNotificationChannelGroup = parentNotificationChannelGroup;
+ mNotificationChannel = parentNotificationChannel;
+ mNotificationChannelGroup = parentNotificationChannelGroup;
mLastEventTimestamp = lastEventTimestamp;
mHasActiveNotifications = hasActiveNotifications;
mHasBirthdayToday = hasBirthdayToday;
@@ -85,8 +85,8 @@
public ConversationChannel(Parcel in) {
mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
mUid = in.readInt();
- mParentNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
- mParentNotificationChannelGroup =
+ mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
+ mNotificationChannelGroup =
in.readParcelable(NotificationChannelGroup.class.getClassLoader());
mLastEventTimestamp = in.readLong();
mHasActiveNotifications = in.readBoolean();
@@ -104,8 +104,8 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mShortcutInfo, flags);
dest.writeInt(mUid);
- dest.writeParcelable(mParentNotificationChannel, flags);
- dest.writeParcelable(mParentNotificationChannelGroup, flags);
+ dest.writeParcelable(mNotificationChannel, flags);
+ dest.writeParcelable(mNotificationChannelGroup, flags);
dest.writeLong(mLastEventTimestamp);
dest.writeBoolean(mHasActiveNotifications);
dest.writeBoolean(mHasBirthdayToday);
@@ -120,12 +120,12 @@
return mUid;
}
- public NotificationChannel getParentNotificationChannel() {
- return mParentNotificationChannel;
+ public NotificationChannel getNotificationChannel() {
+ return mNotificationChannel;
}
- public NotificationChannelGroup getParentNotificationChannelGroup() {
- return mParentNotificationChannelGroup;
+ public NotificationChannelGroup getNotificationChannelGroup() {
+ return mNotificationChannelGroup;
}
public long getLastEventTimestamp() {
@@ -149,4 +149,18 @@
public @Nullable List<ConversationStatus> getStatuses() {
return mStatuses;
}
+
+ @Override
+ public String toString() {
+ return "ConversationChannel{" +
+ "mShortcutInfo=" + mShortcutInfo +
+ ", mUid=" + mUid +
+ ", mNotificationChannel=" + mNotificationChannel +
+ ", mNotificationChannelGroup=" + mNotificationChannelGroup +
+ ", mLastEventTimestamp=" + mLastEventTimestamp +
+ ", mHasActiveNotifications=" + mHasActiveNotifications +
+ ", mHasBirthdayToday=" + mHasBirthdayToday +
+ ", mStatuses=" + mStatuses +
+ '}';
+ }
}
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index 2dbbfdf..e11861f 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -307,10 +307,10 @@
mContactUri = getContactUri(info);
mStatuses = channel.getStatuses();
mLastInteractionTimestamp = channel.getLastEventTimestamp();
- mIsImportantConversation = channel.getParentNotificationChannel() != null
- && channel.getParentNotificationChannel().isImportantConversation();
- mCanBypassDnd = channel.getParentNotificationChannel() != null
- && channel.getParentNotificationChannel().canBypassDnd();
+ mIsImportantConversation = channel.getNotificationChannel() != null
+ && channel.getNotificationChannel().isImportantConversation();
+ mCanBypassDnd = channel.getNotificationChannel() != null
+ && channel.getNotificationChannel().canBypassDnd();
mNotificationPolicyState = SHOW_CONVERSATIONS;
}
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 5fcf4118..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/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index d50404e..24c6a5a 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -43,6 +43,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.ref.Reference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -1178,11 +1179,14 @@
void releaseTheme(long themePtr) {
synchronized (this) {
- nativeThemeDestroy(themePtr);
decRefsLocked(themePtr);
}
}
+ static long getThemeFreeFunction() {
+ return nativeGetThemeFreeFunction();
+ }
+
void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) {
synchronized (this) {
// Need to synchronize on AssetManager because we will be accessing
@@ -1192,6 +1196,31 @@
}
}
+ AssetManager rebaseTheme(long themePtr, @NonNull AssetManager newAssetManager,
+ @StyleRes int[] styleIds, @StyleRes boolean[] force, int count) {
+ // Exchange ownership of the theme with the new asset manager.
+ if (this != newAssetManager) {
+ synchronized (this) {
+ ensureValidLocked();
+ decRefsLocked(themePtr);
+ }
+ synchronized (newAssetManager) {
+ newAssetManager.ensureValidLocked();
+ newAssetManager.incRefsLocked(themePtr);
+ }
+ }
+
+ try {
+ synchronized (newAssetManager) {
+ newAssetManager.ensureValidLocked();
+ nativeThemeRebase(newAssetManager.mObject, themePtr, styleIds, force, count);
+ }
+ } finally {
+ Reference.reachabilityFence(newAssetManager);
+ }
+ return newAssetManager;
+ }
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void setThemeTo(long dstThemePtr, @NonNull AssetManager srcAssetManager, long srcThemePtr) {
synchronized (this) {
@@ -1559,12 +1588,13 @@
// Theme related native methods
private static native long nativeThemeCreate(long ptr);
- private static native void nativeThemeDestroy(long themePtr);
+ private static native long nativeGetThemeFreeFunction();
private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId,
boolean force);
+ private static native void nativeThemeRebase(long ptr, long themePtr, @NonNull int[] styleIds,
+ @NonNull boolean[] force, int styleSize);
private static native void nativeThemeCopy(long dstAssetManagerPtr, long dstThemePtr,
long srcAssetManagerPtr, long srcThemePtr);
- static native void nativeThemeClear(long themePtr);
private static native int nativeThemeGetAttributeValue(long ptr, long themePtr,
@AttrRes int resId, @NonNull TypedValue outValue, boolean resolve);
private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag,
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ac4b7b7..12e41e2 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -341,7 +341,7 @@
/**
* Set the underlying implementation (containing all the resources and caches)
- * and updates all Theme references to new implementations as well.
+ * and updates all Theme implementations as well.
* @hide
*/
@UnsupportedAppUsage
@@ -353,14 +353,14 @@
mBaseApkAssetsSize = ArrayUtils.size(impl.getAssets().getApkAssets());
mResourcesImpl = impl;
- // Create new ThemeImpls that are identical to the ones we have.
+ // Rebase the ThemeImpls using the new ResourcesImpl.
synchronized (mThemeRefs) {
final int count = mThemeRefs.size();
for (int i = 0; i < count; i++) {
WeakReference<Theme> weakThemeRef = mThemeRefs.get(i);
Theme theme = weakThemeRef != null ? weakThemeRef.get() : null;
if (theme != null) {
- theme.setNewResourcesImpl(mResourcesImpl);
+ theme.rebase(mResourcesImpl);
}
}
}
@@ -1515,12 +1515,6 @@
}
}
- void setNewResourcesImpl(ResourcesImpl resImpl) {
- synchronized (mLock) {
- mThemeImpl = resImpl.newThemeImpl(mThemeImpl.getKey());
- }
- }
-
/**
* Place new attribute values into the theme. The style resource
* specified by <var>resid</var> will be retrieved from this Theme's
@@ -1847,6 +1841,12 @@
}
}
+ void rebase(ResourcesImpl resImpl) {
+ synchronized (mLock) {
+ mThemeImpl.rebase(resImpl.mAssets);
+ }
+ }
+
/**
* Returns the resource ID for the style specified using {@code style="..."} in the
* {@link AttributeSet}'s backing XML element or {@link Resources#ID_NULL} otherwise if not
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 553e11b..b9f93b8 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -54,6 +54,8 @@
import com.android.internal.util.GrowingArrayUtils;
+import libcore.util.NativeAllocationRegistry;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -1265,15 +1267,9 @@
return new ThemeImpl();
}
- /**
- * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
- */
- ThemeImpl newThemeImpl(Resources.ThemeKey key) {
- ThemeImpl impl = new ThemeImpl();
- impl.mKey.setTo(key);
- impl.rebase();
- return impl;
- }
+ private static final NativeAllocationRegistry sThemeRegistry =
+ NativeAllocationRegistry.createMalloced(ResourcesImpl.class.getClassLoader(),
+ AssetManager.getThemeFreeFunction());
public class ThemeImpl {
/**
@@ -1282,7 +1278,7 @@
private final Resources.ThemeKey mKey = new Resources.ThemeKey();
@SuppressWarnings("hiding")
- private final AssetManager mAssets;
+ private AssetManager mAssets;
private final long mTheme;
/**
@@ -1293,6 +1289,7 @@
/*package*/ ThemeImpl() {
mAssets = ResourcesImpl.this.mAssets;
mTheme = mAssets.createTheme();
+ sThemeRegistry.registerNativeAllocation(this, mTheme);
}
@Override
@@ -1404,14 +1401,18 @@
* {@link #applyStyle(int, boolean)}.
*/
void rebase() {
- AssetManager.nativeThemeClear(mTheme);
+ rebase(mAssets);
+ }
- // Reapply the same styles in the same order.
- for (int i = 0; i < mKey.mCount; i++) {
- final int resId = mKey.mResId[i];
- final boolean force = mKey.mForce[i];
- mAssets.applyStyleToTheme(mTheme, resId, force);
- }
+ /**
+ * Rebases the theme against the {@code newAssets} by re-applying the styles passed to
+ * {@link #applyStyle(int, boolean)}.
+ *
+ * The theme will use {@code newAssets} for all future invocations of
+ * {@link #applyStyle(int, boolean)}.
+ */
+ void rebase(AssetManager newAssets) {
+ mAssets = mAssets.rebaseTheme(mTheme, newAssets, mKey.mResId, mKey.mForce, mKey.mCount);
}
/**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 224cd84..c22224d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -575,6 +575,8 @@
* <p>This restriction also prevents VPNs from starting. However, in Android 7.0
* ({@linkplain android.os.Build.VERSION_CODES#N API level 24}) or higher, the system does
* start always-on VPNs created by the device or profile owner.
+ * <p>From Android 12 ({@linkplain android.os.Build.VERSION_CODES#S API level 31}) enforcing
+ * this restriction clears currently active VPN if it was configured by the user.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 6e25968..8ebc081 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -160,7 +160,7 @@
/**
* Starts or re-starts loading of data.
*/
- void startLoading(
+ public void startLoading(
@NonNull DataLoaderParams dataLoaderParams,
@Nullable IDataLoaderStatusListener statusListener,
@Nullable StorageHealthCheckParams healthCheckParams,
diff --git a/core/java/android/os/incremental/IncrementalMetrics.java b/core/java/android/os/incremental/IncrementalMetrics.java
index c44b7d9..534525a8 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/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index f3cc35b3..c5d0cd4 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -158,6 +158,8 @@
mPermissionManager = IPermissionManager.Stub.asInterface(ServiceManager.getServiceOrThrow(
"permissionmgr"));
mLegacyPermissionManager = context.getSystemService(LegacyPermissionManager.class);
+ //TODO ntmyren: there should be a way to only enable the watcher when requested
+ mUsageHelper = new PermissionUsageHelper(context);
}
/**
@@ -878,10 +880,6 @@
@NonNull
@RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
public List<PermGroupUsage> getIndicatorAppOpUsageData() {
- // Lazily initialize the usage helper
- if (mUsageHelper == null) {
- mUsageHelper = new PermissionUsageHelper(mContext);
- }
return mUsageHelper.getOpUsageData(new AudioManager().isMicrophoneMute());
}
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 53ba259..d4e548e 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -19,6 +19,10 @@
import static android.Manifest.permission_group.CAMERA;
import static android.Manifest.permission_group.LOCATION;
import static android.Manifest.permission_group.MICROPHONE;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAGS_NONE;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
+import static android.app.AppOpsManager.AttributionFlags;
import static android.app.AppOpsManager.OPSTR_CAMERA;
import static android.app.AppOpsManager.OPSTR_COARSE_LOCATION;
import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
@@ -30,6 +34,7 @@
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -56,7 +61,7 @@
*
* @hide
*/
-public class PermissionUsageHelper {
+public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener {
/** Whether to show the mic and camera icons. */
private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled";
@@ -140,6 +145,7 @@
private ArrayMap<UserHandle, Context> mUserContexts;
private PackageManager mPkgManager;
private AppOpsManager mAppOpsManager;
+ private ArrayMap<Integer, ArrayList<AccessChainLink>> mAttributionChains = new ArrayMap<>();
/**
* Constructor for PermissionUsageHelper
@@ -151,6 +157,10 @@
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mUserContexts = new ArrayMap<>();
mUserContexts.put(Process.myUserHandle(), mContext);
+ // TODO ntmyren: make this listen for flag enable/disable changes
+ String[] ops = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
+ mContext.getSystemService(AppOpsManager.class).startWatchingActive(ops,
+ context.getMainExecutor(), this);
}
private Context getUserContext(UserHandle user) {
@@ -160,6 +170,45 @@
return mUserContexts.get(user);
}
+ @Override
+ public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
+ boolean active) {
+ // not part of an attribution chain. Do nothing
+ }
+
+ @Override
+ public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
+ if ((attributionFlags & ATTRIBUTION_FLAGS_NONE) != 0) {
+ return;
+ }
+
+ if (!active) {
+ // if any link in the chain is finished, remove the chain.
+ // TODO ntmyren: be smarter about this
+ mAttributionChains.remove(attributionChainId);
+ return;
+ }
+
+ ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent(
+ attributionChainId, k -> new ArrayList<>());
+ AccessChainLink link = new AccessChainLink(op, packageName, attributionTag, uid,
+ attributionFlags);
+
+ int currSize = currentChain.size();
+ if (currSize == 0 || link.isEnd() || !currentChain.get(currSize - 1).isEnd()) {
+ // if the list is empty, this link is the end, or the last link in the current chain
+ // isn't the end, add it to the end
+ currentChain.add(link);
+ } else if (link.isStart()) {
+ currentChain.add(0, link);
+ } else if (currentChain.get(currentChain.size() - 1).isEnd()) {
+ // we already have the end, and this is a mid node, so insert before the end
+ currentChain.add(currSize - 1, link);
+ }
+ }
+
/**
* @see PermissionManager.getIndicatorAppOpUsageData
*/
@@ -331,7 +380,7 @@
private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(List<OpUsage> usages) {
ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>();
- if (usages == null) {
+ if (usages == null || usages.isEmpty()) {
return usagesAndLabels;
}
@@ -430,8 +479,51 @@
}
iterNum++;
}
- usagesAndLabels.put(start,
- proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList));
+
+ // TODO ntmyren: remove this proxy logic once camera is converted to AttributionSource
+ // For now: don't add mic proxy usages
+ if (!start.op.equals(OPSTR_RECORD_AUDIO)) {
+ usagesAndLabels.put(start,
+ proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList));
+ }
+ }
+
+ for (int i = 0; i < mAttributionChains.size(); i++) {
+ List<AccessChainLink> usageList = mAttributionChains.valueAt(i);
+ int lastVisible = usageList.size() - 1;
+ // TODO ntmyren: remove this mic code once camera is converted to AttributionSource
+ // if the list is empty or incomplete, do not show it.
+ if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd()
+ || !usageList.get(0).isStart()
+ || !usageList.get(lastVisible).usage.op.equals(OPSTR_RECORD_AUDIO)) {
+ continue;
+ }
+
+ //TODO ntmyren: remove once camera etc. etc.
+ for (AccessChainLink link: usageList) {
+ proxyPackages.add(link.usage.getPackageIdHash());
+ }
+
+ AccessChainLink start = usageList.get(0);
+ AccessChainLink lastVisibleLink = usageList.get(lastVisible);
+ while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) {
+ lastVisible--;
+ lastVisibleLink = usageList.get(lastVisible);
+ }
+ String proxyLabel = null;
+ if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) {
+ try {
+ PackageManager userPkgManager =
+ getUserContext(lastVisibleLink.usage.getUser()).getPackageManager();
+ ApplicationInfo appInfo = userPkgManager.getApplicationInfo(
+ lastVisibleLink.usage.packageName, 0);
+ proxyLabel = appInfo.loadLabel(userPkgManager).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ // do nothing
+ }
+
+ }
+ usagesAndLabels.put(start.usage, proxyLabel);
}
for (int packageHash : mostRecentUsages.keySet()) {
@@ -495,4 +587,24 @@
&& lastAccessTime == other.lastAccessTime && isRunning == other.isRunning;
}
}
+
+ private static class AccessChainLink {
+ public final OpUsage usage;
+ public final @AttributionFlags int flags;
+
+ AccessChainLink(String op, String packageName, String attributionTag, int uid,
+ int flags) {
+ this.usage = new OpUsage(packageName, attributionTag, op, uid,
+ System.currentTimeMillis(), true, null);
+ this.flags = flags;
+ }
+
+ public boolean isEnd() {
+ return (flags & ATTRIBUTION_FLAG_ACCESSOR) != 0;
+ }
+
+ public boolean isStart() {
+ return (flags & ATTRIBUTION_FLAG_RECEIVER) != 0;
+ }
+ }
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index fed28df..67b97ce 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -354,6 +354,13 @@
null);
}
+ EventPayload(boolean triggerAvailable, boolean captureAvailable,
+ AudioFormat audioFormat, int captureSession, byte[] data,
+ HotwordDetectedResult hotwordDetectedResult) {
+ this(triggerAvailable, captureAvailable, audioFormat, captureSession, data,
+ hotwordDetectedResult, null);
+ }
+
EventPayload(AudioFormat audioFormat, HotwordDetectedResult hotwordDetectedResult) {
this(false, false, audioFormat, -1, null, hotwordDetectedResult, null);
}
@@ -1149,7 +1156,8 @@
}
@Override
- public void onKeyphraseDetected(KeyphraseRecognitionEvent event) {
+ public void onKeyphraseDetected(
+ KeyphraseRecognitionEvent event, HotwordDetectedResult result) {
if (DBG) {
Slog.d(TAG, "onDetected(" + event + ")");
} else {
@@ -1157,7 +1165,7 @@
}
Message.obtain(mHandler, MSG_HOTWORD_DETECTED,
new EventPayload(event.triggerInData, event.captureAvailable,
- event.captureFormat, event.captureSession, event.data))
+ event.captureFormat, event.captureSession, event.data, result))
.sendToTarget();
}
@Override
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 47f2c64..204e7df 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -147,8 +147,9 @@
}
@Override
- public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent)
- throws RemoteException {
+ public void onKeyphraseDetected(
+ SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
+ HotwordDetectedResult result) {
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 941e9aa..05ed75a 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>();
@@ -1511,9 +1511,12 @@
void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
float xOffsetStep) {
// to save creating a runnable, check twice
- long current = System.nanoTime() / 1_000_000;
+ long current = SystemClock.elapsedRealtime();
long lapsed = current - currentPage.getLastUpdateTime();
- if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) {
+ // Always update the page when the last update time is <= 0
+ // This is important especially when the device first boots
+ if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION
+ && currentPage.getLastUpdateTime() > 0) {
return;
}
Surface surface = mSurfaceHolder.getSurface();
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/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index 6a2b723..61f524f 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -154,4 +154,13 @@
* app.
*/
void detachNavigationBarFromApp(boolean moveHomeToTop);
+
+ /**
+ * Used for animating the navigation bar during app launch from recents in live tile mode.
+ *
+ * First fade out the navigation bar at the bottom of the display and then fade in to the app.
+ *
+ * @param duration the duration of the app launch animation
+ */
+ void animateNavigationBarToApp(long duration);
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 8d59ba0..b8b13b9 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -65,13 +65,20 @@
/**
* Called when the window insets configuration has changed.
+ *
+ * @param willMove The window frame will be moved soon.
+ * @param willResize The window frame will be resized soon.
*/
- void insetsChanged(in InsetsState insetsState);
+ void insetsChanged(in InsetsState insetsState, in boolean willMove, in boolean willResize);
/**
* Called when this window retrieved control over a specified set of insets sources.
+ *
+ * @param willMove The window frame will be moved soon.
+ * @param willResize The window frame will be resized soon.
*/
- void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls);
+ void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls,
+ in boolean willMove, in boolean willResize);
/**
* Called when a set of insets source window should be shown by policy.
diff --git a/core/java/android/view/ScrollCaptureTarget.java b/core/java/android/view/ScrollCaptureTarget.java
index 4fd4889..44017ed 100644
--- a/core/java/android/view/ScrollCaptureTarget.java
+++ b/core/java/android/view/ScrollCaptureTarget.java
@@ -16,6 +16,8 @@
package android.view;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
@@ -46,11 +48,11 @@
public ScrollCaptureTarget(@NonNull View scrollTarget, @NonNull Rect localVisibleRect,
@NonNull Point positionInWindow, @NonNull ScrollCaptureCallback callback) {
- mContainingView = scrollTarget;
+ mContainingView = requireNonNull(scrollTarget);
mHint = mContainingView.getScrollCaptureHint();
- mCallback = callback;
- mLocalVisibleRect = localVisibleRect;
- mPositionInWindow = positionInWindow;
+ mCallback = requireNonNull(callback);
+ mLocalVisibleRect = requireNonNull(localVisibleRect);
+ mPositionInWindow = requireNonNull(positionInWindow);
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8138b3d3..6d861dd 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9808,23 +9808,9 @@
*/
private void notifyAppearedOrDisappearedForContentCaptureIfNeeded(boolean appeared) {
AttachInfo ai = mAttachInfo;
- // Skip it while the view is being laided out for the first time
+ // Skip it while the view is being laid out for the first time
if (ai != null && !ai.mReadyForContentCaptureUpdates) return;
- if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "notifyContentCapture(" + appeared + ") for " + getClass().getSimpleName());
- }
- try {
- notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(appeared);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
- }
-
- private void notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(boolean appeared) {
- AttachInfo ai = mAttachInfo;
-
// First check if context has client, so it saves a service lookup when it doesn't
if (mContext.getContentCaptureOptions() == null) return;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 67cf85c..86380a2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -585,6 +585,10 @@
final Rect mWinFrame; // frame given by window manager.
final Rect mPendingBackDropFrame = new Rect();
+
+ private boolean mWillMove;
+ private boolean mWillResize;
+
boolean mPendingAlwaysConsumeSystemBars;
private final InsetsState mTempInsets = new InsetsState();
private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
@@ -1708,6 +1712,10 @@
void notifyInsetsChanged() {
mApplyInsetsRequested = true;
+ if (mWillMove || mWillResize) {
+ // The window frame will be changed soon. The following logic will be executed then.
+ return;
+ }
requestLayout();
// See comment for View.sForceLayoutWhenInsetsChanged
@@ -2665,7 +2673,7 @@
}
}
- if (mApplyInsetsRequested) {
+ if (mApplyInsetsRequested && !(mWillMove || mWillResize)) {
dispatchApplyInsets(host);
if (mLayoutRequested) {
// Short-circuit catching a new layout request here, so
@@ -5235,16 +5243,25 @@
break;
case MSG_RESIZED:
case MSG_RESIZED_REPORT: {
+ mWillMove = false;
+ mWillResize = false;
final SomeArgs args = (SomeArgs) msg.obj;
handleResized(msg.what, args);
args.recycle();
break;
}
- case MSG_INSETS_CHANGED:
- mInsetsController.onStateChanged((InsetsState) msg.obj);
+ case MSG_INSETS_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mWillMove = args.argi1 == 1;
+ mWillResize = args.argi2 == 1;
+ mInsetsController.onStateChanged((InsetsState) args.arg1);
+ args.recycle();
break;
+ }
case MSG_INSETS_CONTROL_CHANGED: {
SomeArgs args = (SomeArgs) msg.obj;
+ mWillMove = args.argi1 == 1;
+ mWillResize = args.argi2 == 1;
// Deliver state change before control change, such that:
// a) When gaining control, controller can compare with server state to evaluate
@@ -5253,6 +5270,7 @@
// dispatched state as truth.
mInsetsController.onStateChanged((InsetsState) args.arg1);
mInsetsController.onControlsChanged((InsetsSourceControl[]) args.arg2);
+ args.recycle();
break;
}
case MSG_SHOW_INSETS: {
@@ -5270,6 +5288,7 @@
break;
}
case MSG_WINDOW_MOVED:
+ mWillMove = false;
if (mAdded) {
final int w = mWinFrame.width();
final int h = mWinFrame.height();
@@ -7744,6 +7763,8 @@
mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
}
setFrame(mTmpFrames.frame);
+ mWillMove = false;
+ mWillResize = false;
mInsetsController.onStateChanged(mTempInsets);
mInsetsController.onControlsChanged(mTempControls);
return relayoutResult;
@@ -8179,7 +8200,8 @@
mHandler.sendMessage(msg);
}
- private void dispatchInsetsChanged(InsetsState insetsState) {
+ private void dispatchInsetsChanged(InsetsState insetsState, boolean willMove,
+ boolean willResize) {
if (Binder.getCallingPid() == android.os.Process.myPid()) {
insetsState = new InsetsState(insetsState, true /* copySource */);
}
@@ -8190,11 +8212,15 @@
ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsChanged",
getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
}
- mHandler.obtainMessage(MSG_INSETS_CHANGED, insetsState).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = insetsState;
+ args.argi1 = willMove ? 1 : 0;
+ args.argi2 = willResize ? 1 : 0;
+ mHandler.obtainMessage(MSG_INSETS_CHANGED, args).sendToTarget();
}
private void dispatchInsetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
+ InsetsSourceControl[] activeControls, boolean willMove, boolean willResize) {
if (Binder.getCallingPid() == android.os.Process.myPid()) {
insetsState = new InsetsState(insetsState, true /* copySource */);
if (activeControls != null) {
@@ -8214,6 +8240,8 @@
SomeArgs args = SomeArgs.obtain();
args.arg1 = insetsState;
args.arg2 = activeControls;
+ args.argi1 = willMove ? 1 : 0;
+ args.argi2 = willResize ? 1 : 0;
mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget();
}
@@ -9560,19 +9588,20 @@
}
@Override
- public void insetsChanged(InsetsState insetsState) {
+ public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.dispatchInsetsChanged(insetsState);
+ viewAncestor.dispatchInsetsChanged(insetsState, willMove, willResize);
}
}
@Override
public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
+ InsetsSourceControl[] activeControls, boolean willMove, boolean willResize) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls);
+ viewAncestor.dispatchInsetsControlChanged(
+ insetsState, activeControls, willMove, willResize);
}
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index ad4ba76..aee540f 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -44,8 +44,9 @@
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.LocalLog;
import android.util.Log;
import android.util.TimeUtils;
@@ -60,7 +61,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -151,12 +151,6 @@
private final LocalLog mFlushHistory;
/**
- * If the event in the buffer is of type {@link TYPE_VIEW_TEXT_CHANGED}, this value
- * indicates whether the event has composing span or not.
- */
- private final Map<AutofillId, Boolean> mLastComposingSpan = new ArrayMap<>();
-
- /**
* Binder object used to update the session state.
*/
@NonNull
@@ -352,40 +346,34 @@
// 2.1 either last or current text is empty: add.
// 2.2 last event doesn't have composing span: add.
// Otherwise, merge.
-
final CharSequence text = event.getText();
final boolean textHasComposingSpan = event.getTextHasComposingSpan();
-
- if (textHasComposingSpan && !mLastComposingSpan.isEmpty()) {
- final Boolean lastEventHasComposingSpan = mLastComposingSpan.get(event.getId());
- if (lastEventHasComposingSpan != null && lastEventHasComposingSpan.booleanValue()) {
- ContentCaptureEvent lastEvent = null;
- for (int index = mEvents.size() - 1; index >= 0; index--) {
- final ContentCaptureEvent tmpEvent = mEvents.get(index);
- if (event.getId().equals(tmpEvent.getId())) {
- lastEvent = tmpEvent;
- break;
- }
+ if (textHasComposingSpan) {
+ ContentCaptureEvent lastEvent = null;
+ for (int index = mEvents.size() - 1; index >= 0; index--) {
+ final ContentCaptureEvent tmpEvent = mEvents.get(index);
+ if (event.getId().equals(tmpEvent.getId())) {
+ lastEvent = tmpEvent;
+ break;
}
- if (lastEvent != null) {
- final CharSequence lastText = lastEvent.getText();
- final boolean bothNonEmpty = !TextUtils.isEmpty(lastText)
- && !TextUtils.isEmpty(text);
- boolean equalContent = TextUtils.equals(lastText, text);
- if (equalContent) {
- addEvent = false;
- } else if (bothNonEmpty && lastEventHasComposingSpan) {
- lastEvent.mergeEvent(event);
- addEvent = false;
- }
- if (!addEvent && sVerbose) {
- Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
- + getSanitizedString(text));
- }
+ }
+ if (lastEvent != null && lastEvent.getTextHasComposingSpan()) {
+ final CharSequence lastText = lastEvent.getText();
+ final boolean bothNonEmpty = !TextUtils.isEmpty(lastText)
+ && !TextUtils.isEmpty(text);
+ boolean equalContent = TextUtils.equals(lastText, text);
+ if (equalContent) {
+ addEvent = false;
+ } else if (bothNonEmpty) {
+ lastEvent.mergeEvent(event);
+ addEvent = false;
+ }
+ if (!addEvent && sVerbose) {
+ Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
+ + getSanitizedString(text));
}
}
}
- mLastComposingSpan.put(event.getId(), textHasComposingSpan);
}
if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
@@ -586,7 +574,6 @@
? Collections.EMPTY_LIST
: new ArrayList<>(mEvents);
mEvents.clear();
- mLastComposingSpan.clear();
return new ParceledListSlice<>(events);
}
@@ -717,7 +704,7 @@
// Since the same CharSequence instance may be reused in the TextView, we need to make
// a copy of its content so that its value will not be changed by subsequent updates
// in the TextView.
- final String eventText = text == null ? null : text.toString();
+ final CharSequence eventText = stringOrSpannedStringWithoutNoCopySpans(text);
final boolean textHasComposingSpan =
text instanceof Spannable && BaseInputConnection.getComposingSpanStart(
(Spannable) text) >= 0;
@@ -726,6 +713,16 @@
.setAutofillId(id).setText(eventText, textHasComposingSpan)));
}
+ private CharSequence stringOrSpannedStringWithoutNoCopySpans(CharSequence source) {
+ if (source == null) {
+ return null;
+ } else if (source instanceof Spanned) {
+ return new SpannableString(source, /* ignoreNoCopySpan= */ true);
+ } else {
+ return source.toString();
+ }
+ }
+
/** Public because is also used by ViewRootImpl */
public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
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/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 40cce7c..d596626 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -44,6 +44,7 @@
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZoneId;
+import java.time.ZonedDateTime;
import java.util.Formatter;
import java.util.Locale;
@@ -451,7 +452,9 @@
if (mSecondHandTintInfo.mHasTintList || mSecondHandTintInfo.mHasTintBlendMode) {
mSecondHand = mSecondHandTintInfo.apply(mSecondHand);
}
- mSecondsTick.run();
+ // Re-run the tick runnable immediately as the presence or absence of a seconds hand affects
+ // the next time we need to tick the clock.
+ mTick.run();
mChanged = true;
invalidate();
@@ -583,10 +586,10 @@
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
// OK, this is gross but needed. This class is supported by the
- // remote views machanism and as a part of that the remote views
+ // remote views mechanism and as a part of that the remote views
// can be inflated by a context for another user without the app
// having interact users permission - just for loading resources.
- // For exmaple, when adding widgets from a user profile to the
+ // For example, when adding widgets from a user profile to the
// home screen. Therefore, we register the receiver as the current
// user not the one the context is for.
getContext().registerReceiverAsUser(mIntentReceiver,
@@ -616,14 +619,14 @@
private void onVisible() {
if (!mVisible) {
mVisible = true;
- mSecondsTick.run();
+ mTick.run();
}
}
private void onInvisible() {
if (mVisible) {
- removeCallbacks(mSecondsTick);
+ removeCallbacks(mTick);
mVisible = false;
}
}
@@ -760,6 +763,7 @@
}
}
+ /** Intent receiver for the time or time zone changing. */
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -767,36 +771,56 @@
createClock();
}
- onTimeChanged();
-
- invalidate();
+ mTick.run();
}
};
private boolean mReceiverAttached;
- private final Runnable mSecondsTick = new Runnable() {
+ private final Runnable mTick = new Runnable() {
@Override
public void run() {
removeCallbacks(this);
- if (!mVisible || mSecondHand == null) {
+ if (!mVisible) {
return;
}
Instant now = mClock.instant();
- LocalTime localTime = now.atZone(mClock.getZone()).toLocalTime();
- // How many milliseconds through the second we currently are.
- long millisOfSecond = Duration.ofNanos(localTime.getNano()).toMillis();
- // How many milliseconds there are between tick positions for the seconds hand.
- double millisPerTick = 1000 / (double) mSecondsHandFps;
- // How many milliseconds we are past the last tick position.
- long millisPastLastTick = Math.round(millisOfSecond % millisPerTick);
- // How many milliseconds there are until the next tick position.
- long millisUntilNextTick = Math.round(millisPerTick - millisPastLastTick);
- // If we are exactly at the tick position, this could be 0 milliseconds due to rounding.
- // In this case, advance by the full amount of millis to the next position.
- if (millisUntilNextTick <= 0) {
- millisUntilNextTick = Math.round(millisPerTick);
+ ZonedDateTime zonedDateTime = now.atZone(mClock.getZone());
+ LocalTime localTime = zonedDateTime.toLocalTime();
+
+ long millisUntilNextTick;
+ if (mSecondHand == null) {
+ // If there's no second hand, then tick at the start of the next minute.
+ //
+ // This must be done with ZonedDateTime as opposed to LocalDateTime to ensure proper
+ // handling of DST. Also note that because of leap seconds, it should not be assumed
+ // that one minute == 60 seconds.
+ Instant startOfNextMinute = zonedDateTime.plusMinutes(1).withSecond(0).toInstant();
+ millisUntilNextTick = Duration.between(now, startOfNextMinute).toMillis();
+ if (millisUntilNextTick <= 0) {
+ // This should never occur, but if it does, then just check the tick again in
+ // one minute to ensure we're always moving forward.
+ millisUntilNextTick = Duration.ofMinutes(1).toMillis();
+ }
+ } else {
+ // If there is a seconds hand, then determine the next tick point based on the fps.
+ //
+ // How many milliseconds through the second we currently are.
+ long millisOfSecond = Duration.ofNanos(localTime.getNano()).toMillis();
+ // How many milliseconds there are between tick positions for the seconds hand.
+ double millisPerTick = 1000 / (double) mSecondsHandFps;
+ // How many milliseconds we are past the last tick position.
+ long millisPastLastTick = Math.round(millisOfSecond % millisPerTick);
+ // How many milliseconds there are until the next tick position.
+ millisUntilNextTick = Math.round(millisPerTick - millisPastLastTick);
+ // If we are exactly at the tick position, this could be 0 milliseconds due to
+ // rounding. In this case, advance by the full amount of millis to the next
+ // position.
+ if (millisUntilNextTick <= 0) {
+ millisUntilNextTick = Math.round(millisPerTick);
+ }
}
+
// Schedule a callback for when the next tick should occur.
postDelayed(this, millisUntilNextTick);
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 25c84ba..73d56da 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -114,6 +114,7 @@
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -812,29 +813,8 @@
AdapterView<?> av = (AdapterView<?>) target;
// The PendingIntent template is stored in the view's tag.
OnItemClickListener listener = (parent, view, position, id) -> {
- // The view should be a frame layout
- if (view instanceof ViewGroup) {
- ViewGroup vg = (ViewGroup) view;
-
- // AdapterViews contain their children in a frame
- // so we need to go one layer deeper here.
- if (parent instanceof AdapterViewAnimator) {
- vg = (ViewGroup) vg.getChildAt(0);
- }
- if (vg == null) return;
-
- RemoteResponse response = null;
- int childCount = vg.getChildCount();
- for (int i = 0; i < childCount; i++) {
- Object tag = vg.getChildAt(i).getTag(R.id.fillInIntent);
- if (tag instanceof RemoteResponse) {
- response = (RemoteResponse) tag;
- break;
- }
- }
- if (response == null) return;
- response.handleViewInteraction(view, handler);
- }
+ RemoteResponse response = findRemoteResponseTag(view);
+ response.handleViewInteraction(view, handler);
};
av.setOnItemClickListener(listener);
av.setTag(pendingIntentTemplate);
@@ -845,6 +825,28 @@
}
}
+ @Nullable
+ private RemoteResponse findRemoteResponseTag(@Nullable View rootView) {
+ if (rootView == null) return null;
+
+ ArrayDeque<View> viewsToCheck = new ArrayDeque<>();
+ viewsToCheck.addLast(rootView);
+
+ while (!viewsToCheck.isEmpty()) {
+ View view = viewsToCheck.removeFirst();
+ Object tag = view.getTag(R.id.fillInIntent);
+ if (tag instanceof RemoteResponse) return (RemoteResponse) tag;
+ if (!(view instanceof ViewGroup)) continue;
+
+ ViewGroup viewGroup = (ViewGroup) view;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ viewsToCheck.addLast(viewGroup.getChildAt(i));
+ }
+ }
+
+ return null;
+ }
+
@Override
public int getActionTag() {
return SET_PENDING_INTENT_TEMPLATE_TAG;
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index b1485ef..e48afb2 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -34,7 +34,6 @@
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateFormat;
@@ -45,6 +44,10 @@
import com.android.internal.R;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.TimeZone;
@@ -185,18 +188,29 @@
private final Runnable mTicker = new Runnable() {
public void run() {
- if (mStopTicking) {
+ removeCallbacks(this);
+ if (mStopTicking || !mShouldRunTicker) {
return; // Test disabled the clock ticks
}
onTimeChanged();
- long now = SystemClock.uptimeMillis();
- long next = now + (1000 - now % 1000);
+ Instant now = mTime.toInstant();
+ ZoneId zone = mTime.getTimeZone().toZoneId();
- Handler handler = getHandler();
- if (handler != null) {
- handler.postAtTime(mTicker, next);
+ ZonedDateTime nextTick;
+ if (mHasSeconds) {
+ nextTick = now.atZone(zone).plusSeconds(1).withNano(0);
+ } else {
+ nextTick = now.atZone(zone).plusMinutes(1).withSecond(0).withNano(0);
}
+
+ long millisUntilNextTick = Duration.between(now, nextTick.toInstant()).toMillis();
+ if (millisUntilNextTick <= 0) {
+ // This should never happen, but if it does, then tick again in a second.
+ millisUntilNextTick = 1000;
+ }
+
+ postDelayed(this, millisUntilNextTick);
}
};
@@ -519,8 +533,7 @@
mHasSeconds = DateFormat.hasSeconds(mFormat);
if (mShouldRunTicker && hadSeconds != mHasSeconds) {
- if (hadSeconds) getHandler().removeCallbacks(mTicker);
- else mTicker.run();
+ mTicker.run();
}
}
@@ -557,14 +570,10 @@
if (!mShouldRunTicker && isVisible) {
mShouldRunTicker = true;
- if (mHasSeconds) {
- mTicker.run();
- } else {
- onTimeChanged();
- }
+ mTicker.run();
} else if (mShouldRunTicker && !isVisible) {
mShouldRunTicker = false;
- getHandler().removeCallbacks(mTicker);
+ removeCallbacks(mTicker);
}
}
@@ -592,7 +601,6 @@
private void registerReceiver() {
final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
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/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index 9bec505..ec99c95 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -17,6 +17,7 @@
package com.android.internal.app;
import android.hardware.soundtrigger.SoundTrigger;
+import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordRejectedResult;
/**
@@ -29,8 +30,11 @@
* @param recognitionEvent Object containing data relating to the
* keyphrase recognition event such as keyphrase
* extras.
+ * @param result Successful detection result payload.
*/
- void onKeyphraseDetected(in SoundTrigger.KeyphraseRecognitionEvent recognitionEvent);
+ void onKeyphraseDetected(
+ in SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
+ in HotwordDetectedResult result);
/**
* Called when a generic sound trigger event is witnessed.
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/java/com/android/internal/graphics/palette/CelebiQuantizer.java b/core/java/com/android/internal/graphics/palette/CelebiQuantizer.java
index de6bf20..454fd00 100644
--- a/core/java/com/android/internal/graphics/palette/CelebiQuantizer.java
+++ b/core/java/com/android/internal/graphics/palette/CelebiQuantizer.java
@@ -19,26 +19,32 @@
import java.util.List;
/**
- * An implementation of Celebi's WSM quantizer, or, a Kmeans quantizer that starts with centroids
- * from a Wu quantizer to ensure 100% reproducible and quality results, and has some optimizations
- * to the Kmeans algorithm.
- *
+ * An implementation of Celebi's quantization method.
* See Celebi 2011, “Improving the Performance of K-Means for Color Quantization”
+ *
+ * First, Wu's quantizer runs. The results are used as starting points for a subsequent Kmeans
+ * run. Using Wu's quantizer ensures 100% reproducible quantization results, because the starting
+ * centroids are always the same. It also ensures high quality results, Wu is a box-cutting
+ * quantization algorithm, much like medican color cut. It minimizes variance, much like Kmeans.
+ * Wu is shown to be the highest quality box-cutting quantization algorithm.
+ *
+ * Second, a Kmeans quantizer tweaked for performance is run. Celebi calls this a weighted
+ * square means quantizer, or WSMeans. Optimizations include operating on a map of image pixels
+ * rather than all image pixels, and avoiding excess color distance calculations by using a
+ * matrix and geometrical properties to know when there won't be any cluster closer to a pixel.
*/
public class CelebiQuantizer implements Quantizer {
private List<Palette.Swatch> mSwatches;
- public CelebiQuantizer() { }
+ public CelebiQuantizer() {
+ }
@Override
public void quantize(int[] pixels, int maxColors) {
- WuQuantizer wu = new WuQuantizer(pixels, maxColors);
+ WuQuantizer wu = new WuQuantizer();
wu.quantize(pixels, maxColors);
- List<Palette.Swatch> wuSwatches = wu.getQuantizedColors();
- LABCentroid labCentroidProvider = new LABCentroid();
- WSMeansQuantizer kmeans =
- new WSMeansQuantizer(WSMeansQuantizer.createStartingCentroids(labCentroidProvider,
- wuSwatches), labCentroidProvider, pixels, maxColors);
+ WSMeansQuantizer kmeans = new WSMeansQuantizer(wu.getColors(), new LABPointProvider(),
+ wu.inputPixelToCount());
kmeans.quantize(pixels, maxColors);
mSwatches = kmeans.getQuantizedColors();
}
diff --git a/core/java/com/android/internal/graphics/palette/LABCentroid.java b/core/java/com/android/internal/graphics/palette/LABPointProvider.java
similarity index 92%
rename from core/java/com/android/internal/graphics/palette/LABCentroid.java
rename to core/java/com/android/internal/graphics/palette/LABPointProvider.java
index 408cf1f..21a2212 100644
--- a/core/java/com/android/internal/graphics/palette/LABCentroid.java
+++ b/core/java/com/android/internal/graphics/palette/LABPointProvider.java
@@ -26,11 +26,11 @@
* in L*a*b* space, also known as deltaE, is a universally accepted standard across industries
* and worldwide.
*/
-public class LABCentroid implements CentroidProvider {
+public class LABPointProvider implements PointProvider {
final ColorSpace.Connector mRgbToLab;
final ColorSpace.Connector mLabToRgb;
- public LABCentroid() {
+ public LABPointProvider() {
mRgbToLab = ColorSpace.connect(
ColorSpace.get(ColorSpace.Named.SRGB),
ColorSpace.get(ColorSpace.Named.CIE_LAB));
@@ -39,7 +39,7 @@
}
@Override
- public float[] getCentroid(int color) {
+ public float[] fromInt(int color) {
float r = Color.red(color) / 255.f;
float g = Color.green(color) / 255.f;
float b = Color.blue(color) / 255.f;
@@ -49,7 +49,7 @@
}
@Override
- public int getColor(float[] centroid) {
+ public int toInt(float[] centroid) {
float[] rgb = mLabToRgb.transform(centroid);
int color = Color.rgb(rgb[0], rgb[1], rgb[2]);
return color;
diff --git a/core/java/com/android/internal/graphics/palette/Mean.java b/core/java/com/android/internal/graphics/palette/Mean.java
deleted file mode 100644
index bde0363..0000000
--- a/core/java/com/android/internal/graphics/palette/Mean.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics.palette;
-
-import java.util.Random;
-
-/**
- * Represents a centroid in Kmeans algorithms.
- */
-public class Mean {
- public float[] center;
-
- /**
- * Constructor.
- *
- * @param upperBound maximum value of a dimension in the space Kmeans is optimizing in
- * @param random used to generate a random center
- */
- Mean(int upperBound, Random random) {
- center =
- new float[]{
- random.nextInt(upperBound + 1), random.nextInt(upperBound + 1),
- random.nextInt(upperBound + 1)
- };
- }
-
- Mean(float[] center) {
- this.center = center;
- }
-}
diff --git a/core/java/com/android/internal/graphics/palette/MeanBucket.java b/core/java/com/android/internal/graphics/palette/MeanBucket.java
deleted file mode 100644
index ae8858a..0000000
--- a/core/java/com/android/internal/graphics/palette/MeanBucket.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics.palette;
-
-import java.util.HashSet;
-import java.util.Set;
-
-class MeanBucket {
- float[] mTotal = {0.f, 0.f, 0.f};
- int mCount = 0;
- Set<Integer> mColors = new HashSet<>();
-
- void add(float[] colorAsDoubles, int color, int colorCount) {
- assert (colorAsDoubles.length == 3);
- mColors.add(color);
- mTotal[0] += (colorAsDoubles[0] * colorCount);
- mTotal[1] += (colorAsDoubles[1] * colorCount);
- mTotal[2] += (colorAsDoubles[2] * colorCount);
- mCount += colorCount;
- }
-
- float[] getCentroid() {
- if (mCount == 0) {
- return null;
- }
- return new float[]{mTotal[0] / mCount, mTotal[1] / mCount, mTotal[2] / mCount};
- }
-}
diff --git a/core/java/com/android/internal/graphics/palette/CentroidProvider.java b/core/java/com/android/internal/graphics/palette/PointProvider.java
similarity index 62%
rename from core/java/com/android/internal/graphics/palette/CentroidProvider.java
rename to core/java/com/android/internal/graphics/palette/PointProvider.java
index 5fcfcba..017adeb 100644
--- a/core/java/com/android/internal/graphics/palette/CentroidProvider.java
+++ b/core/java/com/android/internal/graphics/palette/PointProvider.java
@@ -18,21 +18,18 @@
import android.annotation.ColorInt;
-interface CentroidProvider {
- /**
- * @return 3 dimensions representing the color
- */
- float[] getCentroid(@ColorInt int color);
+/**
+ * Interface that allows quantizers to have a plug-and-play interface for experimenting with
+ * quantization in different color spaces.
+ */
+public interface PointProvider {
+ /** Convert a color to 3 coordinates representing the color in a color space. */
+ float[] fromInt(@ColorInt int argb);
- /**
- * @param centroid 3 dimensions representing the color
- * @return 32-bit ARGB representation
- */
+ /** Convert 3 coordinates in the color space into a color */
@ColorInt
- int getColor(float[] centroid);
+ int toInt(float[] point);
- /**
- * Distance between two centroids.
- */
+ /** Find the distance between two colosrin the color space */
float distance(float[] a, float[] b);
}
diff --git a/core/java/com/android/internal/graphics/palette/QuantizerMap.java b/core/java/com/android/internal/graphics/palette/QuantizerMap.java
new file mode 100644
index 0000000..6b60f61
--- /dev/null
+++ b/core/java/com/android/internal/graphics/palette/QuantizerMap.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.graphics.palette;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Converts a set of pixels/colors into a map with keys of unique colors, and values of the count
+ * of the unique color in the original set of pixels.
+ *
+ * This allows other quantizers to get a significant speed boost by simply running this quantizer,
+ * and then performing operations using the map, rather than for each pixel.
+ */
+public final class QuantizerMap implements Quantizer {
+ private HashMap<Integer, Integer> mColorToCount;
+ private Palette mPalette;
+
+ @Override
+ public void quantize(@NonNull int[] pixels, int colorCount) {
+ final HashMap<Integer, Integer> colorToCount = new HashMap<>();
+ for (int pixel : pixels) {
+ colorToCount.merge(pixel, 1, Integer::sum);
+ }
+ mColorToCount = colorToCount;
+
+ List<Palette.Swatch> swatches = new ArrayList<>();
+ for (Map.Entry<Integer, Integer> entry : colorToCount.entrySet()) {
+ swatches.add(new Palette.Swatch(entry.getKey(), entry.getValue()));
+ }
+ mPalette = Palette.from(swatches);
+ }
+
+ @Override
+ public List<Palette.Swatch> getQuantizedColors() {
+ return mPalette.getSwatches();
+ }
+
+ @Nullable
+ public Map<Integer, Integer> getColorToCount() {
+ return mColorToCount;
+ }
+}
diff --git a/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java b/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java
index 1d865c2..19ed8d0 100644
--- a/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java
+++ b/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java
@@ -16,18 +16,20 @@
package com.android.internal.graphics.palette;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
-
/**
- * A color quantizer based on the Kmeans algorithm.
+ * A color quantizer based on the Kmeans algorithm. Prefer using QuantizerCelebi.
*
* This is an implementation of Kmeans based on Celebi's 2011 paper,
* "Improving the Performance of K-Means for Color Quantization". In the paper, this algorithm is
@@ -36,253 +38,237 @@
* well as indexing colors by their count, thus minimizing the number of points to move around.
*
* Celebi's paper also stabilizes results and guarantees high quality by using starting centroids
- * from Wu's quantization algorithm. See CelebiQuantizer for more info.
+ * from Wu's quantization algorithm. See QuantizerCelebi for more info.
*/
-public class WSMeansQuantizer implements Quantizer {
- Mean[] mMeans;
- private final Map<Integer, Integer> mCountByColor = new HashMap<>();
- private final Map<Integer, Integer> mMeanIndexByColor = new HashMap<>();
- private final Set<Integer> mUniqueColors = new HashSet<>();
- private final List<Palette.Swatch> mSwatches = new ArrayList<>();
- private final CentroidProvider mCentroidProvider;
+public final class WSMeansQuantizer implements Quantizer {
+ private static final String TAG = "QuantizerWsmeans";
+ private static final boolean DEBUG = false;
+ private static final int MAX_ITERATIONS = 10;
+ // Points won't be moved to a closer cluster, if the closer cluster is within
+ // this distance. 3.0 used because L*a*b* delta E < 3 is considered imperceptible.
+ private static final float MIN_MOVEMENT_DISTANCE = 3.0f;
- public WSMeansQuantizer(
- float[][] means, CentroidProvider centroidProvider, int[] pixels, int maxColors) {
- if (pixels == null) {
- pixels = new int[]{};
- }
- mCentroidProvider = centroidProvider;
- mMeans = new Mean[maxColors];
- for (int i = 0; i < means.length; i++) {
- mMeans[i] = new Mean(means[i]);
+ private final PointProvider mPointProvider;
+ private @Nullable Map<Integer, Integer> mInputPixelToCount;
+ private float[][] mClusters;
+ private int[] mClusterPopulations;
+ private float[][] mPoints;
+ private int[] mPixels;
+ private int[] mClusterIndices;
+ private int[][] mIndexMatrix = {};
+ private float[][] mDistanceMatrix = {};
+
+ private Palette mPalette;
+
+ public WSMeansQuantizer(int[] inClusters, PointProvider pointProvider,
+ @Nullable Map<Integer, Integer> inputPixelToCount) {
+ mPointProvider = pointProvider;
+
+ mClusters = new float[inClusters.length][3];
+ int index = 0;
+ for (int cluster : inClusters) {
+ float[] point = pointProvider.fromInt(cluster);
+ mClusters[index++] = point;
}
- if (maxColors > means.length) {
- // Always initialize Random with the same seed. Ensures the results of quantization
- // are consistent, even when random centroids are required.
- Random random = new Random(0x42688);
- int randomMeansToCreate = maxColors - means.length;
- for (int i = 0; i < randomMeansToCreate; i++) {
- mMeans[means.length + i] = new Mean(100, random);
- }
- }
-
- for (int pixel : pixels) {
- // These are pixels from the bitmap that is being quantized.
- // Depending on the bitmap & downscaling, it may have pixels that are less than opaque
- // Ignore those pixels.
- ///
- // Note: they don't _have_ to be ignored, for example, we could instead turn them
- // opaque. Traditionally, including outside Android, quantizers ignore transparent
- // pixels, so that strategy was chosen.
- int alpha = (pixel >> 24) & 0xff;
- if (alpha < 255) {
- continue;
- }
- Integer currentCount = mCountByColor.get(pixel);
- if (currentCount == null) {
- currentCount = 0;
- mUniqueColors.add(pixel);
- }
- mCountByColor.put(pixel, currentCount + 1);
- }
- for (int color : mUniqueColors) {
- int closestMeanIndex = -1;
- double closestMeanDistance = -1;
- float[] centroid = mCentroidProvider.getCentroid(color);
- for (int i = 0; i < mMeans.length; i++) {
- double distance = mCentroidProvider.distance(centroid, mMeans[i].center);
- if (closestMeanIndex == -1 || distance < closestMeanDistance) {
- closestMeanIndex = i;
- closestMeanDistance = distance;
- }
- }
- mMeanIndexByColor.put(color, closestMeanIndex);
- }
-
- if (pixels.length == 0) {
- return;
- }
-
- predict(maxColors, 0);
- }
-
- /** Create starting centroids for K-means from a set of colors. */
- public static float[][] createStartingCentroids(CentroidProvider centroidProvider,
- List<Palette.Swatch> swatches) {
- float[][] startingCentroids = new float[swatches.size()][];
- for (int i = 0; i < swatches.size(); i++) {
- startingCentroids[i] = centroidProvider.getCentroid(swatches.get(i).getInt());
- }
- return startingCentroids;
- }
-
- /** Create random starting centroids for K-means. */
- public static float[][] randomMeans(int maxColors, int upperBound) {
- float[][] means = new float[maxColors][];
-
- // Always initialize Random with the same seed. Ensures the results of quantization
- // are consistent, even when random centroids are required.
- Random random = new Random(0x42688);
- for (int i = 0; i < maxColors; i++) {
- means[i] = new Mean(upperBound, random).center;
- }
- return means;
- }
-
-
- @Override
- public void quantize(int[] pixels, int maxColors) {
-
+ mInputPixelToCount = inputPixelToCount;
}
@Override
public List<Palette.Swatch> getQuantizedColors() {
- return mSwatches;
+ return mPalette.getSwatches();
}
- private void predict(int maxColors, int iterationsCompleted) {
- double[][] centroidDistance = new double[maxColors][maxColors];
- for (int i = 0; i <= maxColors; i++) {
- for (int j = i + 1; j < maxColors; j++) {
- float[] meanI = mMeans[i].center;
- float[] meanJ = mMeans[j].center;
- double distance = mCentroidProvider.distance(meanI, meanJ);
- centroidDistance[i][j] = distance;
- centroidDistance[j][i] = distance;
+ @Override
+ public void quantize(@NonNull int[] pixels, int maxColors) {
+ assert (pixels.length > 0);
+
+ if (mInputPixelToCount == null) {
+ QuantizerMap mapQuantizer = new QuantizerMap();
+ mapQuantizer.quantize(pixels, maxColors);
+ mInputPixelToCount = mapQuantizer.getColorToCount();
+ }
+
+ mPoints = new float[mInputPixelToCount.size()][3];
+ mPixels = new int[mInputPixelToCount.size()];
+ int index = 0;
+ for (int pixel : mInputPixelToCount.keySet()) {
+ mPixels[index] = pixel;
+ mPoints[index] = mPointProvider.fromInt(pixel);
+ index++;
+ }
+ if (mClusters.length > 0) {
+ // This implies that the constructor was provided starting clusters. If that was the
+ // case, we limit the number of clusters to the number of starting clusters and don't
+ // initialize random clusters.
+ maxColors = Math.min(maxColors, mClusters.length);
+ }
+ maxColors = Math.min(maxColors, mPoints.length);
+
+ initializeClusters(maxColors);
+ for (int i = 0; i < MAX_ITERATIONS; i++) {
+ calculateClusterDistances(maxColors);
+ if (!reassignPoints(maxColors)) {
+ break;
}
- }
-
- // Construct a K×K matrix M in which row i is a permutation of
- // 1,2,…,K that represents the clusters in increasing order of
- // distance of their centers from ci;
- int[][] distanceMatrix = new int[maxColors][maxColors];
- for (int i = 0; i < maxColors; i++) {
- double[] distancesFromIToAnotherMean = centroidDistance[i];
- double[] sortedByDistanceAscending = distancesFromIToAnotherMean.clone();
- Arrays.sort(sortedByDistanceAscending);
- int[] outputRow = new int[maxColors];
- for (int j = 0; j < maxColors; j++) {
- outputRow[j] = findIndex(distancesFromIToAnotherMean, sortedByDistanceAscending[j]);
- }
- distanceMatrix[i] = outputRow;
- }
-
- // for (i=1;i≤N′;i=i+ 1) do
- // Let Sp be the cluster that xi was assigned to in the previous
- // iteration;
- // p=m[i];
- // min_dist=prev_dist=jjxi−cpjj2;
- boolean anyColorMoved = false;
- for (int intColor : mUniqueColors) {
- float[] color = mCentroidProvider.getCentroid(intColor);
- int indexOfCurrentMean = mMeanIndexByColor.get(intColor);
- Mean currentMean = mMeans[indexOfCurrentMean];
- double minDistance = mCentroidProvider.distance(color, currentMean.center);
- for (int j = 1; j < maxColors; j++) {
- int indexOfClusterFromCurrentToJ = distanceMatrix[indexOfCurrentMean][j];
- double distanceBetweenJAndCurrent =
- centroidDistance[indexOfCurrentMean][indexOfClusterFromCurrentToJ];
- if (distanceBetweenJAndCurrent >= (4 * minDistance)) {
- break;
- }
- double distanceBetweenJAndColor = mCentroidProvider.distance(mMeans[j].center,
- color);
- if (distanceBetweenJAndColor < minDistance) {
- minDistance = distanceBetweenJAndColor;
- mMeanIndexByColor.remove(intColor);
- mMeanIndexByColor.put(intColor, j);
- anyColorMoved = true;
- }
- }
- }
-
- List<MeanBucket> buckets = new ArrayList<>();
- for (int i = 0; i < maxColors; i++) {
- buckets.add(new MeanBucket());
- }
-
- for (int intColor : mUniqueColors) {
- int meanIndex = mMeanIndexByColor.get(intColor);
- MeanBucket meanBucket = buckets.get(meanIndex);
- meanBucket.add(mCentroidProvider.getCentroid(intColor), intColor,
- mCountByColor.get(intColor));
+ recalculateClusterCenters(maxColors);
}
List<Palette.Swatch> swatches = new ArrayList<>();
- boolean done = !anyColorMoved && iterationsCompleted > 0 || iterationsCompleted >= 100;
- if (done) {
- for (int i = 0; i < buckets.size(); i++) {
- MeanBucket a = buckets.get(i);
- if (a.mCount <= 0) {
- continue;
- }
- List<MeanBucket> bucketsToMerge = new ArrayList<>();
- for (int j = i + 1; j < buckets.size(); j++) {
- MeanBucket b = buckets.get(j);
- if (b.mCount == 0) {
- continue;
- }
- float[] bCentroid = b.getCentroid();
- assert (a.mCount > 0);
- assert (a.getCentroid() != null);
-
- assert (bCentroid != null);
- if (mCentroidProvider.distance(a.getCentroid(), b.getCentroid()) < 5) {
- bucketsToMerge.add(b);
- }
- }
-
- for (MeanBucket bucketToMerge : bucketsToMerge) {
- float[] centroid = bucketToMerge.getCentroid();
- a.add(centroid, mCentroidProvider.getColor(centroid), bucketToMerge.mCount);
- buckets.remove(bucketToMerge);
- }
- }
-
- for (MeanBucket bucket : buckets) {
- float[] centroid = bucket.getCentroid();
- if (centroid == null) {
- continue;
- }
-
- int rgb = mCentroidProvider.getColor(centroid);
- swatches.add(new Palette.Swatch(rgb, bucket.mCount));
- mSwatches.clear();
- mSwatches.addAll(swatches);
- }
- } else {
- List<MeanBucket> emptyBuckets = new ArrayList<>();
- for (int i = 0; i < buckets.size(); i++) {
- MeanBucket bucket = buckets.get(i);
- if ((bucket.getCentroid() == null) || (bucket.mCount == 0)) {
- emptyBuckets.add(bucket);
- for (Integer color : mUniqueColors) {
- int meanIndex = mMeanIndexByColor.get(color);
- if (meanIndex > i) {
- mMeanIndexByColor.put(color, meanIndex--);
- }
- }
- }
- }
-
- Mean[] newMeans = new Mean[buckets.size()];
- for (int i = 0; i < buckets.size(); i++) {
- float[] centroid = buckets.get(i).getCentroid();
- newMeans[i] = new Mean(centroid);
- }
-
- predict(buckets.size(), iterationsCompleted + 1);
+ for (int i = 0; i < maxColors; i++) {
+ float[] cluster = mClusters[i];
+ int colorInt = mPointProvider.toInt(cluster);
+ swatches.add(new Palette.Swatch(colorInt, mClusterPopulations[i]));
}
-
+ mPalette = Palette.from(swatches);
}
- private static int findIndex(double[] list, double element) {
- for (int i = 0; i < list.length; i++) {
- if (list[i] == element) {
- return i;
+
+ private void initializeClusters(int maxColors) {
+ boolean hadInputClusters = mClusters.length > 0;
+ if (!hadInputClusters) {
+ int additionalClustersNeeded = maxColors - mClusters.length;
+ if (DEBUG) {
+ Log.d(TAG, "have " + mClusters.length + " clusters, want " + maxColors
+ + " results, so need " + additionalClustersNeeded + " additional clusters");
+ }
+
+ Random random = new Random(0x42688);
+ List<float[]> additionalClusters = new ArrayList<>(additionalClustersNeeded);
+ Set<Integer> clusterIndicesUsed = new HashSet<>();
+ for (int i = 0; i < additionalClustersNeeded; i++) {
+ int index = random.nextInt(mPoints.length);
+ while (clusterIndicesUsed.contains(index)
+ && clusterIndicesUsed.size() < mPoints.length) {
+ index = random.nextInt(mPoints.length);
+ }
+ clusterIndicesUsed.add(index);
+ additionalClusters.add(mPoints[index]);
+ }
+
+ float[][] newClusters = (float[][]) additionalClusters.toArray();
+ float[][] clusters = Arrays.copyOf(mClusters, maxColors);
+ System.arraycopy(newClusters, 0, clusters, clusters.length, newClusters.length);
+ mClusters = clusters;
+ }
+
+ mClusterIndices = new int[mPixels.length];
+ mClusterPopulations = new int[mPixels.length];
+ Random random = new Random(0x42688);
+ for (int i = 0; i < mPixels.length; i++) {
+ int clusterIndex = random.nextInt(maxColors);
+ mClusterIndices[i] = clusterIndex;
+ mClusterPopulations[i] = mInputPixelToCount.get(mPixels[i]);
+ }
+ }
+
+ void calculateClusterDistances(int maxColors) {
+ if (mDistanceMatrix.length != maxColors) {
+ mDistanceMatrix = new float[maxColors][maxColors];
+ }
+
+ for (int i = 0; i <= maxColors; i++) {
+ for (int j = i + 1; j < maxColors; j++) {
+ float distance = mPointProvider.distance(mClusters[i], mClusters[j]);
+ mDistanceMatrix[j][i] = distance;
+ mDistanceMatrix[i][j] = distance;
}
}
- throw new IllegalArgumentException("Element not in list");
+
+ if (mIndexMatrix.length != maxColors) {
+ mIndexMatrix = new int[maxColors][maxColors];
+ }
+
+ for (int i = 0; i < maxColors; i++) {
+ ArrayList<Distance> distances = new ArrayList<>(maxColors);
+ for (int index = 0; index < maxColors; index++) {
+ distances.add(new Distance(index, mDistanceMatrix[i][index]));
+ }
+ distances.sort(
+ (a, b) -> Float.compare(a.getDistance(), b.getDistance()));
+
+ for (int j = 0; j < maxColors; j++) {
+ mIndexMatrix[i][j] = distances.get(j).getIndex();
+ }
+ }
+ }
+
+ boolean reassignPoints(int maxColors) {
+ boolean colorMoved = false;
+ for (int i = 0; i < mPoints.length; i++) {
+ float[] point = mPoints[i];
+ int previousClusterIndex = mClusterIndices[i];
+ float[] previousCluster = mClusters[previousClusterIndex];
+ float previousDistance = mPointProvider.distance(point, previousCluster);
+
+ float minimumDistance = previousDistance;
+ int newClusterIndex = -1;
+ for (int j = 1; j < maxColors; j++) {
+ int t = mIndexMatrix[previousClusterIndex][j];
+ if (mDistanceMatrix[previousClusterIndex][t] >= 4 * previousDistance) {
+ // Triangle inequality proves there's can be no closer center.
+ break;
+ }
+ float distance = mPointProvider.distance(point, mClusters[t]);
+ if (distance < minimumDistance) {
+ minimumDistance = distance;
+ newClusterIndex = t;
+ }
+ }
+ if (newClusterIndex != -1) {
+ float distanceChange = (float)
+ Math.abs((Math.sqrt(minimumDistance) - Math.sqrt(previousDistance)));
+ if (distanceChange > MIN_MOVEMENT_DISTANCE) {
+ colorMoved = true;
+ mClusterIndices[i] = newClusterIndex;
+ }
+ }
+ }
+ return colorMoved;
+ }
+
+ void recalculateClusterCenters(int maxColors) {
+ mClusterPopulations = new int[maxColors];
+ float[] aSums = new float[maxColors];
+ float[] bSums = new float[maxColors];
+ float[] cSums = new float[maxColors];
+ for (int i = 0; i < mPoints.length; i++) {
+ int clusterIndex = mClusterIndices[i];
+ float[] point = mPoints[i];
+ int pixel = mPixels[i];
+ int count = mInputPixelToCount.get(pixel);
+ mClusterPopulations[clusterIndex] += count;
+ aSums[clusterIndex] += point[0] * count;
+ bSums[clusterIndex] += point[1] * count;
+ cSums[clusterIndex] += point[2] * count;
+
+ }
+ for (int i = 0; i < maxColors; i++) {
+ int count = mClusterPopulations[i];
+ float aSum = aSums[i];
+ float bSum = bSums[i];
+ float cSum = cSums[i];
+ mClusters[i][0] = aSum / count;
+ mClusters[i][1] = bSum / count;
+ mClusters[i][2] = cSum / count;
+ }
+ }
+
+ private static class Distance {
+ private final int mIndex;
+ private final float mDistance;
+
+ int getIndex() {
+ return mIndex;
+ }
+
+ float getDistance() {
+ return mDistance;
+ }
+
+ Distance(int index, float distance) {
+ mIndex = index;
+ mDistance = distance;
+ }
}
}
diff --git a/core/java/com/android/internal/graphics/palette/WuQuantizer.java b/core/java/com/android/internal/graphics/palette/WuQuantizer.java
index a2652ea..1cd0d72 100644
--- a/core/java/com/android/internal/graphics/palette/WuQuantizer.java
+++ b/core/java/com/android/internal/graphics/palette/WuQuantizer.java
@@ -16,431 +16,447 @@
package com.android.internal.graphics.palette;
+import static java.lang.System.arraycopy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Color;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
-// All reference Wu implementations are based on the original C code by Wu.
-// Comments on methods are the same as in the original implementation, and the comment below
-// is the original class header.
/**
- * Wu's Color Quantizer (v. 2) (see Graphics Gems vol. II, pp. 126-133) Author: Xiaolin Wu
+ * Wu's quantization algorithm is a box-cut quantizer that minimizes variance. It takes longer to
+ * run than, say, median color cut, but provides the highest quality results currently known.
*
- * <p>Algorithm: Greedy orthogonal bipartition of RGB space for variance minimization aided by
- * inclusion-exclusion tricks. For speed no nearest neighbor search is done. Slightly better
- * performance can be expected by more sophisticated but more expensive versions.
+ * Prefer `QuantizerCelebi`: coupled with Kmeans, this provides the best-known results for image
+ * quantization.
+ *
+ * Seemingly all Wu implementations are based off of one C code snippet that cites a book from 1992
+ * Graphics Gems vol. II, pp. 126-133. As a result, it is very hard to understand the mechanics of
+ * the algorithm, beyond the commentary provided in the C code. Comments on the methods of this
+ * class are avoided in favor of finding another implementation and reading the commentary there,
+ * avoiding perpetuating the same incomplete and somewhat confusing commentary here.
*/
-public class WuQuantizer implements Quantizer {
- private static final int MAX_COLORS = 256;
- private static final int RED = 2;
- private static final int GREEN = 1;
- private static final int BLUE = 0;
+public final class WuQuantizer implements Quantizer {
+ // A histogram of all the input colors is constructed. It has the shape of a
+ // cube. The cube would be too large if it contained all 16 million colors:
+ // historical best practice is to use 5 bits of the 8 in each channel,
+ // reducing the histogram to a volume of ~32,000.
+ private static final int BITS = 5;
+ private static final int MAX_INDEX = 32;
+ private static final int SIDE_LENGTH = 33;
+ private static final int TOTAL_SIZE = 35937;
- private static final int QUANT_SIZE = 33;
- private final List<Palette.Swatch> mSwatches = new ArrayList<>();
+ private int[] mWeights;
+ private int[] mMomentsR;
+ private int[] mMomentsG;
+ private int[] mMomentsB;
+ private double[] mMoments;
+ private Box[] mCubes;
+ private Palette mPalette;
+ private int[] mColors;
+ private Map<Integer, Integer> mInputPixelToCount;
@Override
public List<Palette.Swatch> getQuantizedColors() {
- return mSwatches;
- }
-
- private static final class Box {
- int mR0; /* min value, exclusive */
- int mR1; /* max value, inclusive */
- int mG0;
- int mG1;
- int mB0;
- int mB1;
- int mVol;
- }
-
- private final int mSize; /* image size, in bytes. */
- private int mMaxColors;
- private int[] mQadd;
- private final int[] mPixels;
-
- private final double[][][] mM2 = new double[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE];
- private final long[][][] mWt = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE];
- private final long[][][] mMr = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE];
- private final long[][][] mMg = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE];
- private final long[][][] mMb = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE];
-
- public WuQuantizer(int[] pixels, int maxColorCount) {
- if (pixels == null) {
- pixels = new int[]{};
- }
- this.mPixels = pixels;
- this.mSize = pixels.length;
+ return mPalette.getSwatches();
}
@Override
- public void quantize(int[] colors, int maxColorCount) {
- // All of the sample Wu implementations are reimplementations of a snippet of C code from
- // the early 90s. They all cap the maximum # of colors at 256, and it is impossible to tell
- // if this is a requirement, a consequence of QUANT_SIZE, or arbitrary.
- //
- // Also, the number of maximum colors should be capped at the number of pixels - otherwise,
- // If extraction is run on a set of pixels whose count is less than max colors,
- // then colors.length < max colors, and accesses to colors[index] throw an
- // ArrayOutOfBoundsException.
- this.mMaxColors = Math.min(Math.min(MAX_COLORS, maxColorCount), colors.length);
- Box[] cube = new Box[mMaxColors];
- int red, green, blue;
+ public void quantize(@NonNull int[] pixels, int colorCount) {
+ assert (pixels.length > 0);
- int next, i, k;
- long weight;
- double[] vv = new double[mMaxColors];
- double temp;
-
- compute3DHistogram(mWt, mMr, mMg, mMb, mM2);
- computeMoments(mWt, mMr, mMg, mMb, mM2);
-
- for (i = 0; i < mMaxColors; i++) {
- cube[i] = new Box();
- }
-
- cube[0].mR0 = cube[0].mG0 = cube[0].mB0 = 0;
- cube[0].mR1 = cube[0].mG1 = cube[0].mB1 = QUANT_SIZE - 1;
- next = 0;
-
- for (i = 1; i < mMaxColors; ++i) {
- if (cut(cube[next], cube[i])) {
- vv[next] = (cube[next].mVol > 1) ? getVariance(cube[next]) : 0.0f;
- vv[i] = (cube[i].mVol > 1) ? getVariance(cube[i]) : 0.0f;
- } else {
- vv[next] = 0.0f;
- i--;
+ QuantizerMap quantizerMap = new QuantizerMap();
+ quantizerMap.quantize(pixels, colorCount);
+ mInputPixelToCount = quantizerMap.getColorToCount();
+ // Extraction should not be run on using a color count higher than the number of colors
+ // in the pixels. The algorithm doesn't expect that to be the case, unexpected results and
+ // exceptions may occur.
+ Set<Integer> uniqueColors = mInputPixelToCount.keySet();
+ if (uniqueColors.size() <= colorCount) {
+ mColors = new int[mInputPixelToCount.keySet().size()];
+ int index = 0;
+ for (int color : uniqueColors) {
+ mColors[index++] = color;
}
- next = 0;
- temp = vv[0];
- for (k = 1; k <= i; ++k) {
- if (vv[k] > temp) {
- temp = vv[k];
- next = k;
- }
- }
- if (temp <= 0.0f) {
- break;
- }
- }
-
- for (k = 0; k < mMaxColors; ++k) {
- weight = getVolume(cube[k], mWt);
- if (weight > 0) {
- red = (int) (getVolume(cube[k], mMr) / weight);
- green = (int) (getVolume(cube[k], mMg) / weight);
- blue = (int) (getVolume(cube[k], mMb) / weight);
- colors[k] = (255 << 24) | (red << 16) | (green << 8) | blue;
- } else {
- colors[k] = 0;
- }
- }
-
- int bitsPerPixel = 0;
- while ((1 << bitsPerPixel) < mMaxColors) {
- bitsPerPixel++;
+ } else {
+ constructHistogram(mInputPixelToCount);
+ createMoments();
+ CreateBoxesResult createBoxesResult = createBoxes(colorCount);
+ mColors = createResult(createBoxesResult.mResultCount);
}
List<Palette.Swatch> swatches = new ArrayList<>();
- for (int l = 0; l < k; l++) {
- int pixel = colors[l];
- if (pixel == 0) {
- continue;
- }
- swatches.add(new Palette.Swatch(pixel, 0));
+ for (int color : mColors) {
+ swatches.add(new Palette.Swatch(color, 0));
}
- mSwatches.clear();
- mSwatches.addAll(swatches);
+ mPalette = Palette.from(swatches);
}
- /* Histogram is in elements 1..HISTSIZE along each axis,
- * element 0 is for base or marginal value
- * NB: these must start out 0!
- */
- private void compute3DHistogram(
- long[][][] vwt, long[][][] vmr, long[][][] vmg, long[][][] vmb, double[][][] m2) {
- // build 3-D color histogram of counts, r/g/b, and c^2
- int r, g, b;
- int i;
- int inr;
- int ing;
- int inb;
- int[] table = new int[256];
+ @Nullable
+ public int[] getColors() {
+ return mColors;
+ }
- for (i = 0; i < 256; i++) {
- table[i] = i * i;
- }
+ /** Keys are color ints, values are the number of pixels in the image matching that color int */
+ @Nullable
+ public Map<Integer, Integer> inputPixelToCount() {
+ return mInputPixelToCount;
+ }
- mQadd = new int[mSize];
+ private static int getIndex(int r, int g, int b) {
+ return (r << 10) + (r << 6) + (g << 5) + r + g + b;
+ }
- for (i = 0; i < mSize; ++i) {
- int rgb = mPixels[i];
- // Skip less than opaque pixels. They're not meaningful in the context of palette
- // generation for UI schemes.
- if ((rgb >>> 24) < 0xff) {
- continue;
- }
- r = ((rgb >> 16) & 0xff);
- g = ((rgb >> 8) & 0xff);
- b = (rgb & 0xff);
- inr = (r >> 3) + 1;
- ing = (g >> 3) + 1;
- inb = (b >> 3) + 1;
- mQadd[i] = (inr << 10) + (inr << 6) + inr + (ing << 5) + ing + inb;
- /*[inr][ing][inb]*/
- ++vwt[inr][ing][inb];
- vmr[inr][ing][inb] += r;
- vmg[inr][ing][inb] += g;
- vmb[inr][ing][inb] += b;
- m2[inr][ing][inb] += table[r] + table[g] + table[b];
+ private void constructHistogram(Map<Integer, Integer> pixels) {
+ mWeights = new int[TOTAL_SIZE];
+ mMomentsR = new int[TOTAL_SIZE];
+ mMomentsG = new int[TOTAL_SIZE];
+ mMomentsB = new int[TOTAL_SIZE];
+ mMoments = new double[TOTAL_SIZE];
+
+ for (Map.Entry<Integer, Integer> pair : pixels.entrySet()) {
+ int pixel = pair.getKey();
+ int count = pair.getValue();
+ int red = Color.red(pixel);
+ int green = Color.green(pixel);
+ int blue = Color.blue(pixel);
+ int bitsToRemove = 8 - BITS;
+ int iR = (red >> bitsToRemove) + 1;
+ int iG = (green >> bitsToRemove) + 1;
+ int iB = (blue >> bitsToRemove) + 1;
+ int index = getIndex(iR, iG, iB);
+ mWeights[index] += count;
+ mMomentsR[index] += (red * count);
+ mMomentsG[index] += (green * count);
+ mMomentsB[index] += (blue * count);
+ mMoments[index] += (count * ((red * red) + (green * green) + (blue * blue)));
}
}
- /* At conclusion of the histogram step, we can interpret
- * wt[r][g][b] = sum over voxel of P(c)
- * mr[r][g][b] = sum over voxel of r*P(c) , similarly for mg, mb
- * m2[r][g][b] = sum over voxel of c^2*P(c)
- * Actually each of these should be divided by 'size' to give the usual
- * interpretation of P() as ranging from 0 to 1, but we needn't do that here.
- *
- * We now convert histogram into moments so that we can rapidly calculate
- * the sums of the above quantities over any desired box.
- */
- private void computeMoments(
- long[][][] vwt, long[][][] vmr, long[][][] vmg, long[][][] vmb, double[][][] m2) {
- /* compute cumulative moments. */
- int i, r, g, b;
- int line, line_r, line_g, line_b;
- int[] area = new int[QUANT_SIZE];
- int[] area_r = new int[QUANT_SIZE];
- int[] area_g = new int[QUANT_SIZE];
- int[] area_b = new int[QUANT_SIZE];
- double line2;
- double[] area2 = new double[QUANT_SIZE];
+ private void createMoments() {
+ for (int r = 1; r < SIDE_LENGTH; ++r) {
+ int[] area = new int[SIDE_LENGTH];
+ int[] areaR = new int[SIDE_LENGTH];
+ int[] areaG = new int[SIDE_LENGTH];
+ int[] areaB = new int[SIDE_LENGTH];
+ double[] area2 = new double[SIDE_LENGTH];
- for (r = 1; r < QUANT_SIZE; ++r) {
- for (i = 0; i < QUANT_SIZE; ++i) {
- area2[i] = area[i] = area_r[i] = area_g[i] = area_b[i] = 0;
- }
- for (g = 1; g < QUANT_SIZE; ++g) {
- line2 = line = line_r = line_g = line_b = 0;
- for (b = 1; b < QUANT_SIZE; ++b) {
- line += vwt[r][g][b];
- line_r += vmr[r][g][b];
- line_g += vmg[r][g][b];
- line_b += vmb[r][g][b];
- line2 += m2[r][g][b];
+ for (int g = 1; g < SIDE_LENGTH; ++g) {
+ int line = 0;
+ int lineR = 0;
+ int lineG = 0;
+ int lineB = 0;
+
+ double line2 = 0.0;
+ for (int b = 1; b < SIDE_LENGTH; ++b) {
+ int index = getIndex(r, g, b);
+ line += mWeights[index];
+ lineR += mMomentsR[index];
+ lineG += mMomentsG[index];
+ lineB += mMomentsB[index];
+ line2 += mMoments[index];
area[b] += line;
- area_r[b] += line_r;
- area_g[b] += line_g;
- area_b[b] += line_b;
+ areaR[b] += lineR;
+ areaG[b] += lineG;
+ areaB[b] += lineB;
area2[b] += line2;
- vwt[r][g][b] = vwt[r - 1][g][b] + area[b];
- vmr[r][g][b] = vmr[r - 1][g][b] + area_r[b];
- vmg[r][g][b] = vmg[r - 1][g][b] + area_g[b];
- vmb[r][g][b] = vmb[r - 1][g][b] + area_b[b];
- m2[r][g][b] = m2[r - 1][g][b] + area2[b];
+ int previousIndex = getIndex(r - 1, g, b);
+ mWeights[index] = mWeights[previousIndex] + area[b];
+ mMomentsR[index] = mMomentsR[previousIndex] + areaR[b];
+ mMomentsG[index] = mMomentsG[previousIndex] + areaG[b];
+ mMomentsB[index] = mMomentsB[previousIndex] + areaB[b];
+ mMoments[index] = mMoments[previousIndex] + area2[b];
}
}
}
}
- private long getVolume(Box cube, long[][][] mmt) {
- /* Compute sum over a box of any given statistic */
- return (mmt[cube.mR1][cube.mG1][cube.mB1]
- - mmt[cube.mR1][cube.mG1][cube.mB0]
- - mmt[cube.mR1][cube.mG0][cube.mB1]
- + mmt[cube.mR1][cube.mG0][cube.mB0]
- - mmt[cube.mR0][cube.mG1][cube.mB1]
- + mmt[cube.mR0][cube.mG1][cube.mB0]
- + mmt[cube.mR0][cube.mG0][cube.mB1]
- - mmt[cube.mR0][cube.mG0][cube.mB0]);
- }
-
- /* The next two routines allow a slightly more efficient calculation
- * of Vol() for a proposed subbox of a given box. The sum of Top()
- * and Bottom() is the Vol() of a subbox split in the given direction
- * and with the specified new upper bound.
- */
- private long getBottom(Box cube, int dir, long[][][] mmt) {
- /* Compute part of Vol(cube, mmt) that doesn't depend on r1, g1, or b1 */
- /* (depending on dir) */
- switch (dir) {
- case RED:
- return (-mmt[cube.mR0][cube.mG1][cube.mB1]
- + mmt[cube.mR0][cube.mG1][cube.mB0]
- + mmt[cube.mR0][cube.mG0][cube.mB1]
- - mmt[cube.mR0][cube.mG0][cube.mB0]);
- case GREEN:
- return (-mmt[cube.mR1][cube.mG0][cube.mB1]
- + mmt[cube.mR1][cube.mG0][cube.mB0]
- + mmt[cube.mR0][cube.mG0][cube.mB1]
- - mmt[cube.mR0][cube.mG0][cube.mB0]);
- case BLUE:
- return (-mmt[cube.mR1][cube.mG1][cube.mB0]
- + mmt[cube.mR1][cube.mG0][cube.mB0]
- + mmt[cube.mR0][cube.mG1][cube.mB0]
- - mmt[cube.mR0][cube.mG0][cube.mB0]);
- default:
- return 0;
+ private CreateBoxesResult createBoxes(int maxColorCount) {
+ mCubes = new Box[maxColorCount];
+ for (int i = 0; i < maxColorCount; i++) {
+ mCubes[i] = new Box();
}
- }
+ double[] volumeVariance = new double[maxColorCount];
+ Box firstBox = mCubes[0];
+ firstBox.r1 = MAX_INDEX;
+ firstBox.g1 = MAX_INDEX;
+ firstBox.b1 = MAX_INDEX;
- private long getTop(Box cube, int dir, int pos, long[][][] mmt) {
- /* Compute remainder of Vol(cube, mmt), substituting pos for */
- /* r1, g1, or b1 (depending on dir) */
- switch (dir) {
- case RED:
- return (mmt[pos][cube.mG1][cube.mB1]
- - mmt[pos][cube.mG1][cube.mB0]
- - mmt[pos][cube.mG0][cube.mB1]
- + mmt[pos][cube.mG0][cube.mB0]);
- case GREEN:
- return (mmt[cube.mR1][pos][cube.mB1]
- - mmt[cube.mR1][pos][cube.mB0]
- - mmt[cube.mR0][pos][cube.mB1]
- + mmt[cube.mR0][pos][cube.mB0]);
- case BLUE:
- return (mmt[cube.mR1][cube.mG1][pos]
- - mmt[cube.mR1][cube.mG0][pos]
- - mmt[cube.mR0][cube.mG1][pos]
- + mmt[cube.mR0][cube.mG0][pos]);
- default:
- return 0;
- }
- }
+ int generatedColorCount = 0;
+ int next = 0;
- private double getVariance(Box cube) {
- /* Compute the weighted variance of a box */
- /* NB: as with the raw statistics, this is really the variance * size */
- double dr, dg, db, xx;
- dr = getVolume(cube, mMr);
- dg = getVolume(cube, mMg);
- db = getVolume(cube, mMb);
- xx =
- mM2[cube.mR1][cube.mG1][cube.mB1]
- - mM2[cube.mR1][cube.mG1][cube.mB0]
- - mM2[cube.mR1][cube.mG0][cube.mB1]
- + mM2[cube.mR1][cube.mG0][cube.mB0]
- - mM2[cube.mR0][cube.mG1][cube.mB1]
- + mM2[cube.mR0][cube.mG1][cube.mB0]
- + mM2[cube.mR0][cube.mG0][cube.mB1]
- - mM2[cube.mR0][cube.mG0][cube.mB0];
- return xx - (dr * dr + dg * dg + db * db) / getVolume(cube, mWt);
- }
-
- /* We want to minimize the sum of the variances of two subboxes.
- * The sum(c^2) terms can be ignored since their sum over both subboxes
- * is the same (the sum for the whole box) no matter where we split.
- * The remaining terms have a minus sign in the variance formula,
- * so we drop the minus sign and MAXIMIZE the sum of the two terms.
- */
- private double maximize(
- Box cube,
- int dir,
- int first,
- int last,
- int[] cut,
- long wholeR,
- long wholeG,
- long wholeB,
- long wholeW) {
- long half_r, half_g, half_b, half_w;
- long base_r, base_g, base_b, base_w;
- int i;
- double temp, max;
-
- base_r = getBottom(cube, dir, mMr);
- base_g = getBottom(cube, dir, mMg);
- base_b = getBottom(cube, dir, mMb);
- base_w = getBottom(cube, dir, mWt);
-
- max = 0.0f;
- cut[0] = -1;
-
- for (i = first; i < last; ++i) {
- half_r = base_r + getTop(cube, dir, i, mMr);
- half_g = base_g + getTop(cube, dir, i, mMg);
- half_b = base_b + getTop(cube, dir, i, mMb);
- half_w = base_w + getTop(cube, dir, i, mWt);
- /* now half_x is sum over lower half of box, if split at i */
- if (half_w == 0) /* subbox could be empty of pixels! */ {
- continue; /* never split into an empty box */
+ for (int i = 1; i < maxColorCount; i++) {
+ if (cut(mCubes[next], mCubes[i])) {
+ volumeVariance[next] = (mCubes[next].vol > 1) ? variance(mCubes[next]) : 0.0;
+ volumeVariance[i] = (mCubes[i].vol > 1) ? variance(mCubes[i]) : 0.0;
+ } else {
+ volumeVariance[next] = 0.0;
+ i--;
}
- temp = (half_r * half_r + half_g * half_g + half_b * half_b) / (double) half_w;
- half_r = wholeR - half_r;
- half_g = wholeG - half_g;
- half_b = wholeB - half_b;
- half_w = wholeW - half_w;
- if (half_w == 0) /* subbox could be empty of pixels! */ {
- continue; /* never split into an empty box */
- }
- temp += (half_r * half_r + half_g * half_g + half_b * half_b) / (double) half_w;
- if (temp > max) {
- max = temp;
- cut[0] = i;
+ next = 0;
+
+ double temp = volumeVariance[0];
+ for (int k = 1; k <= i; k++) {
+ if (volumeVariance[k] > temp) {
+ temp = volumeVariance[k];
+ next = k;
+ }
+ }
+ generatedColorCount = i + 1;
+ if (temp <= 0.0) {
+ break;
}
}
- return max;
+ return new CreateBoxesResult(maxColorCount, generatedColorCount);
}
- private boolean cut(Box set1, Box set2) {
- int dir;
- int[] cutr = new int[1];
- int[] cutg = new int[1];
- int[] cutb = new int[1];
- double maxr, maxg, maxb;
- long whole_r, whole_g, whole_b, whole_w;
+ private int[] createResult(int colorCount) {
+ int[] colors = new int[colorCount];
+ int nextAvailableIndex = 0;
+ for (int i = 0; i < colorCount; ++i) {
+ Box cube = mCubes[i];
+ int weight = volume(cube, mWeights);
+ if (weight > 0) {
+ int r = (volume(cube, mMomentsR) / weight);
+ int g = (volume(cube, mMomentsG) / weight);
+ int b = (volume(cube, mMomentsB) / weight);
+ int color = Color.rgb(r, g, b);
+ colors[nextAvailableIndex++] = color;
+ }
+ }
+ int[] resultArray = new int[nextAvailableIndex];
+ arraycopy(colors, 0, resultArray, 0, nextAvailableIndex);
+ return resultArray;
+ }
- whole_r = getVolume(set1, mMr);
- whole_g = getVolume(set1, mMg);
- whole_b = getVolume(set1, mMb);
- whole_w = getVolume(set1, mWt);
+ private double variance(Box cube) {
+ int dr = volume(cube, mMomentsR);
+ int dg = volume(cube, mMomentsG);
+ int db = volume(cube, mMomentsB);
+ double xx =
+ mMoments[getIndex(cube.r1, cube.g1, cube.b1)]
+ - mMoments[getIndex(cube.r1, cube.g1, cube.b0)]
+ - mMoments[getIndex(cube.r1, cube.g0, cube.b1)]
+ + mMoments[getIndex(cube.r1, cube.g0, cube.b0)]
+ - mMoments[getIndex(cube.r0, cube.g1, cube.b1)]
+ + mMoments[getIndex(cube.r0, cube.g1, cube.b0)]
+ + mMoments[getIndex(cube.r0, cube.g0, cube.b1)]
+ - mMoments[getIndex(cube.r0, cube.g0, cube.b0)];
- maxr = maximize(set1, RED, set1.mR0 + 1, set1.mR1, cutr, whole_r, whole_g, whole_b,
- whole_w);
- maxg = maximize(set1, GREEN, set1.mG0 + 1, set1.mG1, cutg, whole_r, whole_g, whole_b,
- whole_w);
- maxb = maximize(set1, BLUE, set1.mB0 + 1, set1.mB1, cutb, whole_r, whole_g, whole_b,
- whole_w);
+ int hypotenuse = (dr * dr + dg * dg + db * db);
+ int volume2 = volume(cube, mWeights);
+ double variance2 = xx - ((double) hypotenuse / (double) volume2);
+ return variance2;
+ }
- if (maxr >= maxg && maxr >= maxb) {
- dir = RED;
- if (cutr[0] < 0) return false; /* can't split the box */
- } else if (maxg >= maxr && maxg >= maxb) {
- dir = GREEN;
+ private boolean cut(Box one, Box two) {
+ int wholeR = volume(one, mMomentsR);
+ int wholeG = volume(one, mMomentsG);
+ int wholeB = volume(one, mMomentsB);
+ int wholeW = volume(one, mWeights);
+
+ MaximizeResult maxRResult =
+ maximize(one, Direction.RED, one.r0 + 1, one.r1, wholeR, wholeG, wholeB, wholeW);
+ MaximizeResult maxGResult =
+ maximize(one, Direction.GREEN, one.g0 + 1, one.g1, wholeR, wholeG, wholeB, wholeW);
+ MaximizeResult maxBResult =
+ maximize(one, Direction.BLUE, one.b0 + 1, one.b1, wholeR, wholeG, wholeB, wholeW);
+ Direction cutDirection;
+ double maxR = maxRResult.mMaximum;
+ double maxG = maxGResult.mMaximum;
+ double maxB = maxBResult.mMaximum;
+ if (maxR >= maxG && maxR >= maxB) {
+ if (maxRResult.mCutLocation < 0) {
+ return false;
+ }
+ cutDirection = Direction.RED;
+ } else if (maxG >= maxR && maxG >= maxB) {
+ cutDirection = Direction.GREEN;
} else {
- dir = BLUE;
+ cutDirection = Direction.BLUE;
}
- set2.mR1 = set1.mR1;
- set2.mG1 = set1.mG1;
- set2.mB1 = set1.mB1;
+ two.r1 = one.r1;
+ two.g1 = one.g1;
+ two.b1 = one.b1;
- switch (dir) {
+ switch (cutDirection) {
case RED:
- set2.mR0 = set1.mR1 = cutr[0];
- set2.mG0 = set1.mG0;
- set2.mB0 = set1.mB0;
+ one.r1 = maxRResult.mCutLocation;
+ two.r0 = one.r1;
+ two.g0 = one.g0;
+ two.b0 = one.b0;
break;
case GREEN:
- set2.mG0 = set1.mG1 = cutg[0];
- set2.mR0 = set1.mR0;
- set2.mB0 = set1.mB0;
+ one.g1 = maxGResult.mCutLocation;
+ two.r0 = one.r0;
+ two.g0 = one.g1;
+ two.b0 = one.b0;
break;
case BLUE:
- set2.mB0 = set1.mB1 = cutb[0];
- set2.mR0 = set1.mR0;
- set2.mG0 = set1.mG0;
+ one.b1 = maxBResult.mCutLocation;
+ two.r0 = one.r0;
+ two.g0 = one.g0;
+ two.b0 = one.b1;
break;
+ default:
+ throw new IllegalArgumentException("unexpected direction " + cutDirection);
}
- set1.mVol = (set1.mR1 - set1.mR0) * (set1.mG1 - set1.mG0) * (set1.mB1 - set1.mB0);
- set2.mVol = (set2.mR1 - set2.mR0) * (set2.mG1 - set2.mG0) * (set2.mB1 - set2.mB0);
+
+ one.vol = (one.r1 - one.r0) * (one.g1 - one.g0) * (one.b1 - one.b0);
+ two.vol = (two.r1 - two.r0) * (two.g1 - two.g0) * (two.b1 - two.b0);
return true;
}
+
+ private MaximizeResult maximize(
+ Box cube,
+ Direction direction,
+ int first,
+ int last,
+ int wholeR,
+ int wholeG,
+ int wholeB,
+ int wholeW) {
+ int baseR = bottom(cube, direction, mMomentsR);
+ int baseG = bottom(cube, direction, mMomentsG);
+ int baseB = bottom(cube, direction, mMomentsB);
+ int baseW = bottom(cube, direction, mWeights);
+
+ double max = 0.0;
+ int cut = -1;
+ for (int i = first; i < last; i++) {
+ int halfR = baseR + top(cube, direction, i, mMomentsR);
+ int halfG = baseG + top(cube, direction, i, mMomentsG);
+ int halfB = baseB + top(cube, direction, i, mMomentsB);
+ int halfW = baseW + top(cube, direction, i, mWeights);
+
+ if (halfW == 0) {
+ continue;
+ }
+ double tempNumerator = halfR * halfR + halfG * halfG + halfB * halfB;
+ double tempDenominator = halfW;
+ double temp = tempNumerator / tempDenominator;
+
+ halfR = wholeR - halfR;
+ halfG = wholeG - halfG;
+ halfB = wholeB - halfB;
+ halfW = wholeW - halfW;
+ if (halfW == 0) {
+ continue;
+ }
+
+ tempNumerator = halfR * halfR + halfG * halfG + halfB * halfB;
+ tempDenominator = halfW;
+ temp += (tempNumerator / tempDenominator);
+ if (temp > max) {
+ max = temp;
+ cut = i;
+ }
+ }
+ return new MaximizeResult(cut, max);
+ }
+
+ private static int volume(Box cube, int[] moment) {
+ return (moment[getIndex(cube.r1, cube.g1, cube.b1)]
+ - moment[getIndex(cube.r1, cube.g1, cube.b0)]
+ - moment[getIndex(cube.r1, cube.g0, cube.b1)]
+ + moment[getIndex(cube.r1, cube.g0, cube.b0)]
+ - moment[getIndex(cube.r0, cube.g1, cube.b1)]
+ + moment[getIndex(cube.r0, cube.g1, cube.b0)]
+ + moment[getIndex(cube.r0, cube.g0, cube.b1)]
+ - moment[getIndex(cube.r0, cube.g0, cube.b0)]);
+ }
+
+ private static int bottom(Box cube, Direction direction, int[] moment) {
+ switch (direction) {
+ case RED:
+ return -moment[getIndex(cube.r0, cube.g1, cube.b1)]
+ + moment[getIndex(cube.r0, cube.g1, cube.b0)]
+ + moment[getIndex(cube.r0, cube.g0, cube.b1)]
+ - moment[getIndex(cube.r0, cube.g0, cube.b0)];
+ case GREEN:
+ return -moment[getIndex(cube.r1, cube.g0, cube.b1)]
+ + moment[getIndex(cube.r1, cube.g0, cube.b0)]
+ + moment[getIndex(cube.r0, cube.g0, cube.b1)]
+ - moment[getIndex(cube.r0, cube.g0, cube.b0)];
+ case BLUE:
+ return -moment[getIndex(cube.r1, cube.g1, cube.b0)]
+ + moment[getIndex(cube.r1, cube.g0, cube.b0)]
+ + moment[getIndex(cube.r0, cube.g1, cube.b0)]
+ - moment[getIndex(cube.r0, cube.g0, cube.b0)];
+ default:
+ throw new IllegalArgumentException("unexpected direction " + direction);
+ }
+ }
+
+ private static int top(Box cube, Direction direction, int position, int[] moment) {
+ switch (direction) {
+ case RED:
+ return (moment[getIndex(position, cube.g1, cube.b1)]
+ - moment[getIndex(position, cube.g1, cube.b0)]
+ - moment[getIndex(position, cube.g0, cube.b1)]
+ + moment[getIndex(position, cube.g0, cube.b0)]);
+ case GREEN:
+ return (moment[getIndex(cube.r1, position, cube.b1)]
+ - moment[getIndex(cube.r1, position, cube.b0)]
+ - moment[getIndex(cube.r0, position, cube.b1)]
+ + moment[getIndex(cube.r0, position, cube.b0)]);
+ case BLUE:
+ return (moment[getIndex(cube.r1, cube.g1, position)]
+ - moment[getIndex(cube.r1, cube.g0, position)]
+ - moment[getIndex(cube.r0, cube.g1, position)]
+ + moment[getIndex(cube.r0, cube.g0, position)]);
+ default:
+ throw new IllegalArgumentException("unexpected direction " + direction);
+ }
+ }
+
+ private enum Direction {
+ RED,
+ GREEN,
+ BLUE
+ }
+
+ private static class MaximizeResult {
+ // < 0 if cut impossible
+ final int mCutLocation;
+ final double mMaximum;
+
+ MaximizeResult(int cut, double max) {
+ mCutLocation = cut;
+ mMaximum = max;
+ }
+ }
+
+ private static class CreateBoxesResult {
+ final int mRequestedCount;
+ final int mResultCount;
+
+ CreateBoxesResult(int requestedCount, int resultCount) {
+ mRequestedCount = requestedCount;
+ mResultCount = resultCount;
+ }
+ }
+
+ private static class Box {
+ public int r0 = 0;
+ public int r1 = 0;
+ public int g0 = 0;
+ public int g1 = 0;
+ public int b0 = 0;
+ public int b1 = 0;
+ public int vol = 0;
+ }
}
+
+
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 47341cd..f212fc7 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -66,12 +66,12 @@
}
@Override
- public void insetsChanged(InsetsState insetsState) {
+ public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {
}
@Override
public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
+ InsetsSourceControl[] activeControls, boolean willMove, boolean willResize) {
}
@Override
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/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 56814c7..5ca4ec0 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2383,7 +2383,7 @@
}
static jint android_media_AudioSystem_getMaxChannelCount(JNIEnv *env, jobject thiz) {
- return FCC_8;
+ return FCC_LIMIT;
}
static jint android_media_AudioSystem_getMaxSampleRate(JNIEnv *env, jobject thiz) {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index ce847e8..73e7d86 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1244,10 +1244,14 @@
return reinterpret_cast<jlong>(assetmanager->NewTheme().release());
}
-static void NativeThemeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) {
+static void NativeThemeDestroy(jlong theme_ptr) {
delete reinterpret_cast<Theme*>(theme_ptr);
}
+static jlong NativeGetThemeFreeFunction(JNIEnv* /*env*/, jclass /*clazz*/) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&NativeThemeDestroy));
+}
+
static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
jint resid, jboolean force) {
// AssetManager is accessed via the theme, so grab an explicit lock here.
@@ -1264,6 +1268,38 @@
// jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
}
+static void NativeThemeRebase(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jintArray style_ids, jbooleanArray force,
+ jint style_count) {
+ // Lock both the original asset manager of the theme and the new asset manager to be used for the
+ // theme.
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+
+ uint32_t* style_id_args = nullptr;
+ if (style_ids != nullptr) {
+ CHECK(style_count <= env->GetArrayLength(style_ids));
+ style_id_args = reinterpret_cast<uint32_t*>(env->GetPrimitiveArrayCritical(style_ids, nullptr));
+ if (style_id_args == nullptr) {
+ return;
+ }
+ }
+
+ jboolean* force_args = nullptr;
+ if (force != nullptr) {
+ CHECK(style_count <= env->GetArrayLength(force));
+ force_args = reinterpret_cast<jboolean*>(env->GetPrimitiveArrayCritical(force, nullptr));
+ if (force_args == nullptr) {
+ env->ReleasePrimitiveArrayCritical(style_ids, style_id_args, JNI_ABORT);
+ return;
+ }
+ }
+
+ auto theme = reinterpret_cast<Theme*>(theme_ptr);
+ theme->Rebase(&(*assetmanager), style_id_args, force_args, static_cast<size_t>(style_count));
+ env->ReleasePrimitiveArrayCritical(style_ids, style_id_args, JNI_ABORT);
+ env->ReleasePrimitiveArrayCritical(force, force_args, JNI_ABORT);
+}
+
static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_asset_manager_ptr,
jlong dst_theme_ptr, jlong src_asset_manager_ptr, jlong src_theme_ptr) {
Theme* dst_theme = reinterpret_cast<Theme*>(dst_theme_ptr);
@@ -1284,10 +1320,6 @@
}
}
-static void NativeThemeClear(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) {
- reinterpret_cast<Theme*>(theme_ptr)->Clear();
-}
-
static jint NativeThemeGetAttributeValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
jint resid, jobject typed_value,
jboolean resolve_references) {
@@ -1446,10 +1478,11 @@
// Theme related methods.
{"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
- {"nativeThemeDestroy", "(J)V", (void*)NativeThemeDestroy},
+ {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
{"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
+ {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
+
{"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
- {"nativeThemeClear", "(J)V", (void*)NativeThemeClear},
{"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
(void*)NativeThemeGetAttributeValue},
{"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6b6cbea..50a242c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1408,13 +1408,13 @@
<!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
system only camera devices.
- <p>Protection level: system|signature
+ <p>Protection level: system|signature|role
@hide -->
<permission android:name="android.permission.SYSTEM_CAMERA"
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_systemCamera"
android:description="@string/permdesc_systemCamera"
- android:protectionLevel="system|signature" />
+ android:protectionLevel="system|signature|role" />
<!-- @SystemApi Allows receiving the camera service notifications when a camera is opened
(by a certain application package) or closed.
diff --git a/core/res/res/drawable-car/car_checkbox.xml b/core/res/res/drawable-car/car_checkbox.xml
index 651e678..083a7aa 100644
--- a/core/res/res/drawable-car/car_checkbox.xml
+++ b/core/res/res/drawable-car/car_checkbox.xml
@@ -20,4 +20,8 @@
android:width="@*android:dimen/car_primary_icon_size"
android:height="@*android:dimen/car_primary_icon_size"
android:drawable="@drawable/btn_check_material_anim"/>
+ <item
+ android:width="@*android:dimen/car_primary_icon_size"
+ android:height="@*android:dimen/car_primary_icon_size"
+ android:drawable="@drawable/car_checkbox_background"/>
</layer-list>
diff --git a/core/res/res/drawable-car/car_checkbox_background.xml b/core/res/res/drawable-car/car_checkbox_background.xml
new file mode 100644
index 0000000..69dcdbb
--- /dev/null
+++ b/core/res/res/drawable-car/car_checkbox_background.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:state_pressed="true">
+ <shape android:shape="rectangle">
+ <solid android:color="#8A0041BE" />
+ <stroke android:width="4dp" android:color="#0041BE" />
+ </shape>
+ </item>
+ <item android:state_focused="true">
+ <shape android:shape="rectangle">
+ <solid android:color="#3D0059B3" />
+ <stroke android:width="8dp" android:color="#0059B3" />
+ </shape>
+ </item>
+</selector>
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/data/etc/platform.xml b/data/etc/platform.xml
index a64129e..756425e 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -293,4 +293,7 @@
be able to connect to the internet when such a proxy is in use, since
all outgoing connections originate from this app. -->
<allow-in-power-save-except-idle package="com.android.proxyhandler" />
+
+ <!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
+ <allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 240d056..04291e3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -488,6 +488,7 @@
<permission name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<permission name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
<permission name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS" />
+ <permission name="android.permission.GET_TOP_ACTIVITY_INFO" />
<permission name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER" />
<permission name="android.permission.SET_MEDIA_KEY_LISTENER" />
<permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
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/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/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/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index b674c68..f81f086 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -290,27 +290,6 @@
applyThemeAttrs();
setClipToPadding(false);
- setOnTouchListener((view, motionEvent) -> {
- if (mTaskView == null) {
- return false;
- }
-
- final Rect avBounds = new Rect();
- mTaskView.getBoundsOnScreen(avBounds);
-
- // Consume and ignore events on the expanded view padding that are within the
- // ActivityView's vertical bounds. These events are part of a back gesture, and so they
- // should not collapse the stack (which all other touches on areas around the AV would
- // do).
- if (motionEvent.getRawY() >= avBounds.top
- && motionEvent.getRawY() <= avBounds.bottom
- && (motionEvent.getRawX() < avBounds.left
- || motionEvent.getRawX() > avBounds.right)) {
- return true;
- }
-
- return false;
- });
// BubbleStackView is forced LTR, but we want to respect the locale for expanded view layout
// so the Manage button appears on the right.
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..16b8150 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
@@ -852,13 +852,6 @@
mTaskbarScrim.setAlpha(0f);
mTaskbarScrim.setVisibility(GONE);
- setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
- if (!mIsExpanded || mIsExpansionAnimating) {
- return view.onApplyWindowInsets(insets);
- }
- return view.onApplyWindowInsets(insets);
- });
-
mOrientationChangedListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
mPositioner.update();
@@ -908,20 +901,16 @@
}
});
- // If the stack itself is touched, it means none of its touchable views (bubbles, flyouts,
- // ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
- setOnTouchListener((view, ev) -> {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (mShowingManage) {
- showManageMenu(false /* show */);
- } else if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) {
- mStackEduView.hide(false);
- } else if (mBubbleData.isExpanded()) {
- mBubbleData.setExpanded(false);
- }
+ // If the stack itself is clicked, it means none of its touchable views (bubbles, flyouts,
+ // TaskView, etc.) were touched. Collapse the stack if it's expanded.
+ setOnClickListener(view -> {
+ if (mShowingManage) {
+ showManageMenu(false /* show */);
+ } else if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) {
+ mStackEduView.hide(false);
+ } else if (mBubbleData.isExpanded()) {
+ mBubbleData.setExpanded(false);
}
-
- return true;
});
animate()
@@ -1492,22 +1481,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 +2389,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 +2398,7 @@
mStackAnimationController.isStackOnLeftSide(),
bubble.getIconView().getDotColor() /* dotColor */,
expandFlyoutAfterDelay /* onLayoutComplete */,
- mAfterFlyoutHidden,
+ mAfterFlyoutHidden /* onHide */,
bubble.getIconView().getDotCenter(),
!bubble.showDot(),
mPositioner);
@@ -2464,6 +2454,10 @@
}
} else {
mBubbleContainer.getBoundsOnScreen(outRect);
+ // Account for the IME in the touchable region so that the touchable region of the
+ // Bubble window doesn't obscure the IME. The touchable region affects which areas
+ // of the screen can be excluded by lower windows (IME is just above the embedded task)
+ outRect.bottom -= (int) mStackAnimationController.getImeHeight();
}
if (mFlyout.getVisibility() == View.VISIBLE) {
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..0802fb5 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;
@@ -532,6 +529,11 @@
mImeHeight = imeHeight;
}
+ /** Returns the current IME height that the stack is offset by. */
+ public float getImeHeight() {
+ return mImeHeight;
+ }
+
/**
* Animates the stack either away from the newly visible IME, or back to its original position
* due to the IME going away.
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/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 9dabec7a..ef113dc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -317,11 +317,11 @@
public void locationInParentDisplayChanged(Point offset) {}
@Override
- public void insetsChanged(InsetsState insetsState) {}
+ public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {}
@Override
public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {}
+ InsetsSourceControl[] activeControls, boolean willMove, boolean willResize) {}
@Override
public void showInsets(int types, boolean fromIme) {}
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..728794d 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
@@ -149,10 +149,10 @@
// Shrink bounds (expand insets) in destination orientation.
if (clockwise) {
positionX -= insets.top * scale;
- positionY -= insets.left * scale;
+ positionY += insets.left * scale;
} else {
positionX += insets.top * scale;
- positionY += insets.left * scale;
+ positionY -= insets.left * scale;
}
}
mTmpTransform.setScale(scale, scale);
@@ -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..afd7d26 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;
}
@@ -627,8 +628,10 @@
final Rect destinationBounds = mPipBoundsState.getBounds();
final SurfaceControl swipeToHomeOverlay = mSwipePipToHomeOverlay;
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- mSurfaceTransactionHelper.resetScale(tx, mLeash, destinationBounds);
- mSurfaceTransactionHelper.crop(tx, mLeash, destinationBounds);
+ mSurfaceTransactionHelper
+ .resetScale(tx, mLeash, destinationBounds)
+ .crop(tx, mLeash, destinationBounds)
+ .round(tx, mLeash, isInPip());
// The animation is finished in the Launcher and here we directly apply the final touch.
applyEnterPipSyncTransaction(destinationBounds, () -> {
// Ensure menu's settled in its final bounds first.
@@ -1041,7 +1044,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 +1221,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..bf1f9e4 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,9 @@
import java.util.List;
import java.util.function.Consumer;
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
/**
* Util class to create the view for a splash screen content.
@@ -76,9 +86,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 +105,7 @@
new HandlerThread("wmshell.splashworker", THREAD_PRIORITY_TOP_APP_BOOST);
shellSplashscreenWorkerThread.start();
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
+ mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
}
/**
@@ -183,17 +197,26 @@
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();
}
+ private static <T> T safeReturnAttrDefault(UnaryOperator<T> getMethod, T def) {
+ try {
+ return getMethod.apply(def);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Get attribute fail, return default: " + e.getMessage());
+ return def;
+ }
+ }
+
/**
* Get the {@link SplashScreenWindowAttrs} from {@code context} and fill them into
* {@code attrs}.
@@ -202,16 +225,18 @@
final TypedArray typedArray = context.obtainStyledAttributes(
com.android.internal.R.styleable.Window);
attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- attrs.mWindowBgColor = typedArray.getColor(
- R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
- attrs.mReplaceIcon = typedArray.getDrawable(
- R.styleable.Window_windowSplashScreenAnimatedIcon);
- attrs.mAnimationDuration = typedArray.getInt(
- R.styleable.Window_windowSplashScreenAnimationDuration, 0);
- attrs.mBrandingImage = typedArray.getDrawable(
- R.styleable.Window_windowSplashScreenBrandingImage);
- attrs.mIconBgColor = typedArray.getColor(
- R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
+ attrs.mWindowBgColor = safeReturnAttrDefault((def) -> typedArray.getColor(
+ R.styleable.Window_windowSplashScreenBackground, def),
+ Color.TRANSPARENT);
+ attrs.mReplaceIcon = safeReturnAttrDefault((def) -> typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenAnimatedIcon), null);
+ attrs.mAnimationDuration = safeReturnAttrDefault((def) -> typedArray.getInt(
+ R.styleable.Window_windowSplashScreenAnimationDuration, def), 0);
+ attrs.mBrandingImage = safeReturnAttrDefault((def) -> typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenBrandingImage), null);
+ attrs.mIconBgColor = safeReturnAttrDefault((def) -> typedArray.getColor(
+ R.styleable.Window_windowSplashScreenIconBackgroundColor, def),
+ Color.TRANSPARENT);
typedArray.recycle();
if (DEBUG) {
Slog.d(TAG, "window attributes color: "
@@ -232,50 +257,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 +297,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 +313,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 +336,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 +360,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.
@@ -491,9 +488,14 @@
drawable = layerDrawable.getDrawable(0);
}
}
- mColorChecker = drawable instanceof ColorDrawable
- ? new SingleColorTester((ColorDrawable) drawable)
- : new ComplexDrawableTester(drawable, filterTransparent);
+ if (drawable == null) {
+ mColorChecker = new SingleColorTester(
+ (ColorDrawable) createDefaultBackgroundDrawable());
+ } else {
+ mColorChecker = drawable instanceof ColorDrawable
+ ? new SingleColorTester((ColorDrawable) drawable)
+ : new ComplexDrawableTester(drawable, filterTransparent);
+ }
}
public float nonTransparentRatio() {
@@ -689,6 +691,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/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 3a0153f..0cde3d1 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -1345,7 +1345,10 @@
}
std::unique_ptr<Theme> AssetManager2::NewTheme() {
- return std::unique_ptr<Theme>(new Theme(this));
+ constexpr size_t kInitialReserveSize = 32;
+ auto theme = std::unique_ptr<Theme>(new Theme(this));
+ theme->entries_.reserve(kInitialReserveSize);
+ return theme;
}
Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
@@ -1353,28 +1356,20 @@
Theme::~Theme() = default;
-namespace {
-
-struct ThemeEntry {
+struct Theme::Entry {
+ uint32_t attr_res_id;
ApkAssetsCookie cookie;
uint32_t type_spec_flags;
Res_value value;
};
-struct ThemeType {
- int entry_count;
- ThemeEntry entries[0];
+namespace {
+struct ThemeEntryKeyComparer {
+ bool operator() (const Theme::Entry& entry, uint32_t attr_res_id) const noexcept {
+ return entry.attr_res_id < attr_res_id;
+ }
};
-
-constexpr size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1;
-
-} // namespace
-
-struct Theme::Package {
- // Each element of Type will be a dynamically sized object
- // allocated to have the entries stored contiguously with the Type.
- std::array<util::unique_cptr<ThemeType>, kTypeCount> types;
-};
+} // namespace
base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) {
ATRACE_NAME("Theme::ApplyStyle");
@@ -1387,116 +1382,74 @@
// Merge the flags from this style.
type_spec_flags_ |= (*bag)->type_spec_flags;
- int last_type_idx = -1;
- int last_package_idx = -1;
- Package* last_package = nullptr;
- ThemeType* last_type = nullptr;
-
- // Iterate backwards, because each bag is sorted in ascending key ID order, meaning we will only
- // need to perform one resize per type.
- using reverse_bag_iterator = std::reverse_iterator<const ResolvedBag::Entry*>;
- const auto rbegin = reverse_bag_iterator(begin(*bag));
- for (auto it = reverse_bag_iterator(end(*bag)); it != rbegin; ++it) {
- const uint32_t attr_resid = it->key;
+ for (auto it = begin(*bag); it != end(*bag); ++it) {
+ const uint32_t attr_res_id = it->key;
// If the resource ID passed in is not a style, the key can be some other identifier that is not
// a resource ID. We should fail fast instead of operating with strange resource IDs.
- if (!is_valid_resid(attr_resid)) {
+ if (!is_valid_resid(attr_res_id)) {
return base::unexpected(std::nullopt);
}
- // We don't use the 0-based index for the type so that we can avoid doing ID validation
- // upon lookup. Instead, we keep space for the type ID 0 in our data structures. Since
- // the construction of this type is guarded with a resource ID check, it will never be
- // populated, and querying type ID 0 will always fail.
- const int package_idx = get_package_id(attr_resid);
- const int type_idx = get_type_id(attr_resid);
- const int entry_idx = get_entry_id(attr_resid);
-
- if (last_package_idx != package_idx) {
- std::unique_ptr<Package>& package = packages_[package_idx];
- if (package == nullptr) {
- package.reset(new Package());
- }
- last_package_idx = package_idx;
- last_package = package.get();
- last_type_idx = -1;
+ // DATA_NULL_EMPTY (@empty) is a valid resource value and DATA_NULL_UNDEFINED represents
+ // an absence of a valid value.
+ bool is_undefined = it->value.dataType == Res_value::TYPE_NULL &&
+ it->value.data != Res_value::DATA_NULL_EMPTY;
+ if (!force && is_undefined) {
+ continue;
}
- if (last_type_idx != type_idx) {
- util::unique_cptr<ThemeType>& type = last_package->types[type_idx];
- if (type == nullptr) {
- // Allocate enough memory to contain this entry_idx. Since we're iterating in reverse over
- // a sorted list of attributes, this shouldn't be resized again during this method call.
- type.reset(reinterpret_cast<ThemeType*>(
- calloc(sizeof(ThemeType) + (entry_idx + 1) * sizeof(ThemeEntry), 1)));
- type->entry_count = entry_idx + 1;
- } else if (entry_idx >= type->entry_count) {
- // Reallocate the memory to contain this entry_idx. Since we're iterating in reverse over
- // a sorted list of attributes, this shouldn't be resized again during this method call.
- const int new_count = entry_idx + 1;
- type.reset(reinterpret_cast<ThemeType*>(
- realloc(type.release(), sizeof(ThemeType) + (new_count * sizeof(ThemeEntry)))));
-
- // Clear out the newly allocated space (which isn't zeroed).
- memset(type->entries + type->entry_count, 0,
- (new_count - type->entry_count) * sizeof(ThemeEntry));
- type->entry_count = new_count;
+ Theme::Entry new_entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value};
+ auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), attr_res_id,
+ ThemeEntryKeyComparer{});
+ if (entry_it != entries_.end() && entry_it->attr_res_id == attr_res_id) {
+ if (is_undefined) {
+ // DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is
+ /// true.
+ entries_.erase(entry_it);
+ } else if (force) {
+ *entry_it = new_entry;
}
- last_type_idx = type_idx;
- last_type = type.get();
- }
-
- ThemeEntry& entry = last_type->entries[entry_idx];
- if (force || (entry.value.dataType == Res_value::TYPE_NULL &&
- entry.value.data != Res_value::DATA_NULL_EMPTY)) {
- entry.cookie = it->cookie;
- entry.type_spec_flags |= (*bag)->type_spec_flags;
- entry.value = it->value;
+ } else {
+ entries_.insert(entry_it, new_entry);
}
}
return {};
}
+void Theme::Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t* force,
+ size_t style_count) {
+ ATRACE_NAME("Theme::Rebase");
+ // Reset the entries without changing the vector capacity to prevent reallocations during
+ // ApplyStyle.
+ entries_.clear();
+ asset_manager_ = am;
+ for (size_t i = 0; i < style_count; i++) {
+ ApplyStyle(style_ids[i], force[i]);
+ }
+}
+
std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) const {
- int cnt = 20;
+ constexpr const uint32_t kMaxIterations = 20;
uint32_t type_spec_flags = 0u;
- do {
- const int package_idx = get_package_id(resid);
- const Package* package = packages_[package_idx].get();
- if (package != nullptr) {
- // The themes are constructed with a 1-based type ID, so no need to decrement here.
- const int type_idx = get_type_id(resid);
- const ThemeType* type = package->types[type_idx].get();
- if (type != nullptr) {
- const int entry_idx = get_entry_id(resid);
- if (entry_idx < type->entry_count) {
- const ThemeEntry& entry = type->entries[entry_idx];
- type_spec_flags |= entry.type_spec_flags;
-
- if (entry.value.dataType == Res_value::TYPE_ATTRIBUTE) {
- if (cnt > 0) {
- cnt--;
- resid = entry.value.data;
- continue;
- }
- return std::nullopt;
- }
-
- // @null is different than @empty.
- if (entry.value.dataType == Res_value::TYPE_NULL &&
- entry.value.data != Res_value::DATA_NULL_EMPTY) {
- return std::nullopt;
- }
-
- return AssetManager2::SelectedValue(entry.value.dataType, entry.value.data, entry.cookie,
- type_spec_flags, 0U /* resid */, {} /* config */);
- }
- }
+ for (uint32_t i = 0; i <= kMaxIterations; i++) {
+ auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), resid,
+ ThemeEntryKeyComparer{});
+ if (entry_it == entries_.end() || entry_it->attr_res_id != resid) {
+ return std::nullopt;
}
- break;
- } while (true);
+
+ type_spec_flags |= entry_it->type_spec_flags;
+ if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) {
+ resid = entry_it->value.data;
+ continue;
+ }
+
+ return AssetManager2::SelectedValue(entry_it->value.dataType, entry_it->value.data,
+ entry_it->cookie, type_spec_flags, 0U /* resid */,
+ {} /* config */);
+ }
return std::nullopt;
}
@@ -1520,56 +1473,25 @@
}
void Theme::Clear() {
- type_spec_flags_ = 0u;
- for (std::unique_ptr<Package>& package : packages_) {
- package.reset();
- }
+ entries_.clear();
}
-base::expected<std::monostate, IOError> Theme::SetTo(const Theme& o) {
- if (this == &o) {
+base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) {
+ if (this == &source) {
return {};
}
- type_spec_flags_ = o.type_spec_flags_;
+ type_spec_flags_ = source.type_spec_flags_;
- if (asset_manager_ == o.asset_manager_) {
- // The theme comes from the same asset manager so all theme data can be copied exactly
- for (size_t p = 0; p < packages_.size(); p++) {
- const Package *package = o.packages_[p].get();
- if (package == nullptr) {
- // The other theme doesn't have this package, clear ours.
- packages_[p].reset();
- continue;
- }
-
- if (packages_[p] == nullptr) {
- // The other theme has this package, but we don't. Make one.
- packages_[p].reset(new Package());
- }
-
- for (size_t t = 0; t < package->types.size(); t++) {
- const ThemeType *type = package->types[t].get();
- if (type == nullptr) {
- // The other theme doesn't have this type, clear ours.
- packages_[p]->types[t].reset();
- continue;
- }
-
- // Create a new type and update it to theirs.
- const size_t type_alloc_size = sizeof(ThemeType) + (type->entry_count * sizeof(ThemeEntry));
- void *copied_data = malloc(type_alloc_size);
- memcpy(copied_data, type, type_alloc_size);
- packages_[p]->types[t].reset(reinterpret_cast<ThemeType *>(copied_data));
- }
- }
+ if (asset_manager_ == source.asset_manager_) {
+ entries_ = source.entries_;
} else {
std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
typedef std::map<int, int> SourceToDestinationRuntimePackageMap;
std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
// Determine which ApkAssets are loaded in both theme AssetManagers.
- const auto src_assets = o.asset_manager_->GetApkAssets();
+ const auto src_assets = source.asset_manager_->GetApkAssets();
for (size_t i = 0; i < src_assets.size(); i++) {
const ApkAssets* src_asset = src_assets[i];
@@ -1587,7 +1509,8 @@
// asset in th destination AssetManager.
SourceToDestinationRuntimePackageMap package_map;
for (const auto& loaded_package : src_asset->GetLoadedArsc()->GetPackages()) {
- const int src_package_id = o.asset_manager_->GetAssignedPackageId(loaded_package.get());
+ const int src_package_id = source.asset_manager_->GetAssignedPackageId(
+ loaded_package.get());
const int dest_package_id = asset_manager_->GetAssignedPackageId(loaded_package.get());
package_map[src_package_id] = dest_package_id;
}
@@ -1599,130 +1522,88 @@
}
// Reset the data in the destination theme.
- for (size_t p = 0; p < packages_.size(); p++) {
- if (packages_[p] != nullptr) {
- packages_[p].reset();
- }
- }
+ entries_.clear();
- for (size_t p = 0; p < packages_.size(); p++) {
- const Package *package = o.packages_[p].get();
- if (package == nullptr) {
- continue;
- }
+ for (const auto& entry : source.entries_) {
+ bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE
+ || entry.value.dataType == Res_value::TYPE_REFERENCE
+ || entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE
+ || entry.value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE)
+ && entry.value.data != 0x0;
- for (size_t t = 0; t < package->types.size(); t++) {
- const ThemeType *type = package->types[t].get();
- if (type == nullptr) {
+ // If the attribute value represents an attribute or reference, the package id of the
+ // value needs to be rewritten to the package id of the value in the destination.
+ uint32_t attribute_data = entry.value.data;
+ if (is_reference) {
+ // Determine the package id of the reference in the destination AssetManager.
+ auto value_package_map = src_asset_cookie_id_map.find(entry.cookie);
+ if (value_package_map == src_asset_cookie_id_map.end()) {
continue;
}
- for (size_t e = 0; e < type->entry_count; e++) {
- const ThemeEntry &entry = type->entries[e];
- if (entry.value.dataType == Res_value::TYPE_NULL &&
- entry.value.data != Res_value::DATA_NULL_EMPTY) {
- continue;
- }
+ auto value_dest_package = value_package_map->second.find(
+ get_package_id(entry.value.data));
+ if (value_dest_package == value_package_map->second.end()) {
+ continue;
+ }
- bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE
- || entry.value.dataType == Res_value::TYPE_REFERENCE
- || entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE
- || entry.value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE)
- && entry.value.data != 0x0;
+ attribute_data = fix_package_id(entry.value.data, value_dest_package->second);
+ }
- // If the attribute value represents an attribute or reference, the package id of the
- // value needs to be rewritten to the package id of the value in the destination.
- uint32_t attribute_data = entry.value.data;
- if (is_reference) {
- // Determine the package id of the reference in the destination AssetManager.
- auto value_package_map = src_asset_cookie_id_map.find(entry.cookie);
- if (value_package_map == src_asset_cookie_id_map.end()) {
- continue;
- }
-
- auto value_dest_package = value_package_map->second.find(
- get_package_id(entry.value.data));
- if (value_dest_package == value_package_map->second.end()) {
- continue;
- }
-
- attribute_data = fix_package_id(entry.value.data, value_dest_package->second);
- }
-
- // Find the cookie of the value in the destination. If the source apk is not loaded in the
- // destination, only copy resources that do not reference resources in the source.
- ApkAssetsCookie data_dest_cookie;
- auto value_dest_cookie = src_to_dest_asset_cookies.find(entry.cookie);
- if (value_dest_cookie != src_to_dest_asset_cookies.end()) {
- data_dest_cookie = value_dest_cookie->second;
- } else {
- if (is_reference || entry.value.dataType == Res_value::TYPE_STRING) {
- continue;
- } else {
- data_dest_cookie = 0x0;
- }
- }
-
- // The package id of the attribute needs to be rewritten to the package id of the
- // attribute in the destination.
- int attribute_dest_package_id = p;
- if (attribute_dest_package_id != 0x01) {
- // Find the cookie of the attribute resource id in the source AssetManager
- base::expected<FindEntryResult, NullOrIOError> attribute_entry_result =
- o.asset_manager_->FindEntry(make_resid(p, t, e), 0 /* density_override */ ,
- true /* stop_at_first_match */,
- true /* ignore_configuration */);
- if (UNLIKELY(IsIOError(attribute_entry_result))) {
- return base::unexpected(GetIOError(attribute_entry_result.error()));
- }
- if (!attribute_entry_result.has_value()) {
- continue;
- }
-
- // Determine the package id of the attribute in the destination AssetManager.
- auto attribute_package_map = src_asset_cookie_id_map.find(
- attribute_entry_result->cookie);
- if (attribute_package_map == src_asset_cookie_id_map.end()) {
- continue;
- }
- auto attribute_dest_package = attribute_package_map->second.find(
- attribute_dest_package_id);
- if (attribute_dest_package == attribute_package_map->second.end()) {
- continue;
- }
- attribute_dest_package_id = attribute_dest_package->second;
- }
-
- // Lazily instantiate the destination package.
- std::unique_ptr<Package>& dest_package = packages_[attribute_dest_package_id];
- if (dest_package == nullptr) {
- dest_package.reset(new Package());
- }
-
- // Lazily instantiate and resize the destination type.
- util::unique_cptr<ThemeType>& dest_type = dest_package->types[t];
- if (dest_type == nullptr || dest_type->entry_count < type->entry_count) {
- const size_t type_alloc_size = sizeof(ThemeType)
- + (type->entry_count * sizeof(ThemeEntry));
- void* dest_data = malloc(type_alloc_size);
- memset(dest_data, 0, type->entry_count * sizeof(ThemeEntry));
-
- // Copy the existing destination type values if the type is resized.
- if (dest_type != nullptr) {
- memcpy(dest_data, type, sizeof(ThemeType)
- + (dest_type->entry_count * sizeof(ThemeEntry)));
- }
-
- dest_type.reset(reinterpret_cast<ThemeType *>(dest_data));
- dest_type->entry_count = type->entry_count;
- }
-
- dest_type->entries[e].cookie = data_dest_cookie;
- dest_type->entries[e].value.dataType = entry.value.dataType;
- dest_type->entries[e].value.data = attribute_data;
- dest_type->entries[e].type_spec_flags = entry.type_spec_flags;
+ // Find the cookie of the value in the destination. If the source apk is not loaded in the
+ // destination, only copy resources that do not reference resources in the source.
+ ApkAssetsCookie data_dest_cookie;
+ auto value_dest_cookie = src_to_dest_asset_cookies.find(entry.cookie);
+ if (value_dest_cookie != src_to_dest_asset_cookies.end()) {
+ data_dest_cookie = value_dest_cookie->second;
+ } else {
+ if (is_reference || entry.value.dataType == Res_value::TYPE_STRING) {
+ continue;
+ } else {
+ data_dest_cookie = 0x0;
}
}
+
+ // The package id of the attribute needs to be rewritten to the package id of the
+ // attribute in the destination.
+ int attribute_dest_package_id = get_package_id(entry.attr_res_id);
+ if (attribute_dest_package_id != 0x01) {
+ // Find the cookie of the attribute resource id in the source AssetManager
+ base::expected<FindEntryResult, NullOrIOError> attribute_entry_result =
+ source.asset_manager_->FindEntry(entry.attr_res_id, 0 /* density_override */ ,
+ true /* stop_at_first_match */,
+ true /* ignore_configuration */);
+ if (UNLIKELY(IsIOError(attribute_entry_result))) {
+ return base::unexpected(GetIOError(attribute_entry_result.error()));
+ }
+ if (!attribute_entry_result.has_value()) {
+ continue;
+ }
+
+ // Determine the package id of the attribute in the destination AssetManager.
+ auto attribute_package_map = src_asset_cookie_id_map.find(
+ attribute_entry_result->cookie);
+ if (attribute_package_map == src_asset_cookie_id_map.end()) {
+ continue;
+ }
+ auto attribute_dest_package = attribute_package_map->second.find(
+ attribute_dest_package_id);
+ if (attribute_dest_package == attribute_package_map->second.end()) {
+ continue;
+ }
+ attribute_dest_package_id = attribute_dest_package->second;
+ }
+
+ auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(entry.attr_res_id),
+ get_entry_id(entry.attr_res_id));
+ Theme::Entry new_entry{dest_attr_id, data_dest_cookie, entry.type_spec_flags,
+ Res_value{.dataType = entry.value.dataType,
+ .data = attribute_data}};
+
+ // Since the entries were cleared, the attribute resource id has yet been mapped to any value.
+ auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), dest_attr_id,
+ ThemeEntryKeyComparer{});
+ entries_.insert(entry_it, new_entry);
}
}
return {};
@@ -1730,31 +1611,10 @@
void Theme::Dump() const {
LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_);
-
- for (int p = 0; p < packages_.size(); p++) {
- auto& package = packages_[p];
- if (package == nullptr) {
- continue;
- }
-
- for (int t = 0; t < package->types.size(); t++) {
- auto& type = package->types[t];
- if (type == nullptr) {
- continue;
- }
-
- for (int e = 0; e < type->entry_count; e++) {
- auto& entry = type->entries[e];
- if (entry.value.dataType == Res_value::TYPE_NULL &&
- entry.value.data != Res_value::DATA_NULL_EMPTY) {
- continue;
- }
-
- LOG(INFO) << base::StringPrintf(" entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)",
- make_resid(p, t, e), entry.value.data,
- entry.value.dataType, entry.cookie);
- }
- }
+ for (auto& entry : entries_) {
+ LOG(INFO) << base::StringPrintf(" entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)",
+ entry.attr_res_id, entry.value.data, entry.value.dataType,
+ entry.cookie);
}
}
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index df3abf6..7d01395 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -508,13 +508,18 @@
// data failed.
base::expected<std::monostate, NullOrIOError> ApplyStyle(uint32_t resid, bool force = false);
- // Sets this Theme to be a copy of `other` if `other` has the same AssetManager as this Theme.
+ // Clears the existing theme, sets the new asset manager to use for this theme, and applies the
+ // styles in `style_ids` through repeated invocations of `ApplyStyle`.
+ void Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t* force,
+ size_t style_count);
+
+ // Sets this Theme to be a copy of `source` if `source` has the same AssetManager as this Theme.
//
- // If `other` does not have the same AssetManager as this theme, only attributes from ApkAssets
+ // If `source` does not have the same AssetManager as this theme, only attributes from ApkAssets
// loaded into both AssetManagers will be copied to this theme.
//
// Returns an I/O error if reading resource data failed.
- base::expected<std::monostate, IOError> SetTo(const Theme& other);
+ base::expected<std::monostate, IOError> SetTo(const Theme& source);
void Clear();
@@ -546,20 +551,16 @@
void Dump() const;
+ struct Entry;
private:
DISALLOW_COPY_AND_ASSIGN(Theme);
- // Called by AssetManager2.
explicit Theme(AssetManager2* asset_manager);
- AssetManager2* asset_manager_;
+ AssetManager2* asset_manager_ = nullptr;
uint32_t type_spec_flags_ = 0u;
- // Defined in the cpp.
- struct Package;
-
- constexpr static size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1;
- std::array<std::unique_ptr<Package>, kPackageCount> packages_;
+ std::vector<Entry> entries_;
};
inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) {
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index f658735..77114f2 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -251,6 +251,80 @@
EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), value->flags);
}
+TEST_F(ThemeTest, ThemeRebase) {
+ AssetManager2 am;
+ am.SetApkAssets({style_assets_.get()});
+
+ AssetManager2 am_night;
+ am_night.SetApkAssets({style_assets_.get()});
+
+ ResTable_config night{};
+ night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
+ night.version = 8u;
+ am_night.SetConfiguration(night);
+
+ auto theme = am.NewTheme();
+ {
+ const uint32_t styles[] = {app::R::style::StyleOne, app::R::style::StyleDayNight};
+ const uint8_t force[] = {true, true};
+ theme->Rebase(&am, styles, force, arraysize(styles));
+ }
+
+ // attr_one in StyleDayNight force overrides StyleOne. attr_one is defined in the StyleOne.
+ auto value = theme->GetAttribute(app::R::attr::attr_one);
+ ASSERT_TRUE(value);
+ EXPECT_EQ(10u, value->data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC | ResTable_config::CONFIG_UI_MODE |
+ ResTable_config::CONFIG_VERSION), value->flags);
+
+ // attr_two is defined in the StyleOne.
+ value = theme->GetAttribute(app::R::attr::attr_two);
+ ASSERT_TRUE(value);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value->type);
+ EXPECT_EQ(2u, value->data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), value->flags);
+
+ {
+ const uint32_t styles[] = {app::R::style::StyleOne, app::R::style::StyleDayNight};
+ const uint8_t force[] = {false, false};
+ theme->Rebase(&am, styles, force, arraysize(styles));
+ }
+
+ // attr_one in StyleDayNight does not override StyleOne because `force` is false.
+ value = theme->GetAttribute(app::R::attr::attr_one);
+ ASSERT_TRUE(value);
+ EXPECT_EQ(1u, value->data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), value->flags);
+
+ // attr_two is defined in the StyleOne.
+ value = theme->GetAttribute(app::R::attr::attr_two);
+ ASSERT_TRUE(value);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value->type);
+ EXPECT_EQ(2u, value->data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), value->flags);
+
+ {
+ const uint32_t styles[] = {app::R::style::StyleOne, app::R::style::StyleDayNight};
+ const uint8_t force[] = {false, true};
+ theme->Rebase(&am_night, styles, force, arraysize(styles));
+ }
+
+ // attr_one is defined in the StyleDayNight.
+ value = theme->GetAttribute(app::R::attr::attr_one);
+ ASSERT_TRUE(value);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value->type);
+ EXPECT_EQ(100u, value->data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC | ResTable_config::CONFIG_UI_MODE |
+ ResTable_config::CONFIG_VERSION), value->flags);
+
+ // attr_two is now not here.
+ value = theme->GetAttribute(app::R::attr::attr_two);
+ ASSERT_TRUE(value);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value->type);
+ EXPECT_EQ(2u, value->data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), value->flags);
+}
+
TEST_F(ThemeTest, OnlyCopySameAssetsThemeWhenAssetManagersDiffer) {
AssetManager2 assetmanager_dst;
assetmanager_dst.SetApkAssets({system_assets_.get(), lib_one_assets_.get(), style_assets_.get(),
diff --git a/libs/androidfw/tests/data/styles/R.h b/libs/androidfw/tests/data/styles/R.h
index f11486f..393a207 100644
--- a/libs/androidfw/tests/data/styles/R.h
+++ b/libs/androidfw/tests/data/styles/R.h
@@ -52,6 +52,7 @@
StyleFive = 0x7f020004u,
StyleSix = 0x7f020005u,
StyleSeven = 0x7f020006u,
+ StyleDayNight = 0x7f020007u,
};
};
};
diff --git a/libs/androidfw/tests/data/styles/res/values/styles.xml b/libs/androidfw/tests/data/styles/res/values/styles.xml
index 06774a8..fe46a3fa 100644
--- a/libs/androidfw/tests/data/styles/res/values/styles.xml
+++ b/libs/androidfw/tests/data/styles/res/values/styles.xml
@@ -89,4 +89,9 @@
<item name="android:theme">@empty</item>
</style>
+ <public type="style" name="StyleDayNight" id="0x7f020007" />
+ <style name="StyleDayNight">
+ <item name="attr_one">10</item>
+ </style>
+
</resources>
diff --git a/libs/androidfw/tests/data/styles/styles.apk b/libs/androidfw/tests/data/styles/styles.apk
index 92e9bf90..91cd654 100644
--- a/libs/androidfw/tests/data/styles/styles.apk
+++ b/libs/androidfw/tests/data/styles/styles.apk
Binary files differ
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/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index cb56ee5..a3842a1 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -178,6 +178,7 @@
public static final int POWER_HIGH = 203;
private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1;
+ private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
+ "LocationManager} methods to provide the provider explicitly.")
@@ -552,7 +553,7 @@
*/
public @IntRange(from = 0) long getMinUpdateIntervalMillis() {
if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) {
- return mInterval;
+ return (long) (mInterval * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
} else {
// the min is only necessary in case someone use a deprecated function to mess with the
// interval or min update interval
@@ -1018,7 +1019,9 @@
* Sets an explicit minimum update interval. If location updates are available faster than
* the request interval then an update will only occur if the minimum update interval has
* expired since the last location update. Defaults to no explicit minimum update interval
- * set, which means the minimum update interval is the same as the interval.
+ * set, which means some sensible default between 0 and the interval will be chosen. The
+ * exact value is not specified at the moment. If an exact known value is required, clients
+ * should set an explicit value themselves.
*
* <p class=note><strong>Note:</strong> Some allowance for jitter is already built into the
* minimum update interval, so you need not worry about updates blocked simply because they
@@ -1037,7 +1040,8 @@
/**
* Clears an explicitly set minimum update interval and reverts to an implicit minimum
- * update interval (ie, the minimum update interval is the same value as the interval).
+ * update interval (ie, the minimum update interval is some sensible default between 0 and
+ * the interval).
*/
public @NonNull Builder clearMinUpdateIntervalMillis() {
mMinUpdateIntervalMillis = IMPLICIT_MIN_UPDATE_INTERVAL;
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/media/jni/soundpool/Sound.cpp b/media/jni/soundpool/Sound.cpp
index 50e0d33..6ef0740 100644
--- a/media/jni/soundpool/Sound.cpp
+++ b/media/jni/soundpool/Sound.cpp
@@ -214,7 +214,7 @@
} else if (sampleRate > kMaxSampleRate) {
ALOGE("%s: sample rate (%u) out of range", __func__, sampleRate);
status = BAD_VALUE;
- } else if (channelCount < 1 || channelCount > FCC_8) {
+ } else if (channelCount < 1 || channelCount > FCC_LIMIT) {
ALOGE("%s: sample channel count (%d) out of range", __func__, channelCount);
status = BAD_VALUE;
} else {
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/BannerMessagePreference/res/values-v31/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
index a39e779..e74ac44 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
@@ -33,7 +33,7 @@
<style name="Banner.Title.SettingsLib"
parent="@android:style/TextAppearance.Material.Subhead">
<item name="android:textSize">20sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml
new file mode 100644
index 0000000..a4c780b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@android:id/background">
+ <shape>
+ <corners android:radius="8dp" />
+ <solid android:color="@color/settingslib_colorSurfaceVariant" />
+ </shape>
+ </item>
+
+ <item
+ android:id="@android:id/progress">
+ <clip>
+ <shape>
+ <corners android:radius="8dp" />
+ <solid android:color="?android:attr/textColorPrimary" />
+ </shape>
+ </clip>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
index 579abbd5..23aa993 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
@@ -42,7 +42,7 @@
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:singleLine="true"
+ android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 426a2ba..69975cd 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -31,4 +31,6 @@
<color name="settingslib_dialog_background">@android:color/system_neutral1_800</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 -->
+
+ <color name="settingslib_colorSurfaceVariant">@android:color/system_neutral1_700</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index fcba6bd..aed6338 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -37,4 +37,6 @@
<color name="settingslib_dialog_background">@android:color/system_neutral1_800</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#d93025</color> <!-- Red 600 -->
+
+ <color name="settingslib_colorSurfaceVariant">@android:color/system_neutral2_100</color>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index e3a0239..9610c94 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -28,4 +28,10 @@
<item name="android:track">@drawable/settingslib_switch_track</item>
<item name="android:thumb">@drawable/settingslib_switch_thumb</item>
</style>
+
+ <style name="HorizontalProgressBar.SettingsLib"
+ parent="android:style/Widget.Material.ProgressBar.Horizontal">
+ <item name="android:progressDrawable">@drawable/settingslib_progress_horizontal</item>
+ <item name="android:scaleY">0.5</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index b3378b20..b5cf368 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -23,6 +23,7 @@
<item name="android:listPreferredItemPaddingEnd">16dp</item>
<item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item>
<item name="android:switchStyle">@style/Switch.SettingsLib</item>
+ <item name="android:progressBarStyleHorizontal">@style/HorizontalProgressBar.SettingsLib</item>
</style>
<!-- Using in SubSettings page including injected settings page -->
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml
index 2c2756b..2c35772 100644
--- a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml
@@ -40,7 +40,7 @@
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:singleLine="true"
+ android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/>
diff --git a/packages/SettingsLib/res/layout/preference_access_point.xml b/packages/SettingsLib/res/layout/preference_access_point.xml
index f3f43ac..802d604 100644
--- a/packages/SettingsLib/res/layout/preference_access_point.xml
+++ b/packages/SettingsLib/res/layout/preference_access_point.xml
@@ -64,7 +64,7 @@
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:singleLine="true"
+ android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
diff --git a/packages/SettingsLib/res/layout/preference_checkable_two_target.xml b/packages/SettingsLib/res/layout/preference_checkable_two_target.xml
index e4f7242..f512f9b 100644
--- a/packages/SettingsLib/res/layout/preference_checkable_two_target.xml
+++ b/packages/SettingsLib/res/layout/preference_checkable_two_target.xml
@@ -61,7 +61,7 @@
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:singleLine="true"
+ android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
diff --git a/packages/SettingsLib/res/layout/restricted_switch_preference.xml b/packages/SettingsLib/res/layout/restricted_switch_preference.xml
index d9f91de..169ae97 100644
--- a/packages/SettingsLib/res/layout/restricted_switch_preference.xml
+++ b/packages/SettingsLib/res/layout/restricted_switch_preference.xml
@@ -51,7 +51,7 @@
<TextView android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:singleLine="true"
+ android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
diff --git a/packages/SettingsLib/res/layout/restricted_switch_widget.xml b/packages/SettingsLib/res/layout/restricted_switch_widget.xml
index 5dbcb79..1520ac8 100644
--- a/packages/SettingsLib/res/layout/restricted_switch_widget.xml
+++ b/packages/SettingsLib/res/layout/restricted_switch_widget.xml
@@ -16,11 +16,11 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/restricted_icon"
- android:layout_width="@*android:dimen/config_restrictedIconSize"
+ android:layout_width="@dimen/two_target_min_width"
android:layout_height="@*android:dimen/config_restrictedIconSize"
android:tint="?android:attr/colorAccent"
android:src="@*android:drawable/ic_info"
- android:gravity="end|center_vertical" />
+ android:gravity="center" />
<!-- Based off frameworks/base/core/res/res/layout/preference_widget_switch.xml -->
<Switch xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/switch_widget"
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/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bcb21d1..959b5ca 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -526,6 +526,9 @@
<!-- Permission required for CTS test - FontManagerTest -->
<uses-permission android:name="android.permission.UPDATE_FONTS" />
+ <!-- Permission required for Launcher testing - DigitalWellbeingToastTest -->
+ <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"/>
+
<!-- Permission required for hotword detection service CTS tests -->
<uses-permission android:name="android.permission.MANAGE_HOTWORD_DETECTION" />
<uses-permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" />
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/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 338bff8e..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/people_space_placeholder_layout.xml b/packages/SystemUI/res/layout/people_space_placeholder_layout.xml
index abb771b..143c173 100644
--- a/packages/SystemUI/res/layout/people_space_placeholder_layout.xml
+++ b/packages/SystemUI/res/layout/people_space_placeholder_layout.xml
@@ -34,7 +34,7 @@
<LinearLayout
android:orientation="vertical"
android:paddingEnd="20dp"
- android:gravity="bottom"
+ android:gravity="start|bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
diff --git a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
index 1086a13..25a1165 100644
--- a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
@@ -113,7 +113,6 @@
android:id="@+id/name"
android:layout_gravity="start|center_vertical"
android:gravity="start|center_vertical"
- android:layout_weight="1"
android:text="@string/empty_user_name"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
@@ -124,8 +123,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
+ android:layout_weight="1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TextView
android:id="@+id/messages_count"
android:gravity="end"
+ android:layout_weight="1"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
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..1524df2 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>
@@ -1232,7 +1236,7 @@
<!-- Blur radius on status bar window and power menu -->
<dimen name="min_window_blur_radius">1px</dimen>
- <dimen name="max_window_blur_radius">150px</dimen>
+ <dimen name="max_window_blur_radius">72px</dimen>
<!-- How much into a DisplayCutout's bounds we can go, on each side -->
<dimen name="display_cutout_margin_consumption">0px</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/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 74aa138..fb9aa4a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -46,12 +46,13 @@
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRectF.set(destinationBounds);
mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+ final float cornerRadius = getScaledCornerRadius(sourceBounds, destinationBounds);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
.setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top)
- .setCornerRadius(leash, mCornerRadius);
+ .setCornerRadius(leash, cornerRadius);
return new PictureInPictureSurfaceTransaction(
mTmpDestinationRectF.left, mTmpDestinationRectF.top,
- mTmpFloat9, 0 /* rotation */, mCornerRadius, sourceBounds);
+ mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds);
}
public PictureInPictureSurfaceTransaction scale(
@@ -62,11 +63,12 @@
mTmpDestinationRectF.set(destinationBounds);
mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
mTmpTransform.postRotate(degree, 0, 0);
+ final float cornerRadius = getScaledCornerRadius(sourceBounds, destinationBounds);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
.setPosition(leash, positionX, positionY)
- .setCornerRadius(leash, mCornerRadius);
+ .setCornerRadius(leash, cornerRadius);
return new PictureInPictureSurfaceTransaction(
- positionX, positionY, mTmpFloat9, degree, mCornerRadius, sourceBounds);
+ positionX, positionY, mTmpFloat9, degree, cornerRadius, sourceBounds);
}
public PictureInPictureSurfaceTransaction scaleAndCrop(
@@ -83,12 +85,15 @@
final float left = destinationBounds.left - insets.left * scale;
final float top = destinationBounds.top - insets.top * scale;
mTmpTransform.setScale(scale, scale);
+ final Rect cornerRadiusRect = new Rect(destinationBounds);
+ cornerRadiusRect.inset(insets);
+ final float cornerRadius = getScaledCornerRadius(sourceBounds, cornerRadiusRect);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
.setWindowCrop(leash, mTmpDestinationRect)
.setPosition(leash, left, top)
- .setCornerRadius(leash, mCornerRadius);
+ .setCornerRadius(leash, cornerRadius);
return new PictureInPictureSurfaceTransaction(
- left, top, mTmpFloat9, 0 /* rotation */, mCornerRadius, mTmpDestinationRect);
+ left, top, mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect);
}
public PictureInPictureSurfaceTransaction scaleAndRotate(
@@ -105,12 +110,22 @@
: (float) destinationBounds.height() / sourceBounds.height();
mTmpTransform.setRotate(degree, 0, 0);
mTmpTransform.postScale(scale, scale);
+ final Rect cornerRadiusRect = new Rect(destinationBounds);
+ cornerRadiusRect.inset(insets);
+ final float cornerRadius = getScaledCornerRadius(sourceBounds, cornerRadiusRect);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
.setWindowCrop(leash, mTmpDestinationRect)
.setPosition(leash, positionX, positionY)
- .setCornerRadius(leash, mCornerRadius);
+ .setCornerRadius(leash, cornerRadius);
return new PictureInPictureSurfaceTransaction(
- positionX, positionY, mTmpFloat9, degree, mCornerRadius, mTmpDestinationRect);
+ positionX, positionY, mTmpFloat9, degree, cornerRadius, mTmpDestinationRect);
+ }
+
+ /** @return the round corner radius scaled by given from and to bounds */
+ private float getScaledCornerRadius(Rect fromBounds, Rect toBounds) {
+ final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height())
+ / Math.hypot(toBounds.width(), toBounds.height()));
+ return mCornerRadius * scale;
}
/** @return {@link SurfaceControl.Transaction} instance with vsync-id */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index 700ec49..8e65560 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -152,4 +152,15 @@
Log.e(TAG, "Failed to detach the navigation bar from app", e);
}
}
+
+ /**
+ * @see IRecentsAnimationController#animateNavigationBarToApp(long)
+ */
+ public void animateNavigationBarToApp(long duration) {
+ try {
+ mAnimationController.animateNavigationBarToApp(duration);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to animate the navigation bar to app", e);
+ }
+ }
}
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/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index fcb090a..717c715 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -66,6 +66,7 @@
import java.io.PrintWriter;
import java.text.NumberFormat;
+import java.util.Collections;
import java.util.Locale;
/**
@@ -178,7 +179,13 @@
// Initialize listeners.
mMirrorViewRunnable = () -> {
if (mMirrorView != null) {
+ final Rect oldViewBounds = new Rect(mMirrorViewBounds);
mMirrorView.getBoundsOnScreen(mMirrorViewBounds);
+ if (oldViewBounds.width() != mMirrorViewBounds.width()
+ || oldViewBounds.height() != mMirrorViewBounds.height()) {
+ mMirrorView.setSystemGestureExclusionRects(Collections.singletonList(
+ new Rect(0, 0, mMirrorViewBounds.width(), mMirrorViewBounds.height())));
+ }
updateSystemUIStateIfNeeded();
mWindowMagnifierCallback.onWindowMagnifierBoundsChanged(
mDisplayId, mMirrorViewBounds);
@@ -269,6 +276,7 @@
if (mMirrorWindowControl != null) {
mMirrorWindowControl.destroyControl();
}
+ mMirrorViewBounds.setEmpty();
updateSystemUIStateIfNeeded();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
index ca59393..76fb49a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
@@ -130,19 +130,29 @@
@Override
public void onAuthenticationFailed(
@Modality int modality, @Nullable String failureReason) {
- if (modality == TYPE_FACE && mActiveSensorType == TYPE_FACE) {
- // switching from face -> fingerprint mode, suppress soft error messages
- failureReason = mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead);
+ super.onAuthenticationFailed(modality, checkErrorForFallback(failureReason));
+ }
+
+ @Override
+ public void onError(int modality, String error) {
+ super.onError(modality, checkErrorForFallback(error));
+ }
+
+ private String checkErrorForFallback(String message) {
+ if (mActiveSensorType == TYPE_FACE) {
+ Log.d(TAG, "Falling back to fingerprint: " + message);
+
+ // switching from face -> fingerprint mode, suppress root error messages
+ mCallback.onAction(Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR);
+ return mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead);
}
- super.onAuthenticationFailed(modality, failureReason);
+ return message;
}
@Override
@BiometricState
protected int getStateForAfterError() {
if (mActiveSensorType == TYPE_FACE) {
- mHandler.post(() -> mCallback.onAction(
- Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
return STATE_AUTHENTICATING;
}
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..f975a80 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");
+ }
});
}
@@ -394,9 +405,8 @@
? event.getPointerId(0)
: event.findPointerIndex(mActivePointerId);
if (idx == event.getActionIndex()) {
- final float x = event.getX(idx);
- final float y = event.getY(idx);
- if (isWithinSensorArea(udfpsView, x, y, fromUdfpsView)) {
+ if (isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx),
+ fromUdfpsView)) {
if (mVelocityTracker == null) {
// touches could be injected, so the velocity tracker may not have
// been initialized (via ACTION_DOWN).
@@ -416,7 +426,8 @@
final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime;
if (!isIlluminationRequested && !mGoodCaptureReceived &&
!exceedsVelocityThreshold) {
- onFingerDown((int) x, (int) y, minor, major);
+ onFingerDown((int) event.getRawX(), (int) event.getRawY(), minor,
+ major);
Log.v(TAG, "onTouch | finger down: " + touchInfo);
mTouchLogTime = SystemClock.elapsedRealtime();
mPowerManager.userActivity(SystemClock.uptimeMillis(),
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..567d0cb 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(),
@@ -416,8 +422,12 @@
// add spacers if necessary to keep control size consistent
val mod = selectedStructure.controls.size % maxColumns
var spacersToAdd = if (mod == 0) 0 else maxColumns - mod
+ val margin = context.resources.getDimensionPixelSize(R.dimen.control_spacing)
while (spacersToAdd > 0) {
- lastRow.addView(Space(context), LinearLayout.LayoutParams(0, 0, 1f))
+ val lp = LinearLayout.LayoutParams(0, 0, 1f).apply {
+ setMarginStart(margin)
+ }
+ lastRow.addView(Space(context), lp)
spacersToAdd--
}
}
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/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 9aeb63d..0da84fb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -163,8 +163,10 @@
) {
if (useMediaResumption) {
// If this had been started from a resume state, disconnect now that it's live
- mediaBrowser?.disconnect()
- mediaBrowser = null
+ if (!key.equals(oldKey)) {
+ mediaBrowser?.disconnect()
+ mediaBrowser = null
+ }
// If we don't have a resume action, check if we haven't already
if (data.resumeAction == null && !data.hasCheckedForResume && data.isLocalSession) {
// TODO also check for a media button receiver intended for restarting (b/154127084)
@@ -194,6 +196,9 @@
*/
private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
Log.d(TAG, "Testing if we can connect to $componentName")
+ // Set null action to prevent additional attempts to connect
+ mediaDataManager.setResumeAction(key, null)
+ mediaBrowser?.disconnect()
mediaBrowser = mediaBrowserFactory.create(
object : ResumeMediaBrowser.Callback() {
override fun onConnected() {
@@ -202,7 +207,6 @@
override fun onError() {
Log.e(TAG, "Cannot resume with $componentName")
- mediaDataManager.setResumeAction(key, null)
mediaBrowser = null
}
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/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 72d382a..06817b9 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -348,7 +348,7 @@
if (mWidth >= getSizeInDp(R.dimen.required_width_for_medium)) {
int spaceAvailableForPadding =
mHeight - (getSizeInDp(R.dimen.avatar_size_for_medium)
- + getLineHeightFromResource(
+ + 4 + getLineHeightFromResource(
R.dimen.name_text_size_for_content));
if (DEBUG) {
Log.d(TAG, "Medium view for mWidth: " + mWidth + " mHeight: " + mHeight
@@ -558,9 +558,6 @@
}
views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
views.setTextViewText(R.id.text_content, statusText);
- if (mLayoutSize == LAYOUT_LARGE) {
- views.setInt(R.id.content, "setGravity", Gravity.BOTTOM);
- }
Icon statusIcon = status.getIcon();
if (statusIcon != null) {
@@ -570,6 +567,7 @@
// Show 1-line subtext on large layout with status images.
if (mLayoutSize == LAYOUT_LARGE) {
if (DEBUG) Log.d(TAG, "Remove name for large");
+ views.setInt(R.id.content, "setGravity", Gravity.BOTTOM);
views.setViewVisibility(R.id.name, View.GONE);
views.setColorAttr(R.id.text_content, "setTextColor",
android.R.attr.textColorPrimary);
@@ -830,6 +828,8 @@
views.setViewPadding(R.id.name, 0, 0, 0,
mContext.getResources().getDimensionPixelSize(
R.dimen.below_name_text_padding));
+ // All large layouts besides missed calls & statuses with images, have gravity top.
+ views.setInt(R.id.content, "setGravity", Gravity.TOP);
}
// For all layouts except Missed Calls, ensure predefined icon is regular sized.
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 39faf5a..775a3af 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -199,7 +199,8 @@
"Received updated conversation: "
+ conversation.getShortcutInfo().getLabel());
}
- updateWidgetsWithConversationChanged(conversation);
+ mBgExecutor.execute(() ->
+ updateWidgetsWithConversationChanged(conversation));
}
}
@@ -675,7 +676,7 @@
updatedTile.setUserIcon(icon);
}
if (DEBUG) Log.d(TAG, "Statuses: " + conversation.getStatuses().toString());
- NotificationChannel channel = conversation.getParentNotificationChannel();
+ NotificationChannel channel = conversation.getNotificationChannel();
if (channel != null) {
if (DEBUG) Log.d(TAG, "Important:" + channel.isImportantConversation());
updatedTile.setIsImportantConversation(channel.isImportantConversation());
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/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 1f9221c..eaa6659 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -554,16 +554,15 @@
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
withWindowAttached(() -> {
- mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
- if (mLastScrollCaptureRequest != null) {
- mLastScrollCaptureRequest.cancel(true);
- }
- mLastScrollCaptureRequest = mScrollCaptureClient.request(DEFAULT_DISPLAY);
- mLastScrollCaptureRequest.addListener(() ->
- onScrollCaptureResponseReady(mLastScrollCaptureRequest), mMainExecutor);
+ requestScrollCapture();
mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
(overrideConfig, newDisplayId) -> {
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+ // Hide the scroll chip until we know it's available in this orientation
+ mScreenshotView.hideScrollChip();
+ // Delay scroll capture eval a bit to allow the underlying activity
+ // to set up in the new orientation.
+ mScreenshotHandler.postDelayed(this::requestScrollCapture, 150);
updateDisplayCutout();
}
});
@@ -593,6 +592,16 @@
cancelTimeout(); // restarted after animation
}
+ private void requestScrollCapture() {
+ mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
+ if (mLastScrollCaptureRequest != null) {
+ mLastScrollCaptureRequest.cancel(true);
+ }
+ mLastScrollCaptureRequest = mScrollCaptureClient.request(DEFAULT_DISPLAY);
+ mLastScrollCaptureRequest.addListener(() ->
+ onScrollCaptureResponseReady(mLastScrollCaptureRequest), mMainExecutor);
+ }
+
private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) {
try {
if (mLastScrollCaptureResponse != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index facebee..77e1d15 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -204,6 +204,10 @@
mSwipeDismissHandler = new SwipeDismissHandler();
}
+ public void hideScrollChip() {
+ mScrollChip.setVisibility(View.GONE);
+ }
+
/**
* Called to display the scroll action chip when support is detected.
*
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..7e676197 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/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index f7b3a35..085a076 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -26,6 +26,8 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
@@ -53,6 +55,11 @@
private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
private static final String TAG = "NotificationShelf";
+ // More extreme version of SLOW_OUT_LINEAR_IN which keeps the icon nearly invisible until after
+ // the next icon has translated out of the way, to avoid overlapping.
+ private static final Interpolator ICON_ALPHA_INTERPOLATOR =
+ new PathInterpolator(0.6f, 0f, 0.6f, 0f);
+
private NotificationIconContainer mShelfIcons;
private int[] mTmp = new int[2];
private boolean mHideBackground;
@@ -659,7 +666,7 @@
if (iconState == null) {
return;
}
- iconState.alpha = transitionAmount;
+ iconState.alpha = ICON_ALPHA_INTERPOLATOR.getInterpolation(transitionAmount);
boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
iconState.hidden = isAppearing
|| (view instanceof ExpandableNotificationRow
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/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 84728f6..760bee2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -98,8 +98,6 @@
}
}
- private var animatingScreenOff = false
-
private var collapsedEnoughToHide: Boolean = false
var pulsing: Boolean = false
@@ -236,11 +234,11 @@
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
- if (overrideDozeAmountIfBypass()) {
+ if (overrideDozeAmountIfAnimatingScreenOff(linear)) {
return
}
- if (overrideDozeAmountIfAnimatingScreenOff(linear)) {
+ if (overrideDozeAmountIfBypass()) {
return
}
@@ -267,7 +265,7 @@
override fun onStateChanged(newState: Int) {
if (unlockedScreenOffAnimationController.shouldPlayScreenOffAnimation()) {
- if (animatingScreenOff &&
+ if (unlockedScreenOffAnimationController.isScreenOffAnimationPlaying() &&
state == StatusBarState.KEYGUARD &&
newState == StatusBarState.SHADE) {
// If we're animating the screen off and going from KEYGUARD back to SHADE, the
@@ -275,12 +273,16 @@
// dozing) so that the notifications are no longer hidden.
setDozeAmount(0f, 0f)
}
-
- animatingScreenOff =
- state == StatusBarState.SHADE && newState == StatusBarState.KEYGUARD
}
- overrideDozeAmountIfBypass()
+ if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
+ return
+ }
+
+ if (overrideDozeAmountIfBypass()) {
+ return
+ }
+
if (bypassController.bypassEnabled &&
newState == StatusBarState.KEYGUARD && state == StatusBarState.SHADE_LOCKED &&
(!statusBarStateController.isDozing || shouldAnimateVisibility())) {
@@ -330,12 +332,7 @@
* animation. If true, the original doze amount should be ignored.
*/
private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
- if (animatingScreenOff) {
- if (linearDozeAmount == 1f) {
- animatingScreenOff = false
- return false
- }
-
+ if (unlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
setDozeAmount(1f, 1f)
return true
}
@@ -395,11 +392,6 @@
override fun onDozingChanged(isDozing: Boolean) {
if (isDozing) {
setNotificationsVisible(visible = false, animate = false, increaseSpeed = false)
- } else {
- // We only unset the flag once we fully went asleep. If the user interrupts the
- // animation in the middle, we have to abort the animation as well to make sure
- // the notifications are visible again.
- animatingScreenOff = 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/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 9390c81..8277fae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -106,6 +106,7 @@
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.util.Assert;
@@ -456,6 +457,7 @@
private long mNumHeadsUp;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final FeatureFlags mFeatureFlags;
+ private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
@@ -497,11 +499,13 @@
GroupMembershipManager groupMembershipManager,
GroupExpansionManager groupExpansionManager,
AmbientState ambientState,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
super(context, attrs, 0, 0);
Resources res = getResources();
mSectionsManager = notificationSectionsManager;
mFeatureFlags = featureFlags;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res);
mSectionsManager.initialize(this, LayoutInflater.from(context));
mSections = mSectionsManager.createSectionsForBuckets();
@@ -606,6 +610,7 @@
RemoteInputController remoteInputController = mRemoteInputManager.getController();
boolean showFooterView = (showDismissView || mController.hasActiveNotifications())
&& mStatusBarState != StatusBarState.KEYGUARD
+ && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
&& (remoteInputController == null || !remoteInputController.isRemoteInputActive());
boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
@@ -2097,11 +2102,13 @@
if (height != 0) {
height += mPaddingBetweenElements;
}
- height += calculateGapHeight(previousView, expandableView, numShownItems);
+ float gapHeight = calculateGapHeight(previousView, expandableView, numShownNotifs);
+ height += gapHeight;
height += viewHeight;
numShownItems++;
- if (!(expandableView instanceof MediaHeaderView)) {
+ if (viewHeight > 0 || !(expandableView instanceof MediaHeaderView)) {
+ // Only count the media as a notification if it has a positive height.
numShownNotifs++;
}
previousView = expandableView;
@@ -4615,7 +4622,7 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@VisibleForTesting
- protected void setStatusBarState(int statusBarState) {
+ public void setStatusBarState(int statusBarState) {
mStatusBarState = statusBarState;
mAmbientState.setStatusBarState(statusBarState);
updateSpeedBumpIndex();
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/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index c911e3d..da37aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -83,9 +83,13 @@
private final Runnable mRemoveViewRunnable = this::removeView;
private final KeyguardBypassController mKeyguardBypassController;
private KeyguardHostViewController mKeyguardViewController;
+ private final List<KeyguardResetCallback> mResetCallbacks = new ArrayList<>();
private final Runnable mResetRunnable = ()-> {
if (mKeyguardViewController != null) {
mKeyguardViewController.resetSecurityContainer();
+ for (KeyguardResetCallback callback : mResetCallbacks) {
+ callback.onKeyguardReset();
+ }
}
};
@@ -573,6 +577,14 @@
}
}
+ public void addKeyguardResetCallback(KeyguardResetCallback callback) {
+ mResetCallbacks.add(callback);
+ }
+
+ public void removeKeyguardResetCallback(KeyguardResetCallback callback) {
+ mResetCallbacks.remove(callback);
+ }
+
public interface BouncerExpansionCallback {
void onFullyShown();
void onStartingToHide();
@@ -593,6 +605,10 @@
default void onVisibilityChanged(boolean isVisible) {}
}
+ public interface KeyguardResetCallback {
+ void onKeyguardReset();
+ }
+
/** Create a {@link KeyguardBouncer} once a container and bouncer callback are available. */
public static class Factory {
private final Context mContext;
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..20f3fc9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -139,6 +139,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -1126,10 +1127,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,28 +1320,33 @@
mNotificationStackScrollLayoutController.getHeight()
- minPadding
- shelfSize
- - bottomPadding
- - mKeyguardStatusViewController.getLogoutButtonHeight();
+ - bottomPadding;
int count = 0;
ExpandableView previousView = null;
for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
ExpandableView child = mNotificationStackScrollLayoutController.getChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- boolean
- suppressedSummary =
- mGroupManager != null && mGroupManager.isSummaryOfSuppressedGroup(
- row.getEntry().getSbn());
- if (suppressedSummary) {
- continue;
- }
- if (!canShowViewOnLockscreen(child)) {
- continue;
- }
- if (row.isRemoved()) {
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ boolean suppressedSummary = mGroupManager != null
+ && mGroupManager.isSummaryOfSuppressedGroup(row.getEntry().getSbn());
+ if (suppressedSummary) {
+ continue;
+ }
+ if (!canShowViewOnLockscreen(child)) {
+ continue;
+ }
+ if (row.isRemoved()) {
+ continue;
+ }
+ } else if (child instanceof MediaHeaderView) {
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ if (child.getIntrinsicHeight() == 0) {
+ continue;
+ }
+ } else {
continue;
}
availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */);
@@ -2233,6 +2236,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();
@@ -2531,7 +2535,9 @@
break;
case FLING_HIDE:
default:
- mQs.closeDetail();
+ if (mQs != null) {
+ mQs.closeDetail();
+ }
target = 0;
}
if (target == mQsExpansionHeight) {
@@ -4152,11 +4158,6 @@
entry.setHeadsUpIsVisible();
}
}
-
- @Override
- public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, isHeadsUp);
- }
}
private class HeightListener implements QS.HeightListener {
@@ -4284,7 +4285,8 @@
/**
* Reconfigures the shade to show the AOD UI (clock, smartspace, etc). This is called by the
* screen off animation controller in order to animate in AOD without "actually" fully switching
- * to the KEYGUARD state.
+ * to the KEYGUARD state, which is a heavy transition that causes jank as 10+ files react to the
+ * change.
*/
public void showAodUi() {
setDozing(true /* dozing */, false /* animate */, null);
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/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index b9b62b4..753def0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -86,7 +86,9 @@
@Override
public void notifyListeners(SignalCallback callback) {
if (mCurrentState.isCarrierMerged) {
- notifyListenersForCarrierWifi(callback);
+ if (mCurrentState.isDefault) {
+ notifyListenersForCarrierWifi(callback);
+ }
} else {
notifyListenersForNonCarrierWifi(callback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index d61848c..e0ff88b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -75,6 +75,10 @@
static final String COLOR_SOURCE_PRESET = "preset";
+ static final String COLOR_SOURCE_HOME = "home_wallpaper";
+
+ static final String COLOR_SOURCE_LOCK = "lock_wallpaper";
+
static final String TIMESTAMP_FIELD = "_applied_timestamp";
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index c23e0b4..ca1f55e 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -16,6 +16,8 @@
package com.android.systemui.theme;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
+import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME;
+import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK;
import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
@@ -200,36 +202,37 @@
String overlayPackageJson = mSecureSettings.getStringForUser(
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
currentUser);
- boolean isDestinationBoth = mWallpaperManager.getWallpaperId(
- WallpaperManager.FLAG_LOCK) < 0;
- if (!TextUtils.isEmpty(overlayPackageJson)) {
- try {
- JSONObject jsonObject = new JSONObject(overlayPackageJson);
- if (!COLOR_SOURCE_PRESET.equals(jsonObject.optString(OVERLAY_COLOR_SOURCE))
- && ((flags & latestWallpaperType) != 0)) {
- mSkipSettingChange = true;
- if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
- OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
- jsonObject.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
- jsonObject.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
- jsonObject.remove(OVERLAY_COLOR_SOURCE);
- jsonObject.remove(OVERLAY_COLOR_INDEX);
- }
- // Keep color_both value because users can change either or both home and
- // lock screen wallpapers.
- jsonObject.put(OVERLAY_COLOR_BOTH, isDestinationBoth ? "1" : "0");
-
- jsonObject.put(TIMESTAMP_FIELD, System.currentTimeMillis());
- if (DEBUG) {
- Log.d(TAG, "Updating theme setting from "
- + overlayPackageJson + " to " + jsonObject.toString());
- }
- mSecureSettings.putString(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
- jsonObject.toString());
+ boolean isDestinationBoth = (flags == (WallpaperManager.FLAG_SYSTEM
+ | WallpaperManager.FLAG_LOCK));
+ try {
+ JSONObject jsonObject = (overlayPackageJson == null) ? new JSONObject()
+ : new JSONObject(overlayPackageJson);
+ if (!COLOR_SOURCE_PRESET.equals(jsonObject.optString(OVERLAY_COLOR_SOURCE))
+ && ((flags & latestWallpaperType) != 0)) {
+ mSkipSettingChange = true;
+ if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
+ OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
+ jsonObject.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
+ jsonObject.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+ jsonObject.remove(OVERLAY_COLOR_INDEX);
}
- } catch (JSONException e) {
- Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
+ // Keep color_both value because users can change either or both home and
+ // lock screen wallpapers.
+ jsonObject.put(OVERLAY_COLOR_BOTH, isDestinationBoth ? "1" : "0");
+
+ jsonObject.put(OVERLAY_COLOR_SOURCE,
+ (flags == WallpaperManager.FLAG_LOCK) ? COLOR_SOURCE_LOCK
+ : COLOR_SOURCE_HOME);
+ jsonObject.put(TIMESTAMP_FIELD, System.currentTimeMillis());
+ if (DEBUG) {
+ Log.d(TAG, "Updating theme setting from "
+ + overlayPackageJson + " to " + jsonObject.toString());
+ }
+ mSecureSettings.putString(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ jsonObject.toString());
}
+ } catch (JSONException e) {
+ Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
}
reevaluateSystemTheme(false /* forceReload */);
}
@@ -237,11 +240,14 @@
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_STARTED.equals(intent.getAction())
- || Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction())) {
- if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+ boolean newWorkProfile = Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction());
+ boolean userStarted = Intent.ACTION_USER_STARTED.equals(intent.getAction());
+ boolean isManagedProfile = mUserManager.isManagedProfile(
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ if (userStarted || newWorkProfile) {
+ if (!mDeviceProvisionedController.isCurrentUserSetup() && isManagedProfile) {
Log.i(TAG, "User setup not finished when " + intent.getAction()
- + " was received. Deferring...");
+ + " was received. Deferring... Managed profile? " + isManagedProfile);
return;
}
if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
@@ -323,17 +329,22 @@
Runnable updateColors = () -> {
WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
getLatestWallpaperType());
- mMainExecutor.execute(() -> {
+ Runnable applyColors = () -> {
if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
mCurrentColors = systemColor;
reevaluateSystemTheme(false /* forceReload */);
- });
+ };
+ if (mDeviceProvisionedController.isCurrentUserSetup()) {
+ mMainExecutor.execute(applyColors);
+ } else {
+ applyColors.run();
+ }
};
// Whenever we're going directly to setup wizard, we need to process colors synchronously,
// otherwise we'll see some jank when the activity is recreated.
if (!mDeviceProvisionedController.isCurrentUserSetup()) {
- mMainExecutor.execute(updateColors);
+ updateColors.run();
} else {
mBgExecutor.execute(updateColors);
}
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/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index ad99e4d..f62069d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -77,6 +77,8 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
@LargeTest
@RunWith(AndroidTestingRunner.class)
public class WindowMagnificationControllerTest extends SysuiTestCase {
@@ -152,6 +154,19 @@
}
@Test
+ public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+ // Wait for Rects updated.
+ waitForIdleSync();
+
+ List<Rect> rects = mWindowManager.getAttachedView().getSystemGestureExclusionRects();
+ assertFalse(rects.isEmpty());
+ }
+
+ @Test
public void deleteWindowMagnification_destroyControl() {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
index aed0da6..82bf041 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
@@ -22,6 +22,8 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import android.content.Context;
@@ -124,16 +126,50 @@
}
@Test
- public void testModeUpdated_whenSwitchToFingerprint() {
+ public void testModeUpdated_onSoftError_whenSwitchToFingerprint() {
mFaceToFpView.onDialogAnimatedIn();
mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
waitForIdleSync();
verify(mIndicatorView).setText(
eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
+ verify(mCallback).onAction(
+ eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING, mFaceToFpView.mState);
}
+ @Test
+ public void testModeUpdated_onHardError_whenSwitchToFingerprint() {
+ mFaceToFpView.onDialogAnimatedIn();
+ mFaceToFpView.onError(TYPE_FACE, "oh no!");
+ waitForIdleSync();
+
+ verify(mIndicatorView).setText(
+ eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
+ verify(mCallback).onAction(
+ eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
+ assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING, mFaceToFpView.mState);
+ }
+
+ @Test
+ public void testFingerprintOnlyStartsOnFirstError() {
+ mFaceToFpView.onDialogAnimatedIn();
+ verify(mFaceToFpView.mIconController)
+ .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
+
+ mFaceToFpView.onDialogAnimatedIn();
+ mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
+ mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
+
+ reset(mCallback);
+
+ mFaceToFpView.onError(TYPE_FACE, "oh no!");
+ mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
+
+ verify(mCallback, never()).onAction(
+ eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
+ }
+
public class TestableView extends AuthBiometricFaceToFingerprintView {
public TestableView(Context context) {
super(context, null, new MockInjector());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index 4e1627f..150f4545 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -44,6 +44,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.isNotNull
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
@@ -183,6 +184,31 @@
}
@Test
+ fun testOnLoad_checksForResume_badService() {
+ // Set up MBS that will allow connection but not return valid media
+ val pm = mock(PackageManager::class.java)
+ whenever(mockContext.packageManager).thenReturn(pm)
+ val resolveInfo = ResolveInfo()
+ val serviceInfo = ServiceInfo()
+ serviceInfo.packageName = PACKAGE_NAME
+ resolveInfo.serviceInfo = serviceInfo
+ resolveInfo.serviceInfo.name = CLASS_NAME
+ val resumeInfo = listOf(resolveInfo)
+ whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo)
+
+ whenever(resumeBrowser.testConnection()).thenAnswer {
+ callbackCaptor.value.onError()
+ }
+
+ // When media data is loaded that has not been checked yet, and does not have a MBS
+ resumeListener.onMediaDataLoaded(KEY, null, data)
+ executor.runAllReady()
+
+ // Then we report back to the manager
+ verify(mediaDataManager).setResumeAction(eq(KEY), eq(null))
+ }
+
+ @Test
fun testOnLoad_remotePlayback_doesNotCheck() {
// When media data is loaded that has not been checked yet, and is not local
val dataRemote = data.copy(isLocalSession = false)
@@ -217,10 +243,11 @@
// Then we test whether the service is valid
executor.runAllReady()
+ verify(mediaDataManager).setResumeAction(eq(KEY), eq(null))
verify(resumeBrowser).testConnection()
- // And since it is, we report back to the manager
- verify(mediaDataManager).setResumeAction(eq(KEY), any())
+ // And since it is, we send info to the manager
+ verify(mediaDataManager).setResumeAction(eq(KEY), isNotNull())
// But we do not tell it to add new controls
verify(mediaDataManager, never())
@@ -291,8 +318,9 @@
// Then we test whether the service is valid and set the resume action
executor.runAllReady()
+ verify(mediaDataManager).setResumeAction(eq(KEY), eq(null))
verify(resumeBrowser).testConnection()
- verify(mediaDataManager).setResumeAction(eq(KEY), capture(actionCaptor))
+ verify(mediaDataManager, times(2)).setResumeAction(eq(KEY), capture(actionCaptor))
// When the resume action is run
actionCaptor.value.run()
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/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 8758e16..43f7284 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -65,6 +65,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.KeyguardBypassEnabledProvider;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import org.junit.Assert;
import org.junit.Before;
@@ -105,6 +106,7 @@
@Mock private NotificationSwipeHelper mNotificationSwipeHelper;
@Mock private NotificationStackScrollLayoutController mStackScrollLayoutController;
@Mock private FeatureFlags mFeatureFlags;
+ @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@Before
@UiThreadTest
@@ -143,7 +145,8 @@
mGroupMembershipManger,
mGroupExpansionManager,
mAmbientState,
- mFeatureFlags);
+ mFeatureFlags,
+ mUnlockedScreenOffAnimationController);
mStackScrollerInternal.initView(getContext(), mKeyguardBypassEnabledProvider,
mNotificationSwipeHelper);
mStackScroller = spy(mStackScrollerInternal);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 3d2a0f1..208790b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -232,9 +232,9 @@
verify(mSecureSettings).putString(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
- assertThat(updatedSetting.getValue().contains("android.theme.customization.system_palette"))
+ assertThat(updatedSetting.getValue().contains("android.theme.customization.accent_color"))
.isFalse();
- assertThat(updatedSetting.getValue().contains("android.theme.customization.color_source"))
+ assertThat(updatedSetting.getValue().contains("android.theme.customization.system_palette"))
.isFalse();
assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
.isFalse();
@@ -289,7 +289,8 @@
.thenReturn(jsonString);
when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(-1);
- mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+ mColorsListener.getValue().onColorsChanged(mainColors,
+ WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
verify(mSecureSettings).putString(
@@ -303,6 +304,60 @@
}
@Test
+ public void onWallpaperColorsChanged_changeLockWallpaper() {
+ // Should ask for a new theme when wallpaper colors change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+ String jsonString =
+ "{\"android.theme.customization.system_palette\":\"override.package.name\","
+ + "\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(1);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings).putString(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+ assertThat(updatedSetting.getValue().contains(
+ "android.theme.customization.color_source\":\"lock_wallpaper")).isTrue();
+ assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
+ .isFalse();
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ }
+
+ @Test
+ public void onWallpaperColorsChanged_changeHomeWallpaper() {
+ // Should ask for a new theme when wallpaper colors change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+ String jsonString =
+ "{\"android.theme.customization.system_palette\":\"override.package.name\","
+ + "\"android.theme.customization.color_source\":\"lock_wallpaper\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(-1);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings).putString(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+ assertThat(updatedSetting.getValue().contains(
+ "android.theme.customization.color_source\":\"home_wallpaper")).isTrue();
+ assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
+ .isFalse();
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ }
+
+ @Test
public void onWallpaperColorsChanged_ResetThemeWhenFromLatestWallpaper() {
// Should ask for a new theme when the colors of the last applied wallpaper change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -374,8 +429,19 @@
}
@Test
+ public void onUserAdded_appliesTheme_ifNotManagedProfile() {
+ reset(mDeviceProvisionedController);
+ when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
+ mBroadcastReceiver.getValue().onReceive(null,
+ new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ }
+
+ @Test
public void onProfileAdded_ignoresUntilSetupComplete() {
reset(mDeviceProvisionedController);
+ when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
mBroadcastReceiver.getValue().onReceive(null,
new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
verify(mThemeOverlayApplier, never())
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..c2259eb 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -84,8 +84,8 @@
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManager.AttributedOpEntry;
+import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManager.HistoricalOps;
import android.app.AppOpsManager.Mode;
import android.app.AppOpsManager.OpEntry;
@@ -3255,13 +3255,6 @@
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
}
- // TODO b/184963112: remove once full blaming is implemented
- private boolean isRecognitionServiceTemp(int code, String packageName) {
- return code == OP_RECORD_AUDIO
- && (packageName.equals("com.google.android.googlequicksearchbox")
- || packageName.equals("com.google.android.tts"));
- }
-
private SyncNotedAppOp noteProxyOperationImpl(int code, AttributionSource attributionSource,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation) {
@@ -3289,8 +3282,7 @@
final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
final boolean isProxyTrusted = mContext.checkPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
- == PackageManager.PERMISSION_GRANTED || isSelfBlame
- || isRecognitionServiceTemp(code, proxyPackageName);
+ == PackageManager.PERMISSION_GRANTED || isSelfBlame;
if (!skipProxyOperation) {
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
@@ -4502,8 +4494,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/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 36dc5cd..ef02a47 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -355,10 +355,6 @@
}
}
- private void cancelAllFingerprintSensors() {
- cancelAllSensors(sensor -> sensor.modality == TYPE_FINGERPRINT);
- }
-
private void cancelAllSensors() {
cancelAllSensors(sensor -> true);
}
@@ -387,6 +383,9 @@
*/
boolean onErrorReceived(int sensorId, int cookie, @BiometricConstants.Errors int error,
int vendorCode) throws RemoteException {
+ if (DEBUG) {
+ Slog.v(TAG, "onErrorReceived sensor: " + sensorId + " error: " + error);
+ }
if (!containsCookie(cookie)) {
Slog.e(TAG, "Unknown/expired cookie: " + cookie);
@@ -454,12 +453,14 @@
// a round trip to SystemUI.
mClientReceiver.onError(modality, error, vendorCode);
return true;
- } else if (shouldErrorTriggerMultiSensorTransition()) {
- // wait for the UI to signal when modality should switch
- mMultiSensorState = MULTI_SENSOR_STATE_SWITCHING;
- Slog.d(TAG, "onErrorReceived: waiting for modality switch callback");
} else {
mState = STATE_ERROR_PENDING_SYSUI;
+ if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT
+ && mMultiSensorState == MULTI_SENSOR_STATE_FACE_SCANNING) {
+ // wait for the UI to signal when modality should switch
+ Slog.d(TAG, "onErrorReceived: waiting for modality switch callback");
+ mMultiSensorState = MULTI_SENSOR_STATE_SWITCHING;
+ }
mStatusBarService.onBiometricError(modality, error, vendorCode);
}
break;
@@ -627,7 +628,7 @@
void onDeviceCredentialPressed() {
// Cancel authentication. Skip the token/package check since we are cancelling
// from system server. The interface is permission protected so this is fine.
- cancelBiometricOnly();
+ cancelAllSensors();
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
}
@@ -733,12 +734,10 @@
}
mClientReceiver.onAuthenticationSucceeded(
Utils.getAuthenticationTypeForResult(reason));
- cancelBiometricOnly();
break;
case BiometricPrompt.DISMISSED_REASON_NEGATIVE:
mClientReceiver.onDialogDismissed(reason);
- cancelBiometricOnly();
break;
case BiometricPrompt.DISMISSED_REASON_USER_CANCEL:
@@ -747,7 +746,6 @@
BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
0 /* vendorCode */
);
- cancelBiometricOnly();
break;
case BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED:
@@ -765,6 +763,9 @@
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
+ } finally {
+ // ensure everything is cleaned up when dismissed
+ cancelAllSensors();
}
}
@@ -780,8 +781,8 @@
|| mState == STATE_AUTH_STARTED
|| mState == STATE_AUTH_STARTED_UI_SHOWING;
+ cancelAllSensors();
if (authStarted && !force) {
- cancelAllSensors();
// Wait for ERROR_CANCELED to be returned from the sensors
return false;
} else {
@@ -804,22 +805,6 @@
return false;
}
- /**
- * Cancels biometric authentication only. AuthSession may either be going into
- * {@link #STATE_SHOWING_DEVICE_CREDENTIAL} or dismissed.
- */
- private void cancelBiometricOnly() {
- if (mState == STATE_AUTH_STARTED || mState == STATE_AUTH_STARTED_UI_SHOWING) {
- cancelAllSensors();
- } else if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
- cancelAllFingerprintSensors();
- } else {
- if (DEBUG) {
- Slog.v(TAG, "nothing to cancel - wrong state: " + mState);
- }
- }
- }
-
boolean isCrypto() {
return mOperationId != 0;
}
@@ -896,13 +881,6 @@
}
}
- private boolean shouldErrorTriggerMultiSensorTransition() {
- if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
- return mMultiSensorState == MULTI_SENSOR_STATE_FACE_SCANNING;
- }
- return false;
- }
-
@BiometricMultiSensorMode
private static int getMultiSensorModeForNewSession(Collection<BiometricSensor> sensors) {
boolean hasFace = false;
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/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
index ba7f44f..1da45bd 100644
--- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
@@ -305,6 +305,10 @@
setState(state -> state.withIdentity(identity));
}
+ public final Set<String> getExtraAttributionTags() {
+ return mInternalState.get().state.extraAttributionTags;
+ }
+
/**
* Call this method to report a change in the provider's extra attribution tags.
*/
diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
index 8193644..021e8db 100644
--- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
@@ -32,6 +32,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collections;
+import java.util.Set;
/**
* Represents a location provider that may switch between a mock implementation and a real
@@ -296,6 +297,10 @@
if (identity != null) {
pw.println("identity=" + identity);
}
+ Set<String> extraAttributionTags = getExtraAttributionTags();
+ if (!extraAttributionTags.isEmpty()) {
+ pw.println("extra attribution tags=" + extraAttributionTags);
+ }
ProviderProperties properties = getProperties();
if (properties != null) {
pw.println("properties=" + properties);
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 5df7870..a9641f0 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -17,6 +17,7 @@
package com.android.server.location.provider.proxy;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+import static com.android.server.location.LocationManagerService.TAG;
import android.annotation.Nullable;
import android.content.Context;
@@ -32,6 +33,7 @@
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.server.FgThread;
@@ -44,6 +46,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -78,6 +81,7 @@
final Context mContext;
final ServiceWatcher mServiceWatcher;
+ final String mName;
@GuardedBy("mLock")
final ArrayList<Runnable> mFlushListeners = new ArrayList<>(0);
@@ -101,6 +105,7 @@
mServiceWatcher = ServiceWatcher.create(context, provider,
new CurrentUserServiceSupplier(context, action, enableOverlayResId,
nonOverlayPackageResId), this);
+ mName = provider;
mProxy = null;
mRequest = ProviderRequest.EMPTY_REQUEST;
@@ -249,6 +254,8 @@
String tagsStr = mBoundServiceInfo.getMetadata().getString(EXTRA_LOCATION_TAGS);
if (!TextUtils.isEmpty(tagsStr)) {
attributionTags = tagsStr.split(LOCATION_TAGS_SEPARATOR);
+ Log.i(TAG, mName + " provider loaded extra attribution tags: "
+ + Arrays.toString(attributionTags));
}
}
ArraySet<String> extraAttributionTags = new ArraySet<>(attributionTags);
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/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 8202587..4b0eb65 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3760,11 +3760,6 @@
return true;
}
- // Retrying commit.
- if (mIncrementalFileStorages != null) {
- return false;
- }
-
final List<InstallationFileParcel> addedFiles = new ArrayList<>();
final List<String> removedFiles = new ArrayList<>();
@@ -3925,18 +3920,24 @@
(pkgInfo != null && pkgInfo.applicationInfo != null) ? new File(
pkgInfo.applicationInfo.getCodePath()).getParentFile() : null;
- mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext, stageDir,
- inheritedDir, params, statusListener, healthCheckParams, healthListener,
- addedFiles, perUidReadTimeouts,
- new IPackageLoadingProgressCallback.Stub() {
- @Override
- public void onPackageLoadingProgressChanged(float progress) {
- synchronized (mProgressLock) {
- mIncrementalProgress = progress;
- computeProgressLocked(true);
+ if (mIncrementalFileStorages == null) {
+ mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext,
+ stageDir, inheritedDir, params, statusListener, healthCheckParams,
+ healthListener, addedFiles, perUidReadTimeouts,
+ new IPackageLoadingProgressCallback.Stub() {
+ @Override
+ public void onPackageLoadingProgressChanged(float progress) {
+ synchronized (mProgressLock) {
+ mIncrementalProgress = progress;
+ computeProgressLocked(true);
+ }
}
- }
- });
+ });
+ } else {
+ // Retrying commit.
+ mIncrementalFileStorages.startLoading(params, statusListener, healthCheckParams,
+ healthListener, perUidReadTimeouts);
+ }
return false;
} catch (IOException e) {
throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(),
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 071119f..a8cc5fd 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");
@@ -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;
@@ -23533,11 +23607,6 @@
throw new IllegalArgumentException("Must specify a component");
}
- boolean componentExists = mComponentResolver.componentExists(componentName);
- if (!componentExists) {
- throw new IllegalArgumentException("Component " + componentName + " not found");
- }
-
int callingUid = Binder.getCallingUid();
String componentPkgName = componentName.getPackageName();
@@ -23569,6 +23638,10 @@
"Changing the label is not allowed for " + componentName);
}
+ if (!mComponentResolver.componentExists(componentName)) {
+ throw new IllegalArgumentException("Component " + componentName + " not found");
+ }
+
if (!pkgSetting.overrideNonLocalizedLabelAndIcon(componentName, nonLocalizedLabel,
icon, userId)) {
// Nothing changed
@@ -25172,7 +25245,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;
@@ -25512,7 +25585,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;
@@ -25526,7 +25599,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/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4bc87a2..7aa1c3a 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3997,15 +3997,18 @@
final int uninstallReason = (shouldMaybeInstall && !shouldReallyInstall) ?
UNINSTALL_REASON_USER_TYPE : UNINSTALL_REASON_UNKNOWN;
ps.setUninstallReason(uninstallReason, userHandle);
- if (!shouldReallyInstall) {
+ if (shouldReallyInstall) {
+ // Need to create a data directory for all apps installed for this user.
+ // Accumulate all required args and call the installer after mPackages lock
+ // has been released
+ final String seInfo = AndroidPackageUtils.getSeInfo(ps.pkg, ps);
+ batch.createAppData(ps.volumeUuid, ps.name, userHandle,
+ StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE,
+ ps.appId, seInfo, ps.pkg.getTargetSdkVersion());
+ } else {
+ // Make sure the app is excluded from storage mapping for this user
writeKernelMappingLPr(ps);
}
- // Need to create a data directory for all apps under this user. Accumulate all
- // required args and call the installer after mPackages lock has been released
- final String seInfo = AndroidPackageUtils.getSeInfo(ps.pkg, ps);
- batch.createAppData(ps.volumeUuid, ps.name, userHandle,
- StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE, ps.appId,
- seInfo, ps.pkg.getTargetSdkVersion());
}
}
t.traceBegin("createAppData");
diff --git a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
index f411c98..901f96f 100644
--- a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
+++ b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
@@ -61,7 +61,7 @@
* Before saving shortcuts.xml, and returning icons to the launcher, we wait for all pending
* saves to finish. However if it takes more than this long, we just give up and proceed.
*/
- private final long SAVE_WAIT_TIMEOUT_MS = 30 * 1000;
+ private final long SAVE_WAIT_TIMEOUT_MS = 5 * 1000;
private final ShortcutService mService;
@@ -281,7 +281,7 @@
}
final String path = file.getAbsolutePath();
- mService.postValue(shortcut, si -> si.setBitmapPath(path));
+ shortcut.setBitmapPath(path);
} catch (IOException | RuntimeException e) {
Slog.e(ShortcutService.TAG, "Unable to write bitmap to file", e);
@@ -296,14 +296,12 @@
Slog.d(TAG, "Saved bitmap.");
}
if (shortcut != null) {
- mService.postValue(shortcut, si -> {
- if (si.getBitmapPath() == null) {
- removeIcon(si);
- }
+ if (shortcut.getBitmapPath() == null) {
+ removeIcon(shortcut);
+ }
- // Whatever happened, remove this flag.
- si.clearFlags(ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE);
- });
+ // Whatever happened, remove this flag.
+ shortcut.clearFlags(ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE);
}
}
return true;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 1e9d7e1f..5f10277 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1208,16 +1208,6 @@
}
}
- void postValue(@NonNull final ShortcutInfo shortcutInfo,
- @NonNull final Consumer<ShortcutInfo> cb) {
- final String pkg = shortcutInfo.getPackage();
- final int userId = shortcutInfo.getUserId();
- final String id = shortcutInfo.getId();
- synchronized (mLock) {
- getPackageShortcutsLocked(pkg, userId).mutateShortcut(id, shortcutInfo, cb);
- }
- }
-
/** Return the last reset time. */
@GuardedBy("mLock")
long getLastResetTimeLocked() {
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 cd78bf9..a391dbc 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -5515,7 +5515,8 @@
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 5fcf4118..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 d976488..94005b2 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -67,6 +67,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();
@@ -231,8 +236,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);
}
@@ -241,10 +252,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);
}
@@ -360,13 +375,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/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index e718ba3..631be38 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -135,6 +135,7 @@
if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(),
newBoundServiceInfo)) {
+ Log.i(TAG, "[" + mTag + "] chose new implementation " + newBoundServiceInfo);
MyServiceConnection oldServiceConnection = mServiceConnection;
MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo);
mServiceConnection = newServiceConnection;
@@ -196,7 +197,9 @@
return;
}
- Log.i(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
+ }
Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
mBoundServiceInfo.getComponentName());
@@ -255,9 +258,7 @@
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
Preconditions.checkState(mBinder == null);
- if (D) {
- Log.d(TAG, "[" + mTag + "] connected to " + component.toShortString());
- }
+ Log.i(TAG, "[" + mTag + "] connected to " + component.toShortString());
mBinder = binder;
@@ -280,9 +281,7 @@
return;
}
- if (D) {
- Log.d(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo);
- }
+ Log.i(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo);
mBinder = null;
if (mServiceListener != null) {
@@ -294,9 +293,11 @@
public final void onBindingDied(ComponentName component) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- Log.i(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
+ Log.w(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
- onServiceChanged(true);
+ // introduce a small delay to prevent spamming binding over and over, since the likely
+ // cause of a binding dying is some package event that may take time to recover from
+ mHandler.postDelayed(() -> onServiceChanged(true), 500);
}
@Override
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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c888e54..53f1035 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -628,7 +628,7 @@
}
// scale if the crop height winds up not matching the recommended metrics
- needScale = wpData.mHeight != cropHint.height()
+ needScale = cropHint.height() > wpData.mHeight
|| cropHint.height() > GLHelper.getMaxTextureSize()
|| cropHint.width() > GLHelper.getMaxTextureSize();
@@ -752,7 +752,7 @@
f = new FileOutputStream(wallpaper.cropFile);
bos = new BufferedOutputStream(f, 32*1024);
- finalCrop.compress(Bitmap.CompressFormat.JPEG, 100, bos);
+ finalCrop.compress(Bitmap.CompressFormat.PNG, 100, bos);
bos.flush(); // don't rely on the implicit flush-at-close when noting success
success = true;
}
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/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index e5a634f..b6b8ad1 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -128,12 +128,9 @@
if (w == null || winHint != null && w != winHint) {
return;
}
- final boolean surfaceReady = w.isDrawn() // Regular case
- || w.isDragResizeChanged(); // Waiting for relayoutWindow to call preserveSurface.
- final boolean needsLetterbox = surfaceReady && shouldShowLetterboxUi(w);
updateRoundedCorners(w);
updateWallpaperForLetterbox(w);
- if (needsLetterbox) {
+ if (shouldShowLetterboxUi(w)) {
if (mLetterbox == null) {
mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
mActivityRecord.mWmService.mTransactionFactory,
@@ -161,19 +158,26 @@
}
}
- /**
- * @return {@code true} when the main window is letterboxed, this activity isn't transparent
- * and doesn't show a wallpaper.
- */
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
- return mainWindow.areAppWindowBoundsLetterboxed() && mActivityRecord.fillsParent()
+ return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
+ // Check that an activity isn't transparent.
+ && mActivityRecord.fillsParent()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
// activity is using blurred wallpaper for letterbox backgroud.
&& (mainWindow.mAttrs.flags & FLAG_SHOW_WALLPAPER) == 0;
}
+ @VisibleForTesting
+ boolean isSurfaceReadyAndVisible(WindowState mainWindow) {
+ boolean surfaceReady = mainWindow.isDrawn() // Regular case
+ // Waiting for relayoutWindow to call preserveSurface
+ || mainWindow.isDragResizeChanged();
+ return surfaceReady && (mActivityRecord.isVisible()
+ || mActivityRecord.isVisibleRequested());
+ }
+
private Color getLetterboxBackgroundColor() {
final WindowState w = mActivityRecord.findMainWindow();
if (w == null || w.isLetterboxedForDisplayCutout()) {
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/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 2b40b75..5362771 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -156,6 +156,7 @@
@VisibleForTesting
boolean mShouldAttachNavBarToAppDuringTransition;
private boolean mNavigationBarAttachedToApp;
+ private ActivityRecord mNavBarAttachedApp;
/**
* Animates the screenshot of task that used to be controlled by RecentsAnimation.
@@ -392,6 +393,18 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public void animateNavigationBarToApp(long duration) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mService.getWindowManagerLock()) {
+ animateNavigationBarForAppLaunch(duration);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
};
/**
@@ -613,7 +626,6 @@
|| mDisplayContent.getFadeRotationAnimationController() != null) {
return;
}
- ActivityRecord topActivity = null;
boolean shouldTranslateNavBar = false;
final boolean isDisplayLandscape =
mDisplayContent.getConfiguration().orientation == ORIENTATION_LANDSCAPE;
@@ -630,12 +642,12 @@
continue;
}
shouldTranslateNavBar = isSplitScreenSecondary;
- topActivity = task.getTopVisibleActivity();
+ mNavBarAttachedApp = task.getTopVisibleActivity();
break;
}
final WindowState navWindow = getNavigationBarWindow();
- if (topActivity == null || navWindow == null || navWindow.mToken == null) {
+ if (mNavBarAttachedApp == null || navWindow == null || navWindow.mToken == null) {
return;
}
mNavigationBarAttachedToApp = true;
@@ -643,9 +655,9 @@
final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
if (shouldTranslateNavBar) {
- navWindow.setSurfaceTranslationY(-topActivity.getBounds().top);
+ navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top);
}
- t.reparent(navSurfaceControl, topActivity.getSurfaceControl());
+ t.reparent(navSurfaceControl, mNavBarAttachedApp.getSurfaceControl());
t.show(navSurfaceControl);
final WindowContainer imeContainer = mDisplayContent.getImeContainer();
@@ -695,9 +707,25 @@
}
}
+ void animateNavigationBarForAppLaunch(long duration) {
+ if (!mShouldAttachNavBarToAppDuringTransition
+ // Skip the case where the nav bar is controlled by fade rotation.
+ || mDisplayContent.getFadeRotationAnimationController() != null
+ || mNavigationBarAttachedToApp
+ || mNavBarAttachedApp == null) {
+ return;
+ }
+
+ final NavBarFadeAnimationController controller =
+ new NavBarFadeAnimationController(mDisplayContent);
+ controller.fadeOutAndInSequentially(duration, null /* fadeOutParent */,
+ mNavBarAttachedApp.getSurfaceControl());
+ }
+
void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) {
if (mRunner != null) {
mIsAddingTaskToTargets = task != null;
+ mNavBarAttachedApp = task == null ? null : task.getTopVisibleActivity();
// No need to send task appeared when the task target already exists, or when the
// task is being managed as a multi-window mode outside of recents (e.g. bubbles).
if (isAnimatingTask(task) || skipAnimation(task)) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 82d1c04..7af8d8b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -7653,17 +7653,23 @@
void clearLastRecentsAnimationTransaction() {
mLastRecentsAnimationTransaction = null;
mLastRecentsAnimationOverlay = null;
- // reset also the transform introduced by mLastRecentsAnimationTransaction
- getPendingTransaction().setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9]);
+ // reset also the crop and transform introduced by mLastRecentsAnimationTransaction
+ Rect bounds = getBounds();
+ getPendingTransaction().setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9])
+ .setWindowCrop(mSurfaceControl, bounds.width(), bounds.height());
}
void maybeApplyLastRecentsAnimationTransaction() {
if (mLastRecentsAnimationTransaction != null) {
+ final SurfaceControl.Transaction tx = getPendingTransaction();
if (mLastRecentsAnimationOverlay != null) {
- getPendingTransaction().reparent(mLastRecentsAnimationOverlay, mSurfaceControl);
+ tx.reparent(mLastRecentsAnimationOverlay, mSurfaceControl);
}
PictureInPictureSurfaceTransaction.apply(mLastRecentsAnimationTransaction,
- mSurfaceControl, getPendingTransaction());
+ mSurfaceControl, tx);
+ // If we are transferring the transform from the root task entering PIP, then also show
+ // the new task immediately
+ tx.show(mSurfaceControl);
mLastRecentsAnimationTransaction = null;
mLastRecentsAnimationOverlay = null;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 642ce1b..299cde8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3928,7 +3928,9 @@
void notifyInsetsChanged() {
ProtoLog.d(WM_DEBUG_IME, "notifyInsetsChanged for %s ", this);
try {
- mClient.insetsChanged(getCompatInsetsState());
+ mClient.insetsChanged(getCompatInsetsState(),
+ hasMoved(),
+ mWindowFrames.isFrameSizeChangeReported());
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver inset state change w=" + this, e);
}
@@ -3944,7 +3946,9 @@
getDisplayContent().getInsetsStateController();
try {
mClient.insetsControlChanged(getCompatInsetsState(),
- stateController.getControlsForDispatch(this));
+ stateController.getControlsForDispatch(this),
+ hasMoved(),
+ mWindowFrames.isFrameSizeChangeReported());
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver inset state change to w=" + this, e);
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index b754644..e13ef99 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1987,43 +1987,39 @@
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
jobject jLights = env->NewObject(gArrayListClassInfo.clazz, gArrayListClassInfo.constructor);
- std::vector<int> lightIds = im->getInputManager()->getReader()->getLightIds(deviceId);
+ std::vector<InputDeviceLightInfo> lights =
+ im->getInputManager()->getReader()->getLights(deviceId);
- for (size_t i = 0; i < lightIds.size(); i++) {
- const InputDeviceLightInfo* lightInfo =
- im->getInputManager()->getReader()->getLightInfo(deviceId, lightIds[i]);
- if (lightInfo == nullptr) {
- ALOGW("Failed to get input device %d light info for id %d", deviceId, lightIds[i]);
- continue;
- }
+ for (size_t i = 0; i < lights.size(); i++) {
+ const InputDeviceLightInfo& lightInfo = lights[i];
jint jTypeId =
env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeInput);
jint jCapability = 0;
- if (lightInfo->type == InputDeviceLightType::MONO) {
+ if (lightInfo.type == InputDeviceLightType::MONO) {
jCapability = env->GetStaticIntField(gLightClassInfo.clazz,
gLightClassInfo.lightCapabilityBrightness);
- } else if (lightInfo->type == InputDeviceLightType::RGB ||
- lightInfo->type == InputDeviceLightType::MULTI_COLOR) {
+ } else if (lightInfo.type == InputDeviceLightType::RGB ||
+ lightInfo.type == InputDeviceLightType::MULTI_COLOR) {
jCapability =
env->GetStaticIntField(gLightClassInfo.clazz,
gLightClassInfo.lightCapabilityBrightness) |
env->GetStaticIntField(gLightClassInfo.clazz,
gLightClassInfo.lightCapabilityRgb);
- } else if (lightInfo->type == InputDeviceLightType::PLAYER_ID) {
+ } else if (lightInfo.type == InputDeviceLightType::PLAYER_ID) {
jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
gLightClassInfo.lightTypePlayerId);
} else {
- ALOGW("Unknown light type %d", lightInfo->type);
+ ALOGW("Unknown light type %d", lightInfo.type);
continue;
}
ScopedLocalRef<jobject> lightObj(env,
env->NewObject(gLightClassInfo.clazz,
gLightClassInfo.constructor,
- static_cast<jint>(lightInfo->id),
- env->NewStringUTF(lightInfo->name.c_str()),
- static_cast<jint>(lightInfo->ordinal),
+ static_cast<jint>(lightInfo.id),
+ env->NewStringUTF(lightInfo.name.c_str()),
+ static_cast<jint>(lightInfo.ordinal),
jTypeId, jCapability));
// Add light object to list
env->CallBooleanMethod(jLights, gArrayListClassInfo.add, lightObj.get());
@@ -2218,39 +2214,28 @@
static jobjectArray nativeGetSensorList(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
- std::vector<InputDeviceInfo> devices = im->getInputManager()->getReader()->getInputDevices();
- // Find the input device by deviceId
- auto it = std::find_if(devices.begin(), devices.end(),
- [deviceId](InputDeviceInfo& info) { return info.getId() == deviceId; });
+ std::vector<InputDeviceSensorInfo> sensors =
+ im->getInputManager()->getReader()->getSensors(deviceId);
- if (it == devices.end()) {
- // Return an array of size 0
- return env->NewObjectArray(0, gInputSensorInfo.clazz, nullptr);
- }
+ jobjectArray arr = env->NewObjectArray(sensors.size(), gInputSensorInfo.clazz, nullptr);
+ for (int i = 0; i < sensors.size(); i++) {
+ const InputDeviceSensorInfo& sensorInfo = sensors[i];
- std::vector<InputDeviceSensorType> types = it->getSensorTypes();
- jobjectArray arr = env->NewObjectArray(types.size(), gInputSensorInfo.clazz, nullptr);
- for (int i = 0; i < types.size(); i++) {
- const InputDeviceSensorInfo* sensorInfo = it->getSensorInfo(types[i]);
- if (sensorInfo == nullptr) {
- ALOGW("Failed to get input device %d sensor info for type %s", deviceId,
- NamedEnum::string(types[i]).c_str());
- continue;
- }
-
- jobject info =
- createInputSensorInfo(env, env->NewStringUTF(sensorInfo->name.c_str()),
- env->NewStringUTF(sensorInfo->vendor.c_str()),
- (jint)sensorInfo->version, 0 /* handle */,
- (jint)sensorInfo->type, (jfloat)sensorInfo->maxRange,
- (jfloat)sensorInfo->resolution, (jfloat)sensorInfo->power,
- (jfloat)sensorInfo->minDelay,
- (jint)sensorInfo->fifoReservedEventCount,
- (jint)sensorInfo->fifoMaxEventCount,
- env->NewStringUTF(sensorInfo->stringType.c_str()),
- env->NewStringUTF("") /* requiredPermission */,
- (jint)sensorInfo->maxDelay, (jint)sensorInfo->flags,
- (jint)sensorInfo->id);
+ jobject info = createInputSensorInfo(env, env->NewStringUTF(sensorInfo.name.c_str()),
+ env->NewStringUTF(sensorInfo.vendor.c_str()),
+ static_cast<jint>(sensorInfo.version), 0 /* handle */,
+ static_cast<jint>(sensorInfo.type),
+ static_cast<jfloat>(sensorInfo.maxRange),
+ static_cast<jfloat>(sensorInfo.resolution),
+ static_cast<jfloat>(sensorInfo.power),
+ static_cast<jfloat>(sensorInfo.minDelay),
+ static_cast<jint>(sensorInfo.fifoReservedEventCount),
+ static_cast<jint>(sensorInfo.fifoMaxEventCount),
+ env->NewStringUTF(sensorInfo.stringType.c_str()),
+ env->NewStringUTF("") /* requiredPermission */,
+ static_cast<jint>(sensorInfo.maxDelay),
+ static_cast<jint>(sensorInfo.flags),
+ static_cast<jint>(sensorInfo.id));
env->SetObjectArrayElement(arr, i, info);
env->DeleteLocalRef(info);
}
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..1d27655 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -22,6 +22,7 @@
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
@@ -990,13 +991,24 @@
@Override
public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
Bundle prevRestrictions) {
- final boolean newlyDisallowed =
- newRestrictions.getBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
- final boolean previouslyDisallowed =
- prevRestrictions.getBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
- final boolean restrictionChanged = (newlyDisallowed != previouslyDisallowed);
+ resetCrossProfileIntentFiltersIfNeeded(userId, newRestrictions, prevRestrictions);
+ resetUserVpnIfNeeded(userId, newRestrictions, prevRestrictions);
+ }
- if (restrictionChanged) {
+ private void resetUserVpnIfNeeded(
+ int userId, Bundle newRestrictions, Bundle prevRestrictions) {
+ final boolean newlyEnforced =
+ !prevRestrictions.getBoolean(UserManager.DISALLOW_CONFIG_VPN)
+ && newRestrictions.getBoolean(UserManager.DISALLOW_CONFIG_VPN);
+ if (newlyEnforced) {
+ mDpms.clearUserConfiguredVpns(userId);
+ }
+ }
+
+ private void resetCrossProfileIntentFiltersIfNeeded(
+ int userId, Bundle newRestrictions, Bundle prevRestrictions) {
+ if (UserRestrictionsUtils.restrictionsChanged(prevRestrictions, newRestrictions,
+ UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE)) {
final int parentId = mUserManagerInternal.getProfileParentId(userId);
if (parentId == userId) {
return;
@@ -1007,13 +1019,55 @@
Slogf.i(LOG_TAG, "Resetting cross-profile intent filters on restriction "
+ "change");
mDpms.resetDefaultCrossProfileIntentFilters(parentId);
- mContext.sendBroadcastAsUser(new Intent(
- DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED),
+ mContext.sendBroadcastAsUser(
+ new Intent(DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED),
UserHandle.of(userId));
}
}
}
+ private void clearUserConfiguredVpns(int userId) {
+ final String adminConfiguredVpnPkg;
+ synchronized (getLockObject()) {
+ final ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
+ if (owner == null) {
+ Slogf.wtf(LOG_TAG, "Admin not found");
+ return;
+ }
+ adminConfiguredVpnPkg = owner.mAlwaysOnVpnPackage;
+ }
+
+ // Clear always-on configuration if it wasn't set by the admin.
+ if (adminConfiguredVpnPkg == null) {
+ mInjector.getVpnManager().setAlwaysOnVpnPackageForUser(userId, null, false, null);
+ }
+
+ // Clear app authorizations to establish VPNs. When DISALLOW_CONFIG_VPN is enforced apps
+ // won't be able to get those authorizations unless it is configured by an admin.
+ final List<AppOpsManager.PackageOps> allVpnOps = mInjector.getAppOpsManager()
+ .getPackagesForOps(new int[] {AppOpsManager.OP_ACTIVATE_VPN});
+ if (allVpnOps == null) {
+ return;
+ }
+ for (AppOpsManager.PackageOps pkgOps : allVpnOps) {
+ if (UserHandle.getUserId(pkgOps.getUid()) != userId
+ || pkgOps.getPackageName().equals(adminConfiguredVpnPkg)) {
+ continue;
+ }
+ if (pkgOps.getOps().size() != 1) {
+ Slogf.wtf(LOG_TAG, "Unexpected number of ops returned");
+ continue;
+ }
+ final @Mode int mode = pkgOps.getOps().get(0).getMode();
+ if (mode == MODE_ALLOWED) {
+ Slogf.i(LOG_TAG, String.format("Revoking VPN authorization for package %s uid %d",
+ pkgOps.getPackageName(), pkgOps.getUid()));
+ mInjector.getAppOpsManager().setMode(AppOpsManager.OP_ACTIVATE_VPN, pkgOps.getUid(),
+ pkgOps.getPackageName(), MODE_DEFAULT);
+ }
+ }
+ }
+
private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
@Override
@@ -6559,6 +6613,19 @@
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE);
+ if (vpnPackage == null) {
+ final String prevVpnPackage;
+ synchronized (getLockObject()) {
+ prevVpnPackage = getProfileOwnerOrDeviceOwnerLocked(caller).mAlwaysOnVpnPackage;
+ // If the admin is clearing VPN package but hasn't configure any VPN previously,
+ // ignore it so that it doesn't interfere with user-configured VPNs.
+ if (TextUtils.isEmpty(prevVpnPackage)) {
+ return true;
+ }
+ }
+ revokeVpnAuthorizationForPackage(prevVpnPackage, caller.getUserId());
+ }
+
final int userId = caller.getUserId();
mInjector.binderWithCleanCallingIdentity(() -> {
if (vpnPackage != null && !isPackageInstalledForUser(vpnPackage, userId)) {
@@ -6581,14 +6648,14 @@
userId, vpnPackage, lockdown, lockdownAllowlist)) {
throw new UnsupportedOperationException();
}
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.SET_ALWAYS_ON_VPN_PACKAGE)
- .setAdmin(caller.getComponentName())
- .setStrings(vpnPackage)
- .setBoolean(lockdown)
- .setInt(lockdownAllowlist != null ? lockdownAllowlist.size() : 0)
- .write();
});
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_ALWAYS_ON_VPN_PACKAGE)
+ .setAdmin(caller.getComponentName())
+ .setStrings(vpnPackage)
+ .setBoolean(lockdown)
+ .setInt(lockdownAllowlist != null ? lockdownAllowlist.size() : 0)
+ .write();
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
if (!TextUtils.equals(vpnPackage, admin.mAlwaysOnVpnPackage)
@@ -6601,6 +6668,23 @@
return true;
}
+ private void revokeVpnAuthorizationForPackage(String vpnPackage, int userId) {
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ try {
+ final ApplicationInfo ai = mIPackageManager.getApplicationInfo(
+ vpnPackage, /* flags= */ 0, userId);
+ if (ai == null) {
+ Slogf.w(LOG_TAG, "Non-existent VPN package: " + vpnPackage);
+ } else {
+ mInjector.getAppOpsManager().setMode(AppOpsManager.OP_ACTIVATE_VPN,
+ ai.uid, vpnPackage, MODE_DEFAULT);
+ }
+ } catch (RemoteException e) {
+ Slogf.e(LOG_TAG, "Can't talk to package managed", e);
+ }
+ });
+ }
+
@Override
public String getAlwaysOnVpnPackage(ComponentName admin) throws SecurityException {
Objects.requireNonNull(admin, "ComponentName is null");
@@ -8390,12 +8474,6 @@
return who != null && who.equals(profileOwner);
}
- private boolean isProfileOwnerUncheckedLocked(ComponentName who, int userId) {
- ensureLocked();
- final ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
- return who != null && who.equals(profileOwner);
- }
-
/**
* Returns {@code true} if the provided caller identity is of a profile owner.
* @param caller identity of caller.
@@ -10005,14 +10083,23 @@
return true;
}
- private AccessibilityManager getAccessibilityManagerForUser(int userId) {
+ /**
+ * Invoke a method in AccessibilityManager ensuring the client is removed.
+ */
+ private <T> T withAccessibilityManager(
+ int userId, Function<AccessibilityManager, T> function) {
// Not using AccessibilityManager.getInstance because that guesses
// at the user you require based on callingUid and caches for a given
// process.
- IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
- IAccessibilityManager service = iBinder == null
+ final IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+ final IAccessibilityManager service = iBinder == null
? null : IAccessibilityManager.Stub.asInterface(iBinder);
- return new AccessibilityManager(mContext, service, userId);
+ final AccessibilityManager am = new AccessibilityManager(mContext, service, userId);
+ try {
+ return function.apply(am);
+ } finally {
+ am.removeClient();
+ }
}
@Override
@@ -10025,22 +10112,21 @@
if (packageList != null) {
int userId = caller.getUserId();
- List<AccessibilityServiceInfo> enabledServices = null;
+ final List<AccessibilityServiceInfo> enabledServices;
long id = mInjector.binderClearCallingIdentity();
try {
UserInfo user = getUserInfo(userId);
if (user.isManagedProfile()) {
userId = user.profileGroupId;
}
- AccessibilityManager accessibilityManager = getAccessibilityManagerForUser(userId);
- enabledServices = accessibilityManager.getEnabledAccessibilityServiceList(
- FEEDBACK_ALL_MASK);
+ enabledServices = withAccessibilityManager(userId,
+ am -> am.getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK));
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
if (enabledServices != null) {
- List<String> enabledPackages = new ArrayList<String>();
+ List<String> enabledPackages = new ArrayList<>();
for (AccessibilityServiceInfo service : enabledServices) {
enabledPackages.add(service.getResolveInfo().serviceInfo.packageName);
}
@@ -10122,10 +10208,9 @@
if (user.isManagedProfile()) {
userId = user.profileGroupId;
}
- AccessibilityManager accessibilityManager =
- getAccessibilityManagerForUser(userId);
- List<AccessibilityServiceInfo> installedServices =
- accessibilityManager.getInstalledAccessibilityServiceList();
+ final List<AccessibilityServiceInfo> installedServices =
+ withAccessibilityManager(userId,
+ AccessibilityManager::getInstalledAccessibilityServiceList);
if (installedServices != null) {
for (AccessibilityServiceInfo service : installedServices) {
@@ -13660,16 +13745,6 @@
+ " is not device owner");
}
- private ComponentName getOwnerComponent(String packageName, int userId) {
- if (isDeviceOwnerPackage(packageName, userId)) {
- return mOwners.getDeviceOwnerComponent();
- }
- if (isProfileOwnerPackage(packageName, userId)) {
- return mOwners.getProfileOwnerComponent(userId);
- }
- return null;
- }
-
/**
* Return device owner or profile owner set on a given user.
*/
@@ -16016,9 +16091,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);
@@ -17462,14 +17537,9 @@
@Override
public boolean isUsbDataSignalingEnabled(String packageName) {
- final CallerIdentity caller = getCallerIdentity(packageName);
- Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
- "USB data signaling can only be controlled by a device owner or "
- + "a profile owner on an organization-owned device.");
-
synchronized (getLockObject()) {
- final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+ final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(
+ getCallerIdentity(packageName));
return admin.mUsbDataSignalingEnabled;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index 48d2d73..532823a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -18,8 +18,6 @@
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
-import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
-
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -142,9 +140,21 @@
}
private List<String> getAccessibilityServices() {
- final List<AccessibilityServiceInfo> accessibilityServiceInfos =
- getAccessibilityManagerForUser(mContext.getUserId())
- .getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
+ final List<AccessibilityServiceInfo> accessibilityServiceInfos;
+ // Not using AccessibilityManager.getInstance because that guesses
+ // at the user you require based on callingUid and caches for a given
+ // process.
+ final IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+ final IAccessibilityManager service = iBinder == null
+ ? null : IAccessibilityManager.Stub.asInterface(iBinder);
+ final AccessibilityManager am =
+ new AccessibilityManager(mContext, service, mContext.getUserId());
+ try {
+ accessibilityServiceInfos = am.getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
+ } finally {
+ am.removeClient();
+ }
+
final List<String> result = new ArrayList<>();
for (final AccessibilityServiceInfo serviceInfo : accessibilityServiceInfos) {
final ComponentName componentName =
@@ -192,12 +202,6 @@
return resolveInfos != null && !resolveInfos.isEmpty();
}
- private AccessibilityManager getAccessibilityManagerForUser(int userId) {
- final IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
- final IAccessibilityManager service =
- iBinder == null ? null : IAccessibilityManager.Stub.asInterface(iBinder);
- return new AccessibilityManager(mContext, service, userId);
- }
void dump(IndentingPrintWriter pw) {
pw.println("PersonalAppsSuspensionHelper");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
index 7148ed4..ce9fa2d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
@@ -68,7 +68,7 @@
notifyCallbackOnError(
InstallSystemUpdateCallback.UPDATE_ERROR_BATTERY_LOW,
"The battery level must be above "
- + mConstants.BATTERY_THRESHOLD_NOT_CHARGING + " while not charging or"
+ + mConstants.BATTERY_THRESHOLD_NOT_CHARGING + " while not charging or "
+ "above " + mConstants.BATTERY_THRESHOLD_CHARGING + " while charging");
return;
}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 47d59b2..757c9de 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -89,6 +89,11 @@
// Max interval after system invoked the DL when readlog collection can be enabled.
static constexpr auto readLogsMaxInterval = 2h;
+
+ // How long should we wait till dataLoader reports destroyed.
+ static constexpr auto destroyTimeout = 60s;
+
+ static constexpr auto anyStatus = INT_MIN;
};
static const Constants& constants() {
@@ -2554,7 +2559,7 @@
mControl = {};
mHealthControl = {};
mHealthListener = {};
- mStatusCondition.wait_until(lock, now + 60s, [this] {
+ mStatusCondition.wait_until(lock, now + Constants::destroyTimeout, [this] {
return mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
});
mStatusListener = {};
@@ -2754,8 +2759,16 @@
switch (targetStatus) {
case IDataLoaderStatusListener::DATA_LOADER_DESTROYED: {
switch (currentStatus) {
+ case IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE:
+ case IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE:
+ destroy();
+ // DataLoader is broken, just assume it's destroyed.
+ compareAndSetCurrentStatus(currentStatus,
+ IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+ return true;
case IDataLoaderStatusListener::DATA_LOADER_BINDING:
- setCurrentStatus(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+ compareAndSetCurrentStatus(currentStatus,
+ IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
return true;
default:
return destroy();
@@ -2776,7 +2789,11 @@
case IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE:
// Before binding need to make sure we are unbound.
// Otherwise we'll get stuck binding.
- return destroy();
+ destroy();
+ // DataLoader is broken, just assume it's destroyed.
+ compareAndSetCurrentStatus(currentStatus,
+ IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+ return true;
case IDataLoaderStatusListener::DATA_LOADER_DESTROYED:
case IDataLoaderStatusListener::DATA_LOADER_BINDING:
return bind();
@@ -2815,6 +2832,11 @@
}
void IncrementalService::DataLoaderStub::setCurrentStatus(int newStatus) {
+ compareAndSetCurrentStatus(Constants::anyStatus, newStatus);
+}
+
+void IncrementalService::DataLoaderStub::compareAndSetCurrentStatus(int expectedStatus,
+ int newStatus) {
int oldStatus, oldTargetStatus, newTargetStatus;
DataLoaderStatusListener listener;
{
@@ -2822,6 +2844,9 @@
if (mCurrentStatus == newStatus) {
return;
}
+ if (expectedStatus != Constants::anyStatus && expectedStatus != mCurrentStatus) {
+ return;
+ }
oldStatus = mCurrentStatus;
oldTargetStatus = mTargetStatus;
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 5de7325..b81e1b1 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -255,6 +255,7 @@
binder::Status onStatusChanged(MountId mount, int newStatus) final;
void setCurrentStatus(int newStatus);
+ void compareAndSetCurrentStatus(int expectedStatus, int newStatus);
sp<content::pm::IDataLoader> getDataLoader();
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 094c5fb..59d96d2 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -808,7 +808,9 @@
METRICS_MILLIS_SINCE_OLDEST_PENDING_READ()
.c_str()),
&millisSinceOldestPendingRead));
- ASSERT_EQ(expectedMillisSinceOldestPendingRead, millisSinceOldestPendingRead);
+ // Allow 10ms.
+ ASSERT_LE(expectedMillisSinceOldestPendingRead, millisSinceOldestPendingRead);
+ ASSERT_GE(expectedMillisSinceOldestPendingRead + 10, millisSinceOldestPendingRead);
int storageHealthStatusCode = -1;
ASSERT_TRUE(
result.getInt(String16(BnIncrementalService::METRICS_STORAGE_HEALTH_STATUS_CODE()
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index d55d060..ef1201e 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -254,6 +254,18 @@
return null;
}
+ ConversationInfo getConversationInfo(String packageName, int userId, String shortcutId) {
+ UserData userData = getUnlockedUserData(userId);
+ if (userData != null) {
+ PackageData packageData = userData.getPackageData(packageName);
+ // App may have been uninstalled.
+ if (packageData != null) {
+ return packageData.getConversationInfo(shortcutId);
+ }
+ }
+ return null;
+ }
+
@Nullable
private ConversationChannel getConversationChannel(String packageName, int userId,
String shortcutId, ConversationInfo conversationInfo) {
@@ -277,7 +289,7 @@
int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
NotificationChannel parentChannel =
mNotificationManagerInternal.getNotificationChannel(packageName, uid,
- conversationInfo.getParentNotificationChannelId());
+ conversationInfo.getNotificationChannelId());
NotificationChannelGroup parentChannelGroup = null;
if (parentChannel != null) {
parentChannelGroup =
@@ -302,7 +314,7 @@
String shortcutId = conversationInfo.getShortcutId();
ConversationChannel channel = getConversationChannel(packageData.getPackageName(),
packageData.getUserId(), shortcutId, conversationInfo);
- if (channel == null || channel.getParentNotificationChannel() == null) {
+ if (channel == null || channel.getNotificationChannel() == null) {
return;
}
conversationChannels.add(channel);
@@ -791,8 +803,8 @@
private boolean isCachedRecentConversation(ConversationInfo conversationInfo) {
return conversationInfo.isShortcutCachedForNotification()
- && conversationInfo.getNotificationChannelId() == null
- && conversationInfo.getParentNotificationChannelId() != null
+ && Objects.equals(conversationInfo.getNotificationChannelId(),
+ conversationInfo.getParentNotificationChannelId())
&& conversationInfo.getLastEventTimestamp() > 0L;
}
@@ -910,7 +922,7 @@
}
@VisibleForTesting
- NotificationListenerService getNotificationListenerServiceForTesting(@UserIdInt int userId) {
+ NotificationListener getNotificationListenerServiceForTesting(@UserIdInt int userId) {
return mNotificationListeners.get(userId);
}
@@ -1132,7 +1144,7 @@
}
@Override
- public void onNotificationPosted(StatusBarNotification sbn) {
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap map) {
if (sbn.getUser().getIdentifier() != mUserId) {
return;
}
@@ -1145,16 +1157,22 @@
});
if (packageData != null) {
+ Ranking rank = new Ranking();
+ map.getRanking(sbn.getKey(), rank);
ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
if (conversationInfo == null) {
return;
}
if (DEBUG) Log.d(TAG, "Last event from notification: " + sbn.getPostTime());
- ConversationInfo updated = new ConversationInfo.Builder(conversationInfo)
+ ConversationInfo.Builder updated = new ConversationInfo.Builder(conversationInfo)
.setLastEventTimestamp(sbn.getPostTime())
- .setParentNotificationChannelId(sbn.getNotification().getChannelId())
- .build();
- packageData.getConversationStore().addOrUpdate(updated);
+ .setNotificationChannelId(rank.getChannel().getId());
+ if (!TextUtils.isEmpty(rank.getChannel().getParentChannelId())) {
+ updated.setParentNotificationChannelId(rank.getChannel().getParentChannelId());
+ } else {
+ updated.setParentNotificationChannelId(sbn.getNotification().getChannelId());
+ }
+ packageData.getConversationStore().addOrUpdate(updated.build());
EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 6c5c1d4..b4f8b6e 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -16,6 +16,7 @@
package com.android.server.pm.test.override
+import android.app.PropertyInvalidatedCache
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
@@ -26,8 +27,10 @@
import com.android.server.pm.AppsFilter
import com.android.server.pm.ComponentResolver
import com.android.server.pm.PackageManagerService
+import com.android.server.pm.PackageManagerTracedLock
import com.android.server.pm.PackageSetting
import com.android.server.pm.Settings
+import com.android.server.pm.UserManagerInternal
import com.android.server.pm.UserManagerService
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.parsing.pkg.PackageImpl
@@ -42,6 +45,7 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
+import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -63,6 +67,7 @@
private const val VALID_PKG = "com.android.server.pm.test.override"
private const val SHARED_PKG = "com.android.server.pm.test.override.shared"
private const val INVALID_PKG = "com.android.server.pm.test.override.invalid"
+ private const val NON_EXISTENT_PKG = "com.android.server.pm.test.override.nonexistent"
private const val SEND_PENDING_BROADCAST = 1 // PackageManagerService.SEND_PENDING_BROADCAST
@@ -94,7 +99,8 @@
// Start with an array of the simplest known inputs and expected outputs
Params(VALID_PKG, AppType.SYSTEM_APP, Result.Changed),
Params(SHARED_PKG, AppType.SYSTEM_APP, Result.Changed),
- Params(INVALID_PKG, AppType.SYSTEM_APP, SecurityException::class.java)
+ Params(INVALID_PKG, AppType.SYSTEM_APP, SecurityException::class.java),
+ Params(NON_EXISTENT_PKG, AppType.SYSTEM_APP, SecurityException::class.java)
)
.flatMap { param ->
mutableListOf(param).apply {
@@ -120,6 +126,13 @@
}
}
+ @BeforeClass
+ @JvmStatic
+ fun disablePropertyInvalidatedCache() {
+ // Disable binder caches in this process.
+ PropertyInvalidatedCache.disableForTestMode()
+ }
+
data class Params(
val pkgName: String,
private val appType: AppType,
@@ -210,8 +223,10 @@
fun verifyExpectedResult() {
if (params.componentName != null) {
val activityInfo = service.getActivityInfo(params.componentName, 0, userId)
- assertThat(activityInfo.nonLocalizedLabel).isEqualTo(params.expectedLabel)
- assertThat(activityInfo.icon).isEqualTo(params.expectedIcon)
+ if (activityInfo != null) {
+ assertThat(activityInfo.nonLocalizedLabel).isEqualTo(params.expectedLabel)
+ assertThat(activityInfo.icon).isEqualTo(params.expectedIcon)
+ }
}
}
@@ -294,34 +309,41 @@
SHARED_PKG to makePkg(SHARED_PKG) { uid = Binder.getCallingUid() },
INVALID_PKG to makePkg(INVALID_PKG) { uid = Binder.getCallingUid() + 1 }
)
- val mockedPkgSettings = mapOf(
+ val mockedPkgSettings = mutableMapOf(
VALID_PKG to makePkgSetting(VALID_PKG),
SHARED_PKG to makePkgSetting(SHARED_PKG),
INVALID_PKG to makePkgSetting(INVALID_PKG)
)
- // Add pkgSetting under test so its attributes override the defaults added above
- .plus(params.pkgName to mockPkgSetting)
- val mockActivity: ParsedActivity = mock {
- whenever(this.packageName) { params.pkgName }
- whenever(this.nonLocalizedLabel) { DEFAULT_LABEL }
- whenever(this.icon) { DEFAULT_ICON }
- whenever(this.componentName) { params.componentName }
- whenever(this.name) { params.componentName?.className }
- whenever(this.isEnabled) { true }
- whenever(this.isDirectBootAware) { params.isSystem }
+ var mockActivity: ParsedActivity? = null
+ if (mockedPkgSettings.containsKey(params.pkgName)) {
+ // Add pkgSetting under test so its attributes override the defaults added above
+ mockedPkgSettings.put(params.pkgName, mockPkgSetting)
+
+ mockActivity = mock<ParsedActivity> {
+ whenever(this.packageName) { params.pkgName }
+ whenever(this.nonLocalizedLabel) { DEFAULT_LABEL }
+ whenever(this.icon) { DEFAULT_ICON }
+ whenever(this.componentName) { params.componentName }
+ whenever(this.name) { params.componentName?.className }
+ whenever(this.isEnabled) { true }
+ whenever(this.isDirectBootAware) { params.isSystem }
+ }
}
val mockSettings = Settings(mockedPkgSettings)
val mockComponentResolver: ComponentResolver = mockThrowOnUnmocked {
params.componentName?.let {
- whenever(this.componentExists(same(it))) { true }
+ whenever(this.componentExists(same(it))) { mockActivity != null }
whenever(this.getActivity(same(it))) { mockActivity }
}
}
val mockUserManagerService: UserManagerService = mockThrowOnUnmocked {
val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent }
whenever(this.exists(intThat(matcher))) { true }
+ }
+ val mockUserManagerInternal: UserManagerInternal = mockThrowOnUnmocked {
+ val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent }
whenever(this.isUserUnlockingOrUnlocked(intThat(matcher))) { true }
}
val mockActivityTaskManager: ActivityTaskManagerInternal = mockThrowOnUnmocked {
@@ -340,18 +362,19 @@
}
}
val mockInjector: PackageManagerService.Injector = mock {
- whenever(this.lock) { Object() }
+ whenever(this.lock) { PackageManagerTracedLock() }
whenever(this.componentResolver) { mockComponentResolver }
whenever(this.userManagerService) { mockUserManagerService }
+ whenever(this.getUserManagerInternal()) { mockUserManagerInternal }
whenever(this.settings) { mockSettings }
whenever(this.getLocalService(ActivityTaskManagerInternal::class.java)) {
mockActivityTaskManager
}
whenever(this.appsFilter) { mockAppsFilter }
whenever(this.context) { mockContext }
+ whenever(this.getHandler()) { testHandler }
}
val testParams = PackageManagerService.TestParams().apply {
- this.handler = testHandler
this.pendingPackageBroadcasts = mockPendingBroadcasts
this.resolveComponentName = ComponentName("android", ".Test")
this.packages = ArrayMap<String, AndroidPackage>().apply { putAll(mockedPkgs) }
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 24e11af..ff7cd75 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -45,6 +45,7 @@
"service-permission.impl",
"service-blobstore",
"service-appsearch",
+ "androidx.test.core",
"androidx.test.runner",
"androidx.test.ext.truth",
"mockito-target-extended-minus-junit4",
diff --git a/services/tests/mockingservicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java b/services/tests/mockingservicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
new file mode 100644
index 0000000..da0b83e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.stats;
+
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.appsearch.AppSearchConfig;
+import com.android.server.appsearch.external.localstorage.stats.CallStats;
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Tests covering the functionalities in {@link PlatformLogger} requiring overriding some flags
+ * in {@link DeviceConfig}.
+ *
+ * <p>To add tests NOT rely on overriding the configs, please add them in
+ * the tests for {@link PlatformLogger} in servicetests.
+ */
+@RunWith(MockitoJUnitRunner.class)
+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 AppSearchConfig mAppSearchConfig;
+
+ @Rule
+ public final TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mAppSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+ }
+
+ @Test
+ public void testCreateExtraStatsLocked_samplingIntervalNotSet_returnsDefault() {
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.of(UserHandle.USER_NULL),
+ mAppSearchConfig);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ Long.toString(TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+ Integer.toString(TEST_DEFAULT_SAMPLING_INTERVAL),
+ false);
+
+ // Make sure default sampling interval is used if there is no config set.
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_UNKNOWN).mSamplingInterval).isEqualTo(
+ TEST_DEFAULT_SAMPLING_INTERVAL);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_INITIALIZE).mSamplingInterval).isEqualTo(
+ TEST_DEFAULT_SAMPLING_INTERVAL);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_SEARCH).mSamplingInterval).isEqualTo(
+ TEST_DEFAULT_SAMPLING_INTERVAL);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_FLUSH).mSamplingInterval).isEqualTo(
+ TEST_DEFAULT_SAMPLING_INTERVAL);
+ }
+
+
+ @Test
+ public void testCreateExtraStatsLocked_samplingIntervalSet_returnsConfigured() {
+ int putDocumentSamplingInterval = 1;
+ int batchCallSamplingInterval = 2;
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.of(UserHandle.USER_NULL), mAppSearchConfig);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ Long.toString(TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+ Integer.toString(TEST_DEFAULT_SAMPLING_INTERVAL),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+ Integer.toString(putDocumentSamplingInterval),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+ Integer.toString(batchCallSamplingInterval),
+ false);
+
+ // The default sampling interval should be used if no sampling interval is
+ // provided for certain call type.
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_INITIALIZE).mSamplingInterval).isEqualTo(
+ TEST_DEFAULT_SAMPLING_INTERVAL);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_FLUSH).mSamplingInterval).isEqualTo(
+ TEST_DEFAULT_SAMPLING_INTERVAL);
+
+ // The configured sampling interval is used if sampling interval is available
+ // for certain call type.
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSamplingInterval).isEqualTo(
+ putDocumentSamplingInterval);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_PUT_DOCUMENTS).mSamplingInterval).isEqualTo(
+ batchCallSamplingInterval);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH).mSamplingInterval).isEqualTo(
+ batchCallSamplingInterval);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_trueWhenSampleIntervalIsOne() {
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.of(UserHandle.USER_NULL),
+ mAppSearchConfig);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+ Long.toString(1),
+ false);
+
+ // Sample should always be logged for the first time if sampling is disabled(value is one).
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_falseWhenSampleIntervalIsNegative() {
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.of(UserHandle.USER_NULL),
+ mAppSearchConfig);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+ Long.toString(-1),
+ false);
+
+ // Makes sure sample will be excluded due to sampling if sample interval is negative.
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
+ // Skipped count should be 0 since it doesn't pass the sampling.
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_falseWhenWithinCoolOffInterval() {
+ // Next sample won't be excluded due to sampling.
+ final int samplingInterval = 1;
+ // Next sample would guaranteed to be too close.
+ final int minTimeIntervalBetweenSamplesMillis = Integer.MAX_VALUE;
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.of(UserHandle.USER_NULL),
+ mAppSearchConfig);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+ Long.toString(samplingInterval),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ Long.toString(minTimeIntervalBetweenSamplesMillis),
+ false);
+ logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
+
+ // Makes sure sample will be excluded due to rate limiting if samples are too close.
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(1);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_trueWhenOutsideOfCoolOffInterval() {
+ // Next sample won't be excluded due to sampling.
+ final int samplingInterval = 1;
+ // Next sample would guaranteed to be included.
+ final int minTimeIntervalBetweenSamplesMillis = 0;
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.of(UserHandle.USER_NULL),
+ mAppSearchConfig);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+ Long.toString(samplingInterval),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+ AppSearchConfig.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ Long.toString(minTimeIntervalBetweenSamplesMillis),
+ false);
+ logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
+
+ // Makes sure sample will be logged if it is not too close to previous sample.
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+ }
+}
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..7c275e13 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
@@ -16,119 +16,64 @@
package com.android.server.appsearch.stats;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.anyInt;
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;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
-import android.os.SystemClock;
import android.os.UserHandle;
-import android.util.SparseIntArray;
+import android.util.ArrayMap;
import androidx.test.core.app.ApplicationProvider;
-import com.android.server.appsearch.external.localstorage.MockPackageManager;
+import com.android.server.appsearch.AppSearchConfig;
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;
+/**
+ * Tests covering the functionalities in {@link PlatformLogger} NOT requiring overriding any flags
+ * in {@link android.provider.DeviceConfig}.
+ *
+ * <p>To add tests rely on overriding the flags, please add them in the
+ * tests for {@link PlatformLogger} in mockingservicestests.
+ */
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
- public void testCreateExtraStatsLocked_nullSamplingIntervalMap_returnsDefault() {
- PlatformLogger logger = new PlatformLogger(
- ApplicationProvider.getApplicationContext(),
- UserHandle.of(UserHandle.USER_NULL),
- new PlatformLogger.Config(
- TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
- TEST_DEFAULT_SAMPLING_INTERVAL,
- /*samplingIntervals=*/ new SparseIntArray()));
-
- // Make sure default sampling interval is used if samplingMap is not provided.
- assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
- CallStats.CALL_TYPE_UNKNOWN).mSamplingInterval).isEqualTo(
- TEST_DEFAULT_SAMPLING_INTERVAL);
- assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
- CallStats.CALL_TYPE_INITIALIZE).mSamplingInterval).isEqualTo(
- TEST_DEFAULT_SAMPLING_INTERVAL);
- assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
- CallStats.CALL_TYPE_SEARCH).mSamplingInterval).isEqualTo(
- TEST_DEFAULT_SAMPLING_INTERVAL);
- assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
- CallStats.CALL_TYPE_FLUSH).mSamplingInterval).isEqualTo(
- TEST_DEFAULT_SAMPLING_INTERVAL);
- }
-
-
- @Test
- public void testCreateExtraStatsLocked_with_samplingIntervalMap_returnsConfigured() {
- int putDocumentSamplingInterval = 1;
- int querySamplingInterval = 2;
- final SparseIntArray samplingIntervals = new SparseIntArray();
- samplingIntervals.put(CallStats.CALL_TYPE_PUT_DOCUMENT, putDocumentSamplingInterval);
- samplingIntervals.put(CallStats.CALL_TYPE_SEARCH, querySamplingInterval);
- PlatformLogger logger = new PlatformLogger(
- ApplicationProvider.getApplicationContext(),
- UserHandle.of(UserHandle.USER_NULL),
- new PlatformLogger.Config(
- TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
- TEST_DEFAULT_SAMPLING_INTERVAL,
- samplingIntervals));
-
- // The default sampling interval should be used if no sampling interval is
- // provided for certain call type.
- assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
- CallStats.CALL_TYPE_INITIALIZE).mSamplingInterval).isEqualTo(
- TEST_DEFAULT_SAMPLING_INTERVAL);
- assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
- CallStats.CALL_TYPE_FLUSH).mSamplingInterval).isEqualTo(
- TEST_DEFAULT_SAMPLING_INTERVAL);
-
- // The configured sampling interval is used if sampling interval is available
- // for certain call type.
- assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
- CallStats.CALL_TYPE_PUT_DOCUMENT).mSamplingInterval).isEqualTo(
- putDocumentSamplingInterval);
- assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
- CallStats.CALL_TYPE_SEARCH).mSamplingInterval).isEqualTo(
- querySamplingInterval);
+ }
+ };
}
@Test
@@ -201,87 +146,6 @@
assertThat(PlatformLogger.calculateHashCodeMd5(/*str=*/ null)).isEqualTo(-1);
}
- @Test
- public void testShouldLogForTypeLocked_trueWhenSampleIntervalIsOne() {
- final int samplingInterval = 1;
- final String testPackageName = "packageName";
- PlatformLogger logger = new PlatformLogger(
- ApplicationProvider.getApplicationContext(),
- UserHandle.of(UserHandle.USER_NULL),
- new PlatformLogger.Config(
- TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
- samplingInterval,
- /*samplingIntervals=*/ new SparseIntArray()));
-
- // Sample should always be logged for the first time if sampling is disabled(value is one).
- assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
- assertThat(logger.createExtraStatsLocked(testPackageName,
- CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
- }
-
- @Test
- public void testShouldLogForTypeLocked_falseWhenSampleIntervalIsNegative() {
- final int samplingInterval = -1;
- final String testPackageName = "packageName";
- PlatformLogger logger = new PlatformLogger(
- ApplicationProvider.getApplicationContext(),
- UserHandle.of(UserHandle.USER_NULL),
- new PlatformLogger.Config(
- TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
- samplingInterval,
- /*samplingIntervals=*/ new SparseIntArray()));
-
- // Makes sure sample will be excluded due to sampling if sample interval is negative.
- assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
- // Skipped count should be 0 since it doesn't pass the sampling.
- assertThat(logger.createExtraStatsLocked(testPackageName,
- CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
- }
-
- @Test
- public void testShouldLogForTypeLocked_falseWhenWithinCoolOffInterval() {
- // Next sample won't be excluded due to sampling.
- final int samplingInterval = 1;
- // Next sample would guaranteed to be too close.
- final int minTimeIntervalBetweenSamplesMillis = Integer.MAX_VALUE;
- final String testPackageName = "packageName";
- PlatformLogger logger = new PlatformLogger(
- ApplicationProvider.getApplicationContext(),
- UserHandle.of(UserHandle.USER_NULL),
- new PlatformLogger.Config(
- minTimeIntervalBetweenSamplesMillis,
- samplingInterval,
- /*samplingIntervals=*/ new SparseIntArray()));
- logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
-
- // Makes sure sample will be excluded due to rate limiting if samples are too close.
- assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
- assertThat(logger.createExtraStatsLocked(testPackageName,
- CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(1);
- }
-
- @Test
- public void testShouldLogForTypeLocked_trueWhenOutsideOfCoolOffInterval() {
- // Next sample won't be excluded due to sampling.
- final int samplingInterval = 1;
- // Next sample would guaranteed to be included.
- final int minTimeIntervalBetweenSamplesMillis = 0;
- final String testPackageName = "packageName";
- PlatformLogger logger = new PlatformLogger(
- ApplicationProvider.getApplicationContext(),
- UserHandle.of(UserHandle.USER_NULL),
- new PlatformLogger.Config(
- minTimeIntervalBetweenSamplesMillis,
- samplingInterval,
- /*samplingIntervals=*/ new SparseIntArray()));
- logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
-
- // Makes sure sample will be logged if it is not too close to previous sample.
- assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
- assertThat(logger.createExtraStatsLocked(testPackageName,
- CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
- }
-
/** Makes sure the caching works while getting the UID for calling package. */
@Test
public void testGetPackageUidAsUser() throws Exception {
@@ -290,43 +154,52 @@
PlatformLogger logger = new PlatformLogger(
mContext,
mContext.getUser(),
- new PlatformLogger.Config(
- TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
- TEST_DEFAULT_SAMPLING_INTERVAL,
- /*samplingIntervals=*/ new SparseIntArray()));
- mMockPackageManager.mockGetPackageUidAsUser(testPackageName, mContext.getUserId(), testUid);
+ AppSearchConfig.create(DIRECT_EXECUTOR));
+ 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/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 4afb7dd..2892bf5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -18,6 +18,7 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricPrompt.DISMISSED_REASON_NEGATIVE;
import static com.android.server.biometrics.BiometricServiceStateProto.*;
@@ -38,7 +39,6 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
-import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricAuthenticator;
@@ -67,6 +67,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
+import java.util.function.Consumer;
@Presubmit
@SmallTest
@@ -245,9 +246,6 @@
assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
assertEquals(BiometricSensor.STATE_COOKIE_RETURNED,
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
- session.onErrorReceived(fingerprintSensorId,
- session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getCookie(),
- BiometricConstants.BIOMETRIC_ERROR_VENDOR, 0 /* vendorCode */);
session.onStartFingerprint();
}
assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
@@ -258,6 +256,21 @@
@Test
public void testCancelAuthentication_whenStateAuthCalled_invokesCancel()
throws RemoteException {
+ testInvokesCancel(session -> session.onCancelAuthSession(false /* force */));
+ }
+
+ @Test
+ public void testCancelAuthentication_whenStateAuthForcedCalled_invokesCancel()
+ throws RemoteException {
+ testInvokesCancel(session -> session.onCancelAuthSession(true /* force */));
+ }
+
+ @Test
+ public void testCancelAuthentication_whenDialogDismissed() throws RemoteException {
+ testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
+ }
+
+ private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException {
final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
setupFace(0 /* id */, false /* confirmationAlwaysRequired */, faceAuthenticator);
@@ -269,7 +282,8 @@
session.goToInitialState();
assertEquals(STATE_AUTH_CALLED, session.getState());
- session.onCancelAuthSession(false /* force */);
+
+ sessionConsumer.accept(session);
verify(faceAuthenticator).cancelAuthenticationFromService(eq(mToken), eq(TEST_PACKAGE));
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index ae836ce..7c7afb7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -1036,7 +1036,7 @@
}
@Test
- public void testDismissedReasonNegative_whilePaused_doesntInvokeHalCancel() throws Exception {
+ public void testDismissedReasonNegative_whilePaused_invokeHalCancel() throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -1050,14 +1050,12 @@
BiometricPrompt.DISMISSED_REASON_NEGATIVE, null /* credentialAttestation */);
waitForIdle();
- verify(mBiometricService.mSensors.get(0).impl,
- never()).cancelAuthenticationFromService(
- any(),
- any());
+ verify(mBiometricService.mSensors.get(0).impl)
+ .cancelAuthenticationFromService(any(), any());
}
@Test
- public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws
+ public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws
Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
@@ -1072,10 +1070,8 @@
BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
waitForIdle();
- verify(mBiometricService.mSensors.get(0).impl,
- never()).cancelAuthenticationFromService(
- any(),
- any());
+ verify(mBiometricService.mSensors.get(0).impl)
+ .cancelAuthenticationFromService(any(), any());
}
@Test
@@ -1091,11 +1087,8 @@
BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */);
waitForIdle();
- // doesn't send cancel to HAL
- verify(mBiometricService.mSensors.get(0).impl,
- never()).cancelAuthenticationFromService(
- any(),
- any());
+ verify(mBiometricService.mSensors.get(0).impl)
+ .cancelAuthenticationFromService(any(), any());
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 654d9fc..fd41b9c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -15,6 +15,9 @@
*/
package com.android.server.devicepolicy;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
import static android.app.Notification.EXTRA_TEXT;
import static android.app.Notification.EXTRA_TITLE;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
@@ -7436,6 +7439,101 @@
assertThrows(SecurityException.class, () -> dpm.setRecommendedGlobalProxy(admin1, null));
}
+ @Test
+ public void testSetAlwaysOnVpnPackage_clearsAdminVpn() throws Exception {
+ setDeviceOwner();
+
+ when(getServices().vpnManager
+ .setAlwaysOnVpnPackageForUser(anyInt(), any(), anyBoolean(), any()))
+ .thenReturn(true);
+
+ // Set VPN package to admin package.
+ dpm.setAlwaysOnVpnPackage(admin1, admin1.getPackageName(), false, null);
+
+ verify(getServices().vpnManager).setAlwaysOnVpnPackageForUser(
+ UserHandle.USER_SYSTEM, admin1.getPackageName(), false, null);
+
+ // Clear VPN package.
+ dpm.setAlwaysOnVpnPackage(admin1, null, false, null);
+
+ // Change should be propagated to VpnManager
+ verify(getServices().vpnManager).setAlwaysOnVpnPackageForUser(
+ UserHandle.USER_SYSTEM, null, false, null);
+ // The package should lose authorization to start VPN.
+ verify(getServices().appOpsManager).setMode(OP_ACTIVATE_VPN,
+ DpmMockContext.CALLER_SYSTEM_USER_UID, admin1.getPackageName(), MODE_DEFAULT);
+ }
+
+ @Test
+ public void testSetAlwaysOnVpnPackage_doesntKillUserVpn() throws Exception {
+ setDeviceOwner();
+
+ when(getServices().vpnManager
+ .setAlwaysOnVpnPackageForUser(anyInt(), any(), anyBoolean(), any()))
+ .thenReturn(true);
+
+ // this time it shouldn't go into VpnManager anymore.
+ dpm.setAlwaysOnVpnPackage(admin1, null, false, null);
+
+ verifyNoMoreInteractions(getServices().vpnManager);
+ verifyNoMoreInteractions(getServices().appOpsManager);
+ }
+
+ @Test
+ public void testDisallowConfigVpn_clearsUserVpn() throws Exception {
+ final String userVpnPackage = "org.some.vpn.servcie";
+ final int userVpnUid = 20374;
+
+ setDeviceOwner();
+
+ setupVpnAuthorization(userVpnPackage, userVpnUid);
+
+ simulateRestrictionAdded(UserManager.DISALLOW_CONFIG_VPN);
+
+ verify(getServices().vpnManager).setAlwaysOnVpnPackageForUser(
+ UserHandle.USER_SYSTEM, null, false, null);
+ verify(getServices().appOpsManager).setMode(OP_ACTIVATE_VPN,
+ userVpnUid, userVpnPackage, MODE_DEFAULT);
+ }
+
+ @Test
+ public void testDisallowConfigVpn_doesntKillAdminVpn() throws Exception {
+ setDeviceOwner();
+
+ when(getServices().vpnManager
+ .setAlwaysOnVpnPackageForUser(anyInt(), any(), anyBoolean(), any()))
+ .thenReturn(true);
+
+ // Set VPN package to admin package.
+ dpm.setAlwaysOnVpnPackage(admin1, admin1.getPackageName(), false, null);
+ setupVpnAuthorization(admin1.getPackageName(), DpmMockContext.CALLER_SYSTEM_USER_UID);
+ clearInvocations(getServices().vpnManager);
+
+ simulateRestrictionAdded(UserManager.DISALLOW_CONFIG_VPN);
+
+ // Admin-set package should remain always-on and should retain its authorization.
+ verifyNoMoreInteractions(getServices().vpnManager);
+ verify(getServices().appOpsManager, never()).setMode(OP_ACTIVATE_VPN,
+ DpmMockContext.CALLER_SYSTEM_USER_UID, admin1.getPackageName(), MODE_DEFAULT);
+ }
+
+ private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
+ final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
+ userVpnUid, List.of(new AppOpsManager.OpEntry(
+ OP_ACTIVATE_VPN, MODE_ALLOWED, Collections.emptyMap())));
+ when(getServices().appOpsManager.getPackagesForOps(any(int[].class)))
+ .thenReturn(List.of(vpnOp));
+ }
+
+ private void simulateRestrictionAdded(String restriction) {
+ RestrictionsListener listener = new RestrictionsListener(
+ mServiceContext, getServices().userManagerInternal, dpms);
+
+ final Bundle newRestrictions = new Bundle();
+ newRestrictions.putBoolean(restriction, true);
+ listener.onUserRestrictionsChanged(UserHandle.USER_SYSTEM, newRestrictions, new Bundle());
+ }
+
private void setUserUnlocked(int userHandle, boolean unlocked) {
when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 2e00468..d10419d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -230,6 +230,8 @@
return mMockSystemServices.appOpsManager;
case Context.CROSS_PROFILE_APPS_SERVICE:
return mMockSystemServices.crossProfileApps;
+ case Context.VPN_MANAGEMENT_SERVICE:
+ return mMockSystemServices.vpnManager;
}
throw new UnsupportedOperationException();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 9cc0572..8a2919d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -50,6 +50,7 @@
import android.net.ConnectivityManager;
import android.net.IIpConnectivityMetrics;
import android.net.Uri;
+import android.net.VpnManager;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.PowerManager;
@@ -123,6 +124,7 @@
public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
public final AppOpsManager appOpsManager;
public final UsbManager usbManager;
+ public final VpnManager vpnManager;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
public final BuildMock buildMock = new BuildMock();
@@ -169,6 +171,7 @@
persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
appOpsManager = mock(AppOpsManager.class);
usbManager = mock(UsbManager.class);
+ vpnManager = mock(VpnManager.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index c506485..2a4896a 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -134,6 +134,8 @@
private static final String NOTIFICATION_CHANNEL_ID = "test : sc";
private static final String PARENT_NOTIFICATION_CHANNEL_ID = "test";
private static final long MILLIS_PER_MINUTE = 1000L * 60L;
+ private static final String GENERIC_KEY = "key";
+ private static final String CUSTOM_KEY = "custom";
@Mock
private Context mContext;
@@ -158,7 +160,11 @@
@Mock
private JobScheduler mJobScheduler;
@Mock
- private StatusBarNotification mStatusBarNotification;
+ private StatusBarNotification mGenericSbn;
+ @Mock
+ private StatusBarNotification mConvoSbn;
+ @Mock
+ private NotificationListenerService.RankingMap mRankingMap;
@Mock
private Notification mNotification;
@Mock
@@ -202,8 +208,6 @@
mParentNotificationChannel = new NotificationChannel(
PARENT_NOTIFICATION_CHANNEL_ID, "test channel",
NotificationManager.IMPORTANCE_DEFAULT);
- when(mNotificationManagerInternal.getNotificationChannel(anyString(), anyInt(),
- anyString())).thenReturn(mParentNotificationChannel);
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
@@ -246,16 +250,26 @@
when(mPackageManager.getPackageUidAsUser(TEST_PKG_NAME, USER_ID_PRIMARY))
.thenReturn(TEST_PKG_UID);
- when(mStatusBarNotification.getNotification()).thenReturn(mNotification);
- when(mStatusBarNotification.getPackageName()).thenReturn(TEST_PKG_NAME);
- when(mStatusBarNotification.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY));
- when(mStatusBarNotification.getPostTime()).thenReturn(System.currentTimeMillis());
- when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID);
- when(mNotification.getChannelId()).thenReturn(PARENT_NOTIFICATION_CHANNEL_ID);
-
mNotificationChannel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "test channel", NotificationManager.IMPORTANCE_DEFAULT);
- mNotificationChannel.setConversationId("test", TEST_SHORTCUT_ID);
+ mNotificationChannel.setConversationId(PARENT_NOTIFICATION_CHANNEL_ID, TEST_SHORTCUT_ID);
+ when(mNotificationManagerInternal.getNotificationChannel(anyString(), anyInt(),
+ eq(mNotificationChannel.getId()))).thenReturn(mNotificationChannel);
+ when(mNotificationManagerInternal.getNotificationChannel(anyString(), anyInt(),
+ eq(mParentNotificationChannel.getId()))).thenReturn(mParentNotificationChannel);
+
+ when(mGenericSbn.getKey()).thenReturn(GENERIC_KEY);
+ when(mGenericSbn.getNotification()).thenReturn(mNotification);
+ when(mGenericSbn.getPackageName()).thenReturn(TEST_PKG_NAME);
+ when(mGenericSbn.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY));
+ when(mGenericSbn.getPostTime()).thenReturn(System.currentTimeMillis());
+ when(mConvoSbn.getKey()).thenReturn(CUSTOM_KEY);
+ when(mConvoSbn.getNotification()).thenReturn(mNotification);
+ when(mConvoSbn.getPackageName()).thenReturn(TEST_PKG_NAME);
+ when(mConvoSbn.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY));
+ when(mConvoSbn.getPostTime()).thenReturn(System.currentTimeMillis());
+
+ when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID);
mCancellationSignal = new CancellationSignal();
@@ -449,10 +463,7 @@
buildPerson());
mDataManager.addOrUpdateConversationInfo(shortcut);
- NotificationListenerService listenerService =
- mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
-
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
List<Range<Long>> activeNotificationOpenTimeSlots = getActiveSlotsForTestShortcut(
Event.NOTIFICATION_EVENT_TYPES);
@@ -471,7 +482,7 @@
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ listenerService.onNotificationRemoved(mGenericSbn, null,
NotificationListenerService.REASON_CLICK);
List<Range<Long>> activeNotificationOpenTimeSlots = getActiveSlotsForTestShortcut(
@@ -495,20 +506,20 @@
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
when(mNotification.getShortcutId()).thenReturn(shortcutId);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
}
// Post another notification for the last conversation.
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
// Removing one of the two notifications does not un-cache the shortcut.
- listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ listenerService.onNotificationRemoved(mGenericSbn, null,
NotificationListenerService.REASON_CANCEL);
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
anyInt(), any(), anyString(), any(), anyInt(), anyInt());
// Removing the second notification un-caches the shortcut.
- listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ listenerService.onNotificationRemoved(mGenericSbn, null,
NotificationListenerService.REASON_CANCEL_ALL);
verify(mShortcutServiceInternal).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME), anyList(), eq(USER_ID_PRIMARY),
@@ -526,7 +537,7 @@
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
@@ -626,19 +637,13 @@
buildPerson());
mDataManager.addOrUpdateConversationInfo(shortcut);
- NotificationListenerService listenerService =
- mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
-
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendConvoNotification();
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
TEST_SHORTCUT_ID)).isNotNull();
- listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
- mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
-
ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
TEST_SHORTCUT_ID);
assertThat(result).isNotNull();
@@ -661,9 +666,7 @@
assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
TEST_SHORTCUT_ID + "1")).isNull();
- NotificationListenerService listenerService =
- mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendConvoNotification();
ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs);
@@ -673,15 +676,51 @@
assertEquals(shortcut.getId(), result.getShortcutInfo().getId());
assertEquals(1, result.getShortcutInfo().getPersons().length);
assertEquals(CONTACT_URI, result.getShortcutInfo().getPersons()[0].getUri());
+ assertEquals(mNotificationChannel.getId(), result.getNotificationChannel().getId());
assertEquals(mParentNotificationChannel.getId(),
- result.getParentNotificationChannel().getId());
- assertEquals(mStatusBarNotification.getPostTime(), result.getLastEventTimestamp());
+ result.getNotificationChannel().getParentChannelId());
+ assertEquals(mConvoSbn.getPostTime(), result.getLastEventTimestamp());
assertTrue(result.hasActiveNotifications());
assertFalse(result.hasBirthdayToday());
assertThat(result.getStatuses()).containsExactly(cs);
}
@Test
+ public void testOnNotificationChannelModified() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+ assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID)).isNull();
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+
+ sendConvoNotification();
+
+ ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID);
+ assertFalse(result.getNotificationChannel().canBubble());
+
+ NotificationChannel updated = new NotificationChannel(mNotificationChannel.getId(),
+ mNotificationChannel.getDescription(), mNotificationChannel.getImportance());
+ updated.setConversationId(mNotificationChannel.getParentChannelId(),
+ mNotificationChannel.getConversationId());
+ updated.setAllowBubbles(true);
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
+ updated, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
+
+ ConversationInfo ci = mDataManager.getConversationInfo(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID);
+ assertThat(ci).isNotNull();
+ assertEquals(mNotificationChannel.getId(), ci.getNotificationChannelId());
+ assertEquals(mParentNotificationChannel.getId(), ci.getParentNotificationChannelId());
+ assertTrue(ci.isBubbled());
+ }
+
+ @Test
public void testGetConversation_demoted() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
@@ -713,11 +752,8 @@
shortcut.setCached(ShortcutInfo.FLAG_PINNED);
mDataManager.addOrUpdateConversationInfo(shortcut);
- NotificationListenerService listenerService =
- mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationPosted(mStatusBarNotification);
- ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
- TEST_SHORTCUT_ID);
+ sendGenericNotification();
+ mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID);
verify(mShortcutServiceInternal).getShortcuts(
anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
@@ -1085,7 +1121,7 @@
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
when(mNotification.getShortcutId()).thenReturn(shortcutId);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
}
mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
@@ -1107,9 +1143,9 @@
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
when(mNotification.getShortcutId()).thenReturn(shortcutId);
- when(mStatusBarNotification.getPostTime()).thenReturn(100L + i);
- listenerService.onNotificationPosted(mStatusBarNotification);
- listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ when(mGenericSbn.getPostTime()).thenReturn(100L + i);
+ sendGenericNotification();
+ listenerService.onNotificationRemoved(mGenericSbn, null,
NotificationListenerService.REASON_CANCEL);
}
@@ -1162,9 +1198,7 @@
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
- NotificationListenerService listenerService =
- mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY);
assertEquals(1, result.size());
@@ -1172,8 +1206,9 @@
assertEquals(1, result.get(0).getShortcutInfo().getPersons().length);
assertEquals(CONTACT_URI, result.get(0).getShortcutInfo().getPersons()[0].getUri());
assertEquals(mParentNotificationChannel.getId(),
- result.get(0).getParentNotificationChannel().getId());
- assertEquals(mStatusBarNotification.getPostTime(), result.get(0).getLastEventTimestamp());
+ result.get(0).getNotificationChannel().getId());
+ assertEquals(null, result.get(0).getNotificationChannel().getParentChannelId());
+ assertEquals(mGenericSbn.getPostTime(), result.get(0).getLastEventTimestamp());
assertTrue(result.get(0).hasActiveNotifications());
}
@@ -1186,11 +1221,9 @@
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
- NotificationListenerService listenerService =
- mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
- List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY);
+ mDataManager.getRecentConversations(USER_ID_PRIMARY);
verify(mShortcutServiceInternal).getShortcuts(
anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
@@ -1211,8 +1244,8 @@
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID);
- listenerService.onNotificationPosted(mStatusBarNotification);
- listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ sendGenericNotification();
+ listenerService.onNotificationRemoved(mGenericSbn, null,
NotificationListenerService.REASON_CLICK);
mDataManager.pruneOldRecentConversations(USER_ID_PRIMARY,
@@ -1231,11 +1264,9 @@
buildPerson());
mDataManager.addOrUpdateConversationInfo(shortcut);
- NotificationListenerService listenerService =
- mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
- assertEquals(mStatusBarNotification.getPostTime(),
+ assertEquals(mGenericSbn.getPostTime(),
mDataManager.getLastInteraction(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID));
assertEquals(0L,
mDataManager.getLastInteraction("not_test_pkg", USER_ID_PRIMARY, TEST_SHORTCUT_ID));
@@ -1375,9 +1406,7 @@
TEST_SHORTCUT_ID, buildPerson());
mDataManager.addOrUpdateConversationInfo(shortcut);
- NotificationListenerService listenerService =
- mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY);
assertTrue(result.isEmpty());
@@ -1395,7 +1424,7 @@
// Post a notification and customize the notification settings.
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -1414,7 +1443,7 @@
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
// posting updates the last interaction time, so delay before deletion
try {
Thread.sleep(500);
@@ -1422,7 +1451,7 @@
e.printStackTrace();
}
long approxDeletionTime = System.currentTimeMillis();
- listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ listenerService.onNotificationRemoved(mGenericSbn, null,
NotificationListenerService.REASON_CANCEL);
ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
@@ -1442,8 +1471,8 @@
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- listenerService.onNotificationPosted(mStatusBarNotification);
- listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ sendGenericNotification();
+ listenerService.onNotificationRemoved(mGenericSbn, null,
NotificationListenerService.REASON_CANCEL);
mDataManager.removeRecentConversation(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
USER_ID_PRIMARY);
@@ -1472,14 +1501,14 @@
// Post a notification and then dismiss it for conversation #1.
when(mNotification.getShortcutId()).thenReturn("1");
- listenerService.onNotificationPosted(mStatusBarNotification);
- listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ sendGenericNotification();
+ listenerService.onNotificationRemoved(mGenericSbn, null,
NotificationListenerService.REASON_CANCEL);
// Post a notification for conversation #2, but don't dismiss it. Its shortcut won't be
// uncached when removeAllRecentConversations() is called.
when(mNotification.getShortcutId()).thenReturn("2");
- listenerService.onNotificationPosted(mStatusBarNotification);
+ sendGenericNotification();
mDataManager.removeAllRecentConversations(USER_ID_PRIMARY);
@@ -1562,6 +1591,58 @@
return (queryFlags & flag) != 0;
}
+ // "Sends" a notification to a non-customized notification channel - the notification channel
+ // is something generic like "messages" and the notification has a shortcut id
+ private void sendGenericNotification() {
+ when(mNotification.getChannelId()).thenReturn(PARENT_NOTIFICATION_CHANNEL_ID);
+ doAnswer(invocationOnMock -> {
+ NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
+ invocationOnMock.getArguments()[1];
+ ranking.populate(
+ (String) invocationOnMock.getArguments()[0],
+ 0,
+ false,
+ 0,
+ 0,
+ mParentNotificationChannel.getImportance(),
+ null, null,
+ mParentNotificationChannel, null, null, true, 0, false, -1, false, null, null,
+ false, false, false, null, 0, false);
+ return true;
+ }).when(mRankingMap).getRanking(eq(GENERIC_KEY),
+ any(NotificationListenerService.Ranking.class));
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationPosted(mGenericSbn, mRankingMap);
+ }
+
+ // "Sends" a notification to a customized notification channel - the notification channel
+ // is specific to a person, and the channel has a convo id matching the notification's shortcut
+ // and the channel has a parent channel id
+ private void sendConvoNotification() {
+ when(mNotification.getChannelId()).thenReturn(NOTIFICATION_CHANNEL_ID);
+ doAnswer(invocationOnMock -> {
+ NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
+ invocationOnMock.getArguments()[1];
+ ranking.populate(
+ (String) invocationOnMock.getArguments()[0],
+ 0,
+ false,
+ 0,
+ 0,
+ mNotificationChannel.getImportance(),
+ null, null,
+ mNotificationChannel, null, null, true, 0, false, -1, false, null, null, false,
+ false, false, null, 0, false);
+ return true;
+ }).when(mRankingMap).getRanking(eq(CUSTOM_KEY),
+ any(NotificationListenerService.Ranking.class));
+
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationPosted(mConvoSbn, mRankingMap);
+ }
+
private class TestContactsQueryHelper extends ContactsQueryHelper {
private Uri mContactUri;
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/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index db7e437..d2270b5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -50,6 +50,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.same;
@@ -357,6 +358,11 @@
final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "window");
assertEquals(window, mActivity.findMainWindow());
+
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(true).when(mActivity.mLetterboxUiController)
+ .isSurfaceReadyAndVisible(any());
+
assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
mActivity.findMainWindow()));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index b3a0745..4c31ee2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -53,12 +53,12 @@
}
@Override
- public void insetsChanged(InsetsState insetsState) throws RemoteException {
+ public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {
}
@Override
- public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)
- throws RemoteException {
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls, boolean willMove, boolean willResize) {
}
@Override
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/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 326d604..bac703121 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -139,6 +139,17 @@
protected long getAutoDisconnectTimeoutMs() {
return -1;
}
+
+ @Override
+ public void binderDied() {
+ super.binderDied();
+ Slog.w(TAG, "binderDied");
+ try {
+ callback.onError(-1);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to report onError status: " + e);
+ }
+ }
};
mRemoteHotwordDetectionService.connect();
if (callback == null) {
@@ -337,7 +348,7 @@
if (DEBUG) {
Slog.d(TAG, "onDetected");
}
- externalCallback.onKeyphraseDetected(recognitionEvent);
+ externalCallback.onKeyphraseDetected(recognitionEvent, result);
}
@Override
@@ -370,8 +381,7 @@
if (DEBUG) {
Slog.d(TAG, "onDetected");
}
- // TODO: Propagate the HotwordDetectedResult.
- externalCallback.onKeyphraseDetected(recognitionEvent);
+ externalCallback.onKeyphraseDetected(recognitionEvent, result);
}
@Override
@@ -415,7 +425,7 @@
mHotwordDetectionConnection.detectFromDspSource(
recognitionEvent, mExternalCallback);
} else {
- mExternalCallback.onKeyphraseDetected(recognitionEvent);
+ mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
}
}
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;