Merge "Disable tests flaky on flame" into sc-dev
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 ed55f00..3bbc945 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -64,7 +64,7 @@
public void onStart() {
publishBinderService(Context.APP_SEARCH_SERVICE, new Stub());
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mImplInstanceManager = new ImplInstanceManager(getContext());
+ mImplInstanceManager = ImplInstanceManager.getInstance(getContext());
}
private class Stub extends IAppSearchManager.Stub {
@@ -102,7 +102,8 @@
}
schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
}
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
impl.setSchema(
packageName,
databaseName,
@@ -133,7 +134,8 @@
final long callingIdentity = Binder.clearCallingIdentity();
try {
verifyCallingPackage(callingUid, packageName);
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
List<AppSearchSchema> schemas = impl.getSchema(packageName, databaseName);
List<Bundle> schemaBundles = new ArrayList<>(schemas.size());
for (int i = 0; i < schemas.size(); i++) {
@@ -166,7 +168,8 @@
verifyCallingPackage(callingUid, packageName);
AppSearchBatchResult.Builder<String, Void> resultBuilder =
new AppSearchBatchResult.Builder<>();
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
for (int i = 0; i < documentBundles.size(); i++) {
GenericDocument document = new GenericDocument(documentBundles.get(i));
try {
@@ -207,12 +210,18 @@
verifyCallingPackage(callingUid, packageName);
AppSearchBatchResult.Builder<String, Bundle> resultBuilder =
new AppSearchBatchResult.Builder<>();
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
for (int i = 0; i < uris.size(); i++) {
String uri = uris.get(i);
try {
- GenericDocument document = impl.getDocument(packageName, databaseName,
- namespace, uri, typePropertyPaths);
+ GenericDocument document =
+ impl.getDocument(
+ packageName,
+ databaseName,
+ namespace,
+ uri,
+ typePropertyPaths);
resultBuilder.setSuccess(uri, document.getBundle());
} catch (Throwable t) {
resultBuilder.setResult(uri, throwableToFailedResult(t));
@@ -245,7 +254,8 @@
final long callingIdentity = Binder.clearCallingIdentity();
try {
verifyCallingPackage(callingUid, packageName);
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
SearchResultPage searchResultPage =
impl.query(
packageName,
@@ -278,12 +288,14 @@
final long callingIdentity = Binder.clearCallingIdentity();
try {
verifyCallingPackage(callingUid, packageName);
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
- SearchResultPage searchResultPage = impl.globalQuery(
- queryExpression,
- new SearchSpec(searchSpecBundle),
- packageName,
- callingUid);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
+ SearchResultPage searchResultPage =
+ impl.globalQuery(
+ queryExpression,
+ new SearchSpec(searchSpecBundle),
+ packageName,
+ callingUid);
invokeCallbackOnResult(
callback,
AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
@@ -306,7 +318,8 @@
// TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally
// opened it
try {
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
SearchResultPage searchResultPage = impl.getNextPage(nextPageToken);
invokeCallbackOnResult(
callback,
@@ -324,7 +337,8 @@
int callingUserId = handleIncomingUser(userId, callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
impl.invalidateNextPageToken(nextPageToken);
} catch (Throwable t) {
Log.e(TAG, "Unable to invalidate the query page token", t);
@@ -350,15 +364,11 @@
int callingUserId = handleIncomingUser(userId, callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
- impl.reportUsage(
- packageName,
- databaseName,
- namespace,
- uri,
- usageTimeMillis);
- invokeCallbackOnResult(callback,
- AppSearchResult.newSuccessfulResult(/*result=*/ null));
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
+ impl.reportUsage(packageName, databaseName, namespace, uri, usageTimeMillis);
+ invokeCallbackOnResult(
+ callback, AppSearchResult.newSuccessfulResult(/*result=*/ null));
} catch (Throwable t) {
invokeCallbackOnError(callback, t);
} finally {
@@ -385,7 +395,8 @@
verifyCallingPackage(callingUid, packageName);
AppSearchBatchResult.Builder<String, Void> resultBuilder =
new AppSearchBatchResult.Builder<>();
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
for (int i = 0; i < uris.size(); i++) {
String uri = uris.get(i);
try {
@@ -421,7 +432,8 @@
final long callingIdentity = Binder.clearCallingIdentity();
try {
verifyCallingPackage(callingUid, packageName);
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
impl.removeByQuery(
packageName,
databaseName,
@@ -441,7 +453,8 @@
int callingUserId = handleIncomingUser(userId, callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
- AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+ AppSearchImpl impl =
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
impl.persistToDisk();
} catch (Throwable t) {
Log.e(TAG, "Unable to persist the data to disk", t);
@@ -457,7 +470,7 @@
int callingUserId = handleIncomingUser(userId, callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
- mImplInstanceManager.getInstance(callingUserId);
+ mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
} catch (Throwable t) {
invokeCallbackOnError(callback, t);
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 fe3c2e1..97b1a8c 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -41,14 +41,33 @@
public final class ImplInstanceManager {
private static final String APP_SEARCH_DIR = "appSearch";
- private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>();
+ private static ImplInstanceManager sImplInstanceManager;
- private final Context mContext;
+ private final SparseArray<AppSearchImpl> mInstances = new SparseArray<>();
private final String mGlobalQuerierPackage;
- public ImplInstanceManager(@NonNull Context context) {
- mContext = context;
- mGlobalQuerierPackage = getGlobalAppSearchDataQuerierPackageName(mContext);
+ private ImplInstanceManager(@NonNull String globalQuerierPackage) {
+ mGlobalQuerierPackage = globalQuerierPackage;
+ }
+
+ /**
+ * Gets an instance of ImplInstanceManager 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 ImplInstanceManager getInstance(@NonNull Context context) {
+ if (sImplInstanceManager == null) {
+ synchronized (ImplInstanceManager.class) {
+ if (sImplInstanceManager == null) {
+ sImplInstanceManager =
+ new ImplInstanceManager(
+ getGlobalAppSearchDataQuerierPackageName(context));
+ }
+ }
+ }
+ return sImplInstanceManager;
}
/**
@@ -57,30 +76,30 @@
* <p>If no AppSearchImpl instance exists for this user, Icing will be initialized and one will
* be created.
*
+ * @param context The context
* @param userId The multi-user userId of the device user calling AppSearch
* @return An initialized {@link AppSearchImpl} for this user
*/
@NonNull
- public AppSearchImpl getInstance(@UserIdInt int userId)
+ public AppSearchImpl getAppSearchImpl(@NonNull Context context, @UserIdInt int userId)
throws AppSearchException {
- AppSearchImpl instance = sInstances.get(userId);
+ AppSearchImpl instance = mInstances.get(userId);
if (instance == null) {
synchronized (ImplInstanceManager.class) {
- instance = sInstances.get(userId);
+ instance = mInstances.get(userId);
if (instance == null) {
- instance = createImpl(userId);
- sInstances.put(userId, instance);
+ instance = createImpl(context, userId);
+ mInstances.put(userId, instance);
}
}
}
return instance;
}
- private AppSearchImpl createImpl(@UserIdInt int userId)
+ private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
throws AppSearchException {
- File appSearchDir = getAppSearchDir(mContext, userId);
- return AppSearchImpl.create(
- appSearchDir, mContext, userId, mGlobalQuerierPackage);
+ File appSearchDir = getAppSearchDir(context, userId);
+ return AppSearchImpl.create(appSearchDir, context, userId, mGlobalQuerierPackage);
}
private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) {
@@ -96,7 +115,8 @@
*
* @param context Context of the system service.
*/
- private static String getGlobalAppSearchDataQuerierPackageName(Context context) {
+ @NonNull
+ private static String getGlobalAppSearchDataQuerierPackageName(@NonNull Context context) {
String globalAppSearchDataQuerierPackage =
context.getString(R.string.config_globalAppSearchDataQuerierPackage);
try {
diff --git a/core/api/current.txt b/core/api/current.txt
index ec712d8..3dd1f59 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -46182,6 +46182,7 @@
method @NonNull public android.graphics.Rect getBoundingRectRight();
method @NonNull public android.graphics.Rect getBoundingRectTop();
method @NonNull public java.util.List<android.graphics.Rect> getBoundingRects();
+ method @Nullable public android.graphics.Path getCutoutPath();
method public int getSafeInsetBottom();
method public int getSafeInsetLeft();
method public int getSafeInsetRight();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c237d7d..ea7dc03 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -684,6 +684,7 @@
method public void holdLock(android.os.IBinder, int);
field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
+ field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80
field public static final int MATCH_KNOWN_PACKAGES = 4202496; // 0x402000
field public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services";
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 68d3a92..49f508d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5255,6 +5255,7 @@
// We still want a time to be set but gone, such that we can show and hide it
// on demand in case it's a child notification without anything in the header
contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
+ setTextViewColorSecondary(contentView, R.id.time, p);
}
}
diff --git a/core/java/android/app/people/ConversationChannel.aidl b/core/java/android/app/people/ConversationChannel.aidl
new file mode 100644
index 0000000..78df2f1
--- /dev/null
+++ b/core/java/android/app/people/ConversationChannel.aidl
@@ -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.
+ */
+
+package android.app.people;
+
+parcelable ConversationChannel;
\ No newline at end of file
diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl
index 0d12ed0..ebe9f60 100644
--- a/core/java/android/app/people/IPeopleManager.aidl
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -17,6 +17,7 @@
package android.app.people;
import android.app.people.ConversationStatus;
+import android.app.people.ConversationChannel;
import android.content.pm.ParceledListSlice;
import android.net.Uri;
import android.os.IBinder;
@@ -26,6 +27,13 @@
* {@hide}
*/
interface IPeopleManager {
+
+ /**
+ * Returns the specified conversation from the conversations list. If the conversation can't be
+ * found, returns null.
+ */
+ ConversationChannel getConversation(in String packageName, int userId, in String shortcutId);
+
/**
* Returns the recent conversations. The conversations that have customized notification
* settings are excluded from the returned list.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 68792b2..9ae9c25 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3385,6 +3385,7 @@
* {@link #hasSystemFeature}: This device supports HDMI-CEC.
* @hide
*/
+ @TestApi
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index fe8bf9d..e6c0f6a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6265,6 +6265,55 @@
}
/**
+ * Returns whether this {@code SigningDetails} has a signer in common with the provided
+ * {@code otherDetails} with the specified {@code flags} capabilities provided by this
+ * signer.
+ *
+ * <p>Note this method allows for the signing lineage to diverge, so this should only be
+ * used for instances where the only requirement is a common signer in the lineage with
+ * the specified capabilities. If the current signer of this instance is an ancestor of
+ * {@code otherDetails} then {@code true} is immediately returned since the current signer
+ * has all capabilities granted.
+ */
+ public boolean hasCommonSignerWithCapability(SigningDetails otherDetails,
+ @CertCapabilities int flags) {
+ if (this == UNKNOWN || otherDetails == UNKNOWN) {
+ return false;
+ }
+ // If either is signed with more than one signer then both must be signed by the same
+ // signers to consider the capabilities granted.
+ if (signatures.length > 1 || otherDetails.signatures.length > 1) {
+ return signaturesMatchExactly(otherDetails);
+ }
+ // The Signature class does not use the granted capabilities in the hashCode
+ // computation, so a Set can be used to check for a common signer.
+ Set<Signature> otherSignatures = new ArraySet<>();
+ if (otherDetails.hasPastSigningCertificates()) {
+ otherSignatures.addAll(Arrays.asList(otherDetails.pastSigningCertificates));
+ } else {
+ otherSignatures.addAll(Arrays.asList(otherDetails.signatures));
+ }
+ // If the current signer of this instance is an ancestor of the other than return true
+ // since all capabilities are granted to the current signer.
+ if (otherSignatures.contains(signatures[0])) {
+ return true;
+ }
+ if (hasPastSigningCertificates()) {
+ // Since the current signer was checked above and the last signature in the
+ // pastSigningCertificates is the current signer skip checking the last element.
+ for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
+ if (otherSignatures.contains(pastSigningCertificates[i])) {
+ // If the caller specified multiple capabilities ensure all are set.
+ if ((pastSigningCertificates[i].getFlags() & flags) == flags) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
* Determines if the provided {@code oldDetails} is an ancestor of this one, and whether or
* not this one grants it the provided capability, represented by the {@code flags}
* parameter. In the event of signing certificate rotation, a package may still interact
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index b1b2925..8b6082b3 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -28,7 +28,10 @@
import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
import static android.app.AppOpsManager.opToPermission;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
+import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
+import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+import android.Manifest;
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.ComponentName;
@@ -41,12 +44,14 @@
import android.content.pm.ResolveInfo;
import android.icu.text.ListFormatter;
import android.location.LocationManager;
+import android.media.AudioManager;
import android.os.Process;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.speech.RecognitionService;
import android.speech.RecognizerIntent;
+import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.inputmethod.InputMethodInfo;
@@ -75,6 +80,9 @@
private static final String PROPERTY_LOCATION_INDICATORS_ENABLED =
"location_indicators_enabled";
+ /** Whether to show the Permissions Hub. */
+ private static final String PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled";
+
/** How long after an access to show it as "recent" */
private static final String RECENT_ACCESS_TIME_MS = "recent_acccess_time_ms";
@@ -84,17 +92,25 @@
/** The name of the expected voice IME subtype */
private static final String VOICE_IME_SUBTYPE = "voice";
+ private static final String SYSTEM_PKG = "android";
+
private static final long DEFAULT_RUNNING_TIME_MS = 5000L;
private static final long DEFAULT_RECENT_TIME_MS = 30000L;
+ private static boolean shouldShowPermissionsHub() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PERMISSIONS_HUB_2_ENABLED, false);
+ }
+
private static boolean shouldShowIndicators() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_CAMERA_MIC_ICONS_ENABLED, true);
+ PROPERTY_CAMERA_MIC_ICONS_ENABLED, true) || shouldShowPermissionsHub();
}
private static boolean shouldShowLocationIndicator() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_LOCATION_INDICATORS_ENABLED, false);
+ PROPERTY_LOCATION_INDICATORS_ENABLED, false)
+ || shouldShowPermissionsHub();
}
private static long getRecentThreshold(Long now) {
@@ -113,7 +129,7 @@
);
private static final List<String> MIC_OPS = List.of(
- OPSTR_PHONE_CALL_CAMERA,
+ OPSTR_PHONE_CALL_MICROPHONE,
OPSTR_RECORD_AUDIO
);
@@ -163,6 +179,13 @@
return mUserContexts.get(user);
}
+ // TODO ntmyren: Replace this with better check if this moves beyond teamfood
+ private boolean isAppPredictor(String packageName, UserHandle user) {
+ return shouldShowPermissionsHub() && getUserContext(user).getPackageManager()
+ .checkPermission(Manifest.permission.MANAGE_APP_PREDICTIONS, packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
/**
* @see PermissionManager.getIndicatorAppOpUsageData
*/
@@ -186,7 +209,28 @@
Map<PackageAttribution, CharSequence> packagesWithAttributionLabels =
getTrustedAttributions(rawUsages.get(MICROPHONE), proxyChains);
- List<String> usedPermGroups = new ArrayList<>(rawUsages.keySet());
+ ArrayList<String> usedPermGroups = new ArrayList<>(rawUsages.keySet());
+
+ // If we have a phone call, and a carrier privileged app using microphone, hide the
+ // phone call.
+ AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+ boolean hasPhoneCall = usedPermGroups.contains(OPSTR_PHONE_CALL_CAMERA)
+ || usedPermGroups.contains(OPSTR_PHONE_CALL_MICROPHONE);
+ if (hasPhoneCall && usedPermGroups.contains(MICROPHONE) && audioManager.getMode()
+ == MODE_IN_COMMUNICATION) {
+ TelephonyManager telephonyManager =
+ mContext.getSystemService(TelephonyManager.class);
+ List<OpUsage> permUsages = rawUsages.get(MICROPHONE);
+ for (int usageNum = 0; usageNum < permUsages.size(); usageNum++) {
+ if (telephonyManager.checkCarrierPrivilegesForPackage(
+ permUsages.get(usageNum).packageName)
+ == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ usedPermGroups.remove(OPSTR_PHONE_CALL_CAMERA);
+ usedPermGroups.remove(OPSTR_PHONE_CALL_MICROPHONE);
+ }
+ }
+ }
+
for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) {
boolean isPhone = false;
String permGroup = usedPermGroups.get(permGroupNum);
@@ -269,8 +313,11 @@
if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) {
continue;
}
- if (!isUserSensitive(packageName, user, op)
- && !isLocationProvider(packageName, user)) {
+
+ if (packageName.equals(SYSTEM_PKG)
+ || (!isUserSensitive(packageName, user, op)
+ && !isLocationProvider(packageName, user)
+ && !isAppPredictor(packageName, user))) {
continue;
}
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
index a44ed59..698cb77 100644
--- a/core/java/android/util/RotationUtils.java
+++ b/core/java/android/util/RotationUtils.java
@@ -21,7 +21,9 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import android.annotation.Dimension;
import android.graphics.Insets;
+import android.graphics.Matrix;
import android.view.Surface.Rotation;
/**
@@ -69,4 +71,34 @@
}
return rotated;
}
+
+ /**
+ * Sets a matrix such that given a rotation, it transforms physical display
+ * coordinates to that rotation's logical coordinates.
+ *
+ * @param rotation the rotation to which the matrix should transform
+ * @param out the matrix to be set
+ */
+ public static void transformPhysicalToLogicalCoordinates(@Rotation int rotation,
+ @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
+ switch (rotation) {
+ case ROTATION_0:
+ out.reset();
+ break;
+ case ROTATION_90:
+ out.setRotate(270);
+ out.postTranslate(0, physicalWidth);
+ break;
+ case ROTATION_180:
+ out.setRotate(180);
+ out.postTranslate(physicalWidth, physicalHeight);
+ break;
+ case ROTATION_270:
+ out.setRotate(90);
+ out.postTranslate(physicalHeight, 0);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown rotation: " + rotation);
+ }
+ }
}
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 525ac53..e1a4402 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -23,6 +23,7 @@
import static android.view.DisplayCutoutProto.BOUND_RIGHT;
import static android.view.DisplayCutoutProto.BOUND_TOP;
import static android.view.DisplayCutoutProto.INSETS;
+import static android.view.Surface.ROTATION_0;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -31,13 +32,16 @@
import android.annotation.Nullable;
import android.content.res.Resources;
import android.graphics.Insets;
+import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Pair;
+import android.util.RotationUtils;
import android.util.proto.ProtoOutputStream;
+import android.view.Surface.Rotation;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -69,6 +73,9 @@
"com.android.internal.display_cutout_emulation";
private static final Rect ZERO_RECT = new Rect();
+ private static final CutoutPathParserInfo EMPTY_PARSER_INFO = new CutoutPathParserInfo(
+ 0 /* displayWidth */, 0 /* displayHeight */, 0f /* density */, "" /* cutoutSpec */,
+ 0 /* rotation */, 0f /* scale */);
/**
* An instance where {@link #isEmpty()} returns {@code true}.
@@ -76,7 +83,7 @@
* @hide
*/
public static final DisplayCutout NO_CUTOUT = new DisplayCutout(
- ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT,
+ ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, EMPTY_PARSER_INFO,
false /* copyArguments */);
@@ -96,11 +103,15 @@
@GuardedBy("CACHE_LOCK")
private static Insets sCachedWaterfallInsets;
+ @GuardedBy("CACHE_LOCK")
+ private static CutoutPathParserInfo sCachedCutoutPathParserInfo;
+ @GuardedBy("CACHE_LOCK")
+ private static Path sCachedCutoutPath;
+
private final Rect mSafeInsets;
@NonNull
private final Insets mWaterfallInsets;
-
/**
* The bound is at the left of the screen.
* @hide
@@ -210,6 +221,7 @@
}
return result;
}
+
@Override
public boolean equals(@Nullable Object o) {
if (o == this) {
@@ -232,6 +244,106 @@
private final Bounds mBounds;
/**
+ * Stores all the needed info to create the cutout paths.
+ *
+ * @hide
+ */
+ public static class CutoutPathParserInfo {
+ private final int mDisplayWidth;
+ private final int mDisplayHeight;
+ private final float mDensity;
+ private final String mCutoutSpec;
+ private final @Rotation int mRotation;
+ private final float mScale;
+
+ public CutoutPathParserInfo(int displayWidth, int displayHeight, float density,
+ String cutoutSpec, @Rotation int rotation, float scale) {
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ mDensity = density;
+ mCutoutSpec = cutoutSpec == null ? "" : cutoutSpec;
+ mRotation = rotation;
+ mScale = scale;
+ }
+
+ public CutoutPathParserInfo(CutoutPathParserInfo cutoutPathParserInfo) {
+ mDisplayWidth = cutoutPathParserInfo.mDisplayWidth;
+ mDisplayHeight = cutoutPathParserInfo.mDisplayHeight;
+ mDensity = cutoutPathParserInfo.mDensity;
+ mCutoutSpec = cutoutPathParserInfo.mCutoutSpec;
+ mRotation = cutoutPathParserInfo.mRotation;
+ mScale = cutoutPathParserInfo.mScale;
+ }
+
+ public int getDisplayWidth() {
+ return mDisplayWidth;
+ }
+
+ public int getDisplayHeight() {
+ return mDisplayHeight;
+ }
+
+ public float getDensity() {
+ return mDensity;
+ }
+
+ public @NonNull String getCutoutSpec() {
+ return mCutoutSpec;
+ }
+
+ public int getRotation() {
+ return mRotation;
+ }
+
+ public float getScale() {
+ return mScale;
+ }
+
+ private boolean hasCutout() {
+ return !mCutoutSpec.isEmpty();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ result = result * 48271 + Integer.hashCode(mDisplayWidth);
+ result = result * 48271 + Integer.hashCode(mDisplayHeight);
+ result = result * 48271 + Float.hashCode(mDensity);
+ result = result * 48271 + mCutoutSpec.hashCode();
+ result = result * 48271 + Integer.hashCode(mRotation);
+ result = result * 48271 + Float.hashCode(mScale);
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof CutoutPathParserInfo) {
+ CutoutPathParserInfo c = (CutoutPathParserInfo) o;
+ return mDisplayWidth == c.mDisplayWidth && mDisplayHeight == c.mDisplayHeight
+ && mDensity == c.mDensity && mCutoutSpec.equals(c.mCutoutSpec)
+ && mRotation == c.mRotation && mScale == c.mScale;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "CutoutPathParserInfo{displayWidth=" + mDisplayWidth
+ + " displayHeight=" + mDisplayHeight
+ + " density={" + mDensity + "}"
+ + " cutoutSpec={" + mCutoutSpec + "}"
+ + " rotation={" + mRotation + "}"
+ + " scale={" + mScale + "}"
+ + "}";
+ }
+ }
+
+ private final @NonNull CutoutPathParserInfo mCutoutPathParserInfo;
+
+ /**
* Creates a DisplayCutout instance.
*
* <p>Note that this is only useful for tests. For production code, developers should always
@@ -251,7 +363,8 @@
// TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
@Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom) {
- this(safeInsets.toRect(), Insets.NONE, boundLeft, boundTop, boundRight, boundBottom, true);
+ this(safeInsets.toRect(), Insets.NONE, boundLeft, boundTop, boundRight, boundBottom, null,
+ true);
}
/**
@@ -276,7 +389,7 @@
@Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
@NonNull Insets waterfallInsets) {
this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
- true);
+ null, true);
}
/**
@@ -294,7 +407,7 @@
// TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
@Deprecated
public DisplayCutout(@Nullable Rect safeInsets, @Nullable List<Rect> boundingRects) {
- this(safeInsets, Insets.NONE, extractBoundsFromList(safeInsets, boundingRects),
+ this(safeInsets, Insets.NONE, extractBoundsFromList(safeInsets, boundingRects), null,
true /* copyArguments */);
}
@@ -303,28 +416,42 @@
*
* @param safeInsets the insets from each edge which avoid the display cutout as returned by
* {@link #getSafeInsetTop()} etc.
+ * @param waterfallInsets the insets for the curved areas in waterfall display.
+ * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed,
+ * it's treated as an empty rectangle (0,0)-(0,0).
+ * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed,
+ * it's treated as an empty rectangle (0,0)-(0,0).
+ * @param boundRight the right bounding rect of the display cutout in pixels. If null is
+ * passed, it's treated as an empty rectangle (0,0)-(0,0).
+ * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is
+ * passed, it's treated as an empty rectangle (0,0)-(0,0).
+ * @param info the cutout path parser info.
* @param copyArguments if true, create a copy of the arguments. If false, the passed arguments
* are not copied and MUST remain unchanged forever.
*/
- private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft,
- Rect boundTop, Rect boundRight, Rect boundBottom, boolean copyArguments) {
+ private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft, Rect boundTop,
+ Rect boundRight, Rect boundBottom, CutoutPathParserInfo info,
+ boolean copyArguments) {
mSafeInsets = getCopyOrRef(safeInsets, copyArguments);
mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
mBounds = new Bounds(boundLeft, boundTop, boundRight, boundBottom, copyArguments);
+ mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info;
}
private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect[] bounds,
- boolean copyArguments) {
+ CutoutPathParserInfo info, boolean copyArguments) {
mSafeInsets = getCopyOrRef(safeInsets, copyArguments);
mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
mBounds = new Bounds(bounds, copyArguments);
+ mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info;
}
- private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds) {
+ private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds,
+ CutoutPathParserInfo info) {
mSafeInsets = safeInsets;
mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
mBounds = bounds;
-
+ mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info;
}
private static Rect getCopyOrRef(Rect r, boolean copyArguments) {
@@ -534,10 +661,70 @@
return mBounds.getRect(BOUNDS_POSITION_BOTTOM);
}
+ /**
+ * Returns a {@link Path} that contains the cutout paths of all sides on the display.
+ *
+ * To get a cutout path for one specific side, apps can intersect the {@link Path} with the
+ * {@link Rect} obtained from {@link #getBoundingRectLeft()}, {@link #getBoundingRectTop()},
+ * {@link #getBoundingRectRight()} or {@link #getBoundingRectBottom()}.
+ *
+ * @return a {@link Path} contains all the cutout paths based on display coordinate. Returns
+ * null if there is no cutout on the display.
+ */
+ public @Nullable Path getCutoutPath() {
+ if (!mCutoutPathParserInfo.hasCutout()) {
+ return null;
+ }
+ synchronized (CACHE_LOCK) {
+ if (mCutoutPathParserInfo.equals(sCachedCutoutPathParserInfo)) {
+ return sCachedCutoutPath;
+ }
+ }
+ final CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(
+ mCutoutPathParserInfo.getDensity(), mCutoutPathParserInfo.getDisplayWidth(),
+ mCutoutPathParserInfo.getDisplayHeight())
+ .parse(mCutoutPathParserInfo.getCutoutSpec());
+
+ final Path cutoutPath = cutoutSpec.getPath();
+ if (cutoutPath == null || cutoutPath.isEmpty()) {
+ return null;
+ }
+ final Matrix matrix = new Matrix();
+ if (mCutoutPathParserInfo.getRotation() != ROTATION_0) {
+ RotationUtils.transformPhysicalToLogicalCoordinates(
+ mCutoutPathParserInfo.getRotation(),
+ mCutoutPathParserInfo.getDisplayWidth(),
+ mCutoutPathParserInfo.getDisplayHeight(),
+ matrix
+ );
+ }
+ matrix.postScale(mCutoutPathParserInfo.getScale(), mCutoutPathParserInfo.getScale());
+ cutoutPath.transform(matrix);
+
+ synchronized (CACHE_LOCK) {
+ sCachedCutoutPathParserInfo = new CutoutPathParserInfo(mCutoutPathParserInfo);
+ sCachedCutoutPath = cutoutPath;
+ }
+ return cutoutPath;
+ }
+
+ /**
+ * @return the {@link CutoutPathParserInfo};
+ *
+ * @hide
+ */
+ public CutoutPathParserInfo getCutoutPathParserInfo() {
+ return mCutoutPathParserInfo;
+ }
+
@Override
public int hashCode() {
- return (mSafeInsets.hashCode() * 48271 + mBounds.hashCode()) * 48271
- + mWaterfallInsets.hashCode();
+ int result = 0;
+ result = 48271 * result + mSafeInsets.hashCode();
+ result = 48271 * result + mBounds.hashCode();
+ result = 48271 * result + mWaterfallInsets.hashCode();
+ result = 48271 * result + mCutoutPathParserInfo.hashCode();
+ return result;
}
@Override
@@ -548,7 +735,8 @@
if (o instanceof DisplayCutout) {
DisplayCutout c = (DisplayCutout) o;
return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds)
- && mWaterfallInsets.equals(c.mWaterfallInsets);
+ && mWaterfallInsets.equals(c.mWaterfallInsets)
+ && mCutoutPathParserInfo.equals(c.mCutoutPathParserInfo);
}
return false;
}
@@ -558,6 +746,7 @@
return "DisplayCutout{insets=" + mSafeInsets
+ " waterfall=" + mWaterfallInsets
+ " boundingRect={" + mBounds + "}"
+ + " cutoutPathParserInfo={" + mCutoutPathParserInfo + "}"
+ "}";
}
@@ -607,7 +796,7 @@
}
return new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds,
- false /* copyArguments */);
+ mCutoutPathParserInfo, false /* copyArguments */);
}
private Rect insetInsets(int insetLeft, int insetTop, int insetRight, int insetBottom,
@@ -638,7 +827,8 @@
* @hide
*/
public DisplayCutout replaceSafeInsets(Rect safeInsets) {
- return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds);
+ return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds,
+ mCutoutPathParserInfo);
}
private static int atLeastZero(int value) {
@@ -658,16 +848,18 @@
for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) {
bounds[i] = (pos == i) ? new Rect(left, top, right, bottom) : new Rect();
}
- return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, false /* copyArguments */);
+ return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null, false /* copyArguments */);
}
/**
- * Creates an instance from a bounding and waterfall insets.
+ * Creates an instance from bounds, waterfall insets and CutoutPathParserInfo.
*
* @hide
*/
- public static DisplayCutout fromBoundsAndWaterfall(Rect[] bounds, Insets waterfallInsets) {
- return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, false /* copyArguments */);
+ public static DisplayCutout constructDisplayCutout(Rect[] bounds, Insets waterfallInsets,
+ CutoutPathParserInfo info) {
+ return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, info,
+ false /* copyArguments */);
}
/**
@@ -676,7 +868,8 @@
* @hide
*/
public static DisplayCutout fromBounds(Rect[] bounds) {
- return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, false /* copyArguments */);
+ return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null /* cutoutPathParserInfo */,
+ false /* copyArguments */);
}
/**
@@ -686,10 +879,12 @@
*
* @hide
*/
- public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth, int displayHeight) {
- return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation),
+ public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth,
+ int displayHeight) {
+ return pathAndDisplayCutoutFromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout),
+ res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation),
displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
- loadWaterfallInset(res));
+ loadWaterfallInset(res)).second;
}
/**
@@ -699,7 +894,7 @@
*/
public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) {
return pathAndDisplayCutoutFromSpec(
- res.getString(R.string.config_mainBuiltInDisplayCutout),
+ res.getString(R.string.config_mainBuiltInDisplayCutout), null,
displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
loadWaterfallInset(res)).first;
}
@@ -710,14 +905,30 @@
* @hide
*/
@VisibleForTesting(visibility = PRIVATE)
- public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight,
- float density, Insets waterfallInsets) {
+ public static DisplayCutout fromSpec(String pathSpec, int displayWidth,
+ int displayHeight, float density, Insets waterfallInsets) {
return pathAndDisplayCutoutFromSpec(
- spec, displayWidth, displayHeight, density, waterfallInsets).second;
+ pathSpec, null, displayWidth, displayHeight, density, waterfallInsets)
+ .second;
}
- private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(String spec,
- int displayWidth, int displayHeight, float density, Insets waterfallInsets) {
+ /**
+ * Gets the cutout path and the corresponding DisplayCutout instance from the spec string.
+ *
+ * @param pathSpec the spec string read from config_mainBuiltInDisplayCutout.
+ * @param rectSpec the spec string read from config_mainBuiltInDisplayCutoutRectApproximation.
+ * @param displayWidth the display width.
+ * @param displayHeight the display height.
+ * @param density the display density.
+ * @param waterfallInsets the waterfall insets of the display.
+ * @return a Pair contains the cutout path and the corresponding DisplayCutout instance.
+ */
+ private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(
+ String pathSpec, String rectSpec, int displayWidth, int displayHeight, float density,
+ Insets waterfallInsets) {
+ // Always use the rect approximation spec to create the cutout if it's not null because
+ // transforming and sending a Region constructed from a path is very costly.
+ String spec = rectSpec != null ? rectSpec : pathSpec;
if (TextUtils.isEmpty(spec) && waterfallInsets.equals(Insets.NONE)) {
return NULL_PAIR;
}
@@ -750,9 +961,12 @@
Math.max(waterfallInsets.bottom, safeInset.bottom));
}
+ final CutoutPathParserInfo cutoutPathParserInfo = new CutoutPathParserInfo(displayWidth,
+ displayHeight, density, pathSpec.trim(), ROTATION_0, 1f /* scale */);
+
final DisplayCutout cutout = new DisplayCutout(
- safeInset, waterfallInsets, boundLeft, boundTop,
- boundRight, boundBottom, false /* copyArguments */);
+ safeInset, waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
+ cutoutPathParserInfo , false /* copyArguments */);
final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout);
synchronized (CACHE_LOCK) {
sCachedSpec = spec;
@@ -817,6 +1031,12 @@
out.writeTypedObject(cutout.mSafeInsets, flags);
out.writeTypedArray(cutout.mBounds.getRects(), flags);
out.writeTypedObject(cutout.mWaterfallInsets, flags);
+ out.writeInt(cutout.mCutoutPathParserInfo.getDisplayWidth());
+ out.writeInt(cutout.mCutoutPathParserInfo.getDisplayHeight());
+ out.writeFloat(cutout.mCutoutPathParserInfo.getDensity());
+ out.writeString(cutout.mCutoutPathParserInfo.getCutoutSpec());
+ out.writeInt(cutout.mCutoutPathParserInfo.getRotation());
+ out.writeFloat(cutout.mCutoutPathParserInfo.getScale());
}
}
@@ -860,9 +1080,17 @@
Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH];
in.readTypedArray(bounds, Rect.CREATOR);
Insets waterfallInsets = in.readTypedObject(Insets.CREATOR);
+ int displayWidth = in.readInt();
+ int displayHeight = in.readInt();
+ float density = in.readFloat();
+ String cutoutSpec = in.readString();
+ int rotation = in.readInt();
+ float scale = in.readFloat();
+ final CutoutPathParserInfo info = new CutoutPathParserInfo(
+ displayWidth, displayHeight, density, cutoutSpec, rotation, scale);
return new DisplayCutout(
- safeInsets, waterfallInsets, bounds, false /* copyArguments */);
+ safeInsets, waterfallInsets, bounds, info, false /* copyArguments */);
}
public DisplayCutout get() {
@@ -884,7 +1112,15 @@
bounds.scale(scale);
final Rect waterfallInsets = mInner.mWaterfallInsets.toRect();
waterfallInsets.scale(scale);
- mInner = new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds);
+ final CutoutPathParserInfo info = new CutoutPathParserInfo(
+ mInner.mCutoutPathParserInfo.getDisplayWidth(),
+ mInner.mCutoutPathParserInfo.getDisplayHeight(),
+ mInner.mCutoutPathParserInfo.getDensity(),
+ mInner.mCutoutPathParserInfo.getCutoutSpec(),
+ mInner.mCutoutPathParserInfo.getRotation(),
+ scale);
+
+ mInner = new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds, info);
}
@Override
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 106e392..0a1a231 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -414,6 +414,15 @@
*/
public static final int SECURE = 0x00000080;
+
+ /**
+ * Queue up BufferStateLayer buffers instead of dropping the oldest buffer when this flag is
+ * set. This blocks the client until all the buffers have been presented. If the buffers
+ * have presentation timestamps, then we may drop buffers.
+ * @hide
+ */
+ public static final int ENABLE_BACKPRESSURE = 0x00000100;
+
/**
* Surface creation flag: Creates a surface where color components are interpreted
* as "non pre-multiplied" by their alpha channel. Of course this flag is
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 0e878fc..4ef63ae 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -262,7 +262,7 @@
}
}
- binder::Status onScreenCaptureComplete(
+ binder::Status onScreenCaptureCompleted(
const gui::ScreenCaptureResults& captureResults) override {
JNIEnv* env = getenv();
if (captureResults.result != NO_ERROR || captureResults.buffer == nullptr) {
@@ -270,6 +270,7 @@
gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr);
return binder::Status::ok();
}
+ captureResults.fence->waitForever("");
jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer(
env, captureResults.buffer->toAHardwareBuffer());
const jint namedColorSpace =
diff --git a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
index 24f45a5..bffd1e4 100644
--- a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
+++ b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.fail;
import android.content.pm.PackageParser.SigningDetails;
+import android.util.ArraySet;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -35,6 +36,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Set;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SigningDetailsTest {
@@ -208,8 +211,8 @@
SigningDetails result1 = noLineageDetails.mergeLineageWith(lineageDetails);
SigningDetails result2 = lineageDetails.mergeLineageWith(noLineageDetails);
- assertTrue(result1 == lineageDetails);
- assertTrue(result2 == lineageDetails);
+ assertSigningDetailsContainsLineage(result1, FIRST_SIGNATURE, SECOND_SIGNATURE);
+ assertSigningDetailsContainsLineage(result2, FIRST_SIGNATURE, SECOND_SIGNATURE);
}
@Test
@@ -271,8 +274,10 @@
SigningDetails result1 = singleSignerDetails.mergeLineageWith(fullLineageDetails);
SigningDetails result2 = fullLineageDetails.mergeLineageWith(singleSignerDetails);
- assertTrue(result1 == fullLineageDetails);
- assertTrue(result2 == fullLineageDetails);
+ assertSigningDetailsContainsLineage(result1, FIRST_SIGNATURE, SECOND_SIGNATURE,
+ THIRD_SIGNATURE);
+ assertSigningDetailsContainsLineage(result2, FIRST_SIGNATURE, SECOND_SIGNATURE,
+ THIRD_SIGNATURE);
}
@Test
@@ -605,6 +610,213 @@
assertTrue(secondLineageDetails.hasCommonAncestor(firstLineageDetails));
}
+ @Test
+ public void hasCommonSignerWithCapabilities_singleMatchingSigner_returnsTrue()
+ throws Exception {
+ // The hasCommonSignerWithCapabilities method is intended to grant the specified
+ // capabilities to a requesting package that has a common signer in the lineage (or as the
+ // current signer) even if their signing identities have diverged. This test verifies if the
+ // two SigningDetails have the same single signer then the requested capability can be
+ // granted since the current signer always has all capabilities granted.
+ SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE);
+ SigningDetails secondSignerDetails = createSigningDetails(FIRST_SIGNATURE);
+
+ assertTrue(firstDetails.hasCommonSignerWithCapability(secondSignerDetails, PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_singleDifferentSigners_returnsFalse()
+ throws Exception {
+ // If each package is signed by a single different signer then the method should return
+ // false since there is no shared signer.
+ SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE);
+ SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE);
+
+ assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
+ assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_oneWithMultipleSigners_returnsFalse()
+ throws Exception {
+ // If one of the packages is signed with multiple signers and the other only a single signer
+ // this method should return false since all signers must match exactly for multiple signer
+ // cases.
+ SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ SigningDetails secondDetails = createSigningDetails(FIRST_SIGNATURE);
+
+ assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
+ assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_multipleMatchingSigners_returnsTrue()
+ throws Exception {
+ // if both packages are signed by the same multiple signers then this method should return
+ // true since the current signer is granted all capabilities.
+ SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE, FIRST_SIGNATURE);
+
+ assertTrue(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
+ assertTrue(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_singleSignerInLineage_returnsTrue()
+ throws Exception {
+ // if a single signer is in the lineage and that previous signer has the requested
+ // capability then this method should return true.
+ SigningDetails lineageDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE},
+ new int[]{DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
+ SigningDetails singleSignerDetails = createSigningDetails(FIRST_SIGNATURE);
+
+ assertTrue(lineageDetails.hasCommonSignerWithCapability(singleSignerDetails, PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_singleSignerInLineageWOCapability_returnsFalse()
+ throws Exception {
+ // If a single signer is in the lineage and that previous signer does not have the requested
+ // capability then this method should return false.
+ SigningDetails lineageDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE},
+ new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES});
+ SigningDetails singleSignerDetails = createSigningDetails(FIRST_SIGNATURE);
+
+ assertFalse(lineageDetails.hasCommonSignerWithCapability(singleSignerDetails, PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_singleSignerMatchesCurrentSigner_returnsTrue()
+ throws Exception {
+ // If a requesting app is signed by the same current signer as an app with a lineage the
+ // method should return true since the current signer is granted all capabilities.
+ SigningDetails lineageDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE},
+ new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES});
+ SigningDetails singleSignerDetails = createSigningDetails(SECOND_SIGNATURE);
+
+ assertTrue(lineageDetails.hasCommonSignerWithCapability(singleSignerDetails, PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_divergingSignersWithCommonSigner_returnsTrue()
+ throws Exception {
+ // This method is intended to allow granting a capability to another app that has a common
+ // signer in the lineage with the capability still granted; this test verifies when the
+ // current signers diverge but a common ancestor has the requested capability this method
+ // returns true.
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+ new int[]{DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, FOURTH_SIGNATURE);
+
+ assertTrue(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
+ PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_divergingSignersOneGrantsCapability_returnsTrue()
+ throws Exception {
+ // If apps have multiple common signers in the lineage with one denying the requested
+ // capability but the other granting it this method should return true.
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+ new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, FOURTH_SIGNATURE);
+
+ assertTrue(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
+ PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_divergingSignersNoneGrantCapability_returnsFalse()
+ throws Exception {
+ // If apps have multiple common signers in the lineage with all denying the requested
+ // capability this method should return false.
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+ new int[]{SHARED_USER_ID, AUTH, DEFAULT_CAPABILITIES});
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, FOURTH_SIGNATURE);
+
+ assertFalse(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
+ PERMISSION));
+ }
+
+ @Test
+ public void
+ hasCommonSignerWithCapabilities_divergingSignersNoneGrantsAllCapabilities_returnsTrue()
+ throws Exception {
+ // If an app has multiple common signers in the lineage, each granting one of the requested
+ // capabilities but neither granting all this method should return false since a single
+ // common ancestor must grant all requested capabilities.
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+ new int[]{SHARED_USER_ID, PERMISSION, DEFAULT_CAPABILITIES});
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, FOURTH_SIGNATURE);
+
+ assertFalse(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
+ PERMISSION | SHARED_USER_ID));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_currentSignerInLineageOfRequestingApp_returnsTrue()
+ throws Exception {
+ // If the current signer of an app is in the lineage of the requesting app then this method
+ // should return true since the current signer is granted all capabilities.
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ assertTrue(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
+ PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_currentSignerInLineageOfDeclaringApp_returnsTrue()
+ throws Exception {
+ // If the current signer of a requesting app with a lineage is in the lineage of the
+ // declaring app and that previous signature is granted the requested capability the method
+ // should return true.
+ SigningDetails declaringDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+ new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
+ SigningDetails requestingDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+
+ assertTrue(declaringDetails.hasCommonSignerWithCapability(requestingDetails, PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_oneSignerNullLineage_returns() throws Exception {
+ // While the pastSigningCertificates should only be null in the case of multiple current
+ // signers there are instances where this can be null with a single signer; verify that a
+ // null pastSigningCertificates array in either SigningDetails does not result in a
+ // NullPointerException.
+ SigningDetails firstDetails = createSigningDetails(true, FIRST_SIGNATURE);
+ SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE);
+
+ assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
+ assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
+ }
+
+ @Test
+ public void hasCommonSignerWithCapabilities_unknownSigner_returnsFalse() throws Exception {
+ // An unknown SigningDetails for either instance should immediately result in false being
+ // returned.
+ SigningDetails firstDetails = SigningDetails.UNKNOWN;
+ SigningDetails secondDetails = createSigningDetails(FIRST_SIGNATURE);
+
+ assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
+ assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
+ }
+
private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception {
int[] capabilities = new int[signers.length];
for (int i = 0; i < capabilities.length; i++) {
@@ -629,10 +841,34 @@
}
private SigningDetails createSigningDetails(String... signers) throws Exception {
+ return createSigningDetails(false, signers);
+ }
+
+ private SigningDetails createSigningDetails(boolean useNullPastSigners, String... signers)
+ throws Exception {
Signature[] currentSignatures = new Signature[signers.length];
for (int i = 0; i < signers.length; i++) {
currentSignatures[i] = new Signature(signers[i]);
}
- return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
+ // If there are multiple signers then the pastSigningCertificates should be set to null, but
+ // if there is only a single signer both the current signer and the past signers should be
+ // set to that one signer.
+ if (signers.length > 1) {
+ return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
+ }
+ return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, currentSignatures);
+ }
+
+ private void assertSigningDetailsContainsLineage(SigningDetails details,
+ String... pastSigners) {
+ // This method should only be invoked for results that contain a single signer.
+ assertEquals(1, details.signatures.length);
+ assertTrue(details.signatures[0].toCharsString().equalsIgnoreCase(
+ pastSigners[pastSigners.length - 1]));
+ Set<String> signatures = new ArraySet<>(pastSigners);
+ for (Signature pastSignature : details.pastSigningCertificates) {
+ assertTrue(signatures.remove(pastSignature.toCharsString()));
+ }
+ assertEquals(0, signatures.size());
}
}
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index d02c6d5..a5261ae 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -22,6 +22,8 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -30,6 +32,7 @@
import static org.junit.Assert.assertTrue;
import android.graphics.Insets;
+import android.graphics.Path;
import android.graphics.Rect;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
@@ -130,6 +133,8 @@
@Test
public void testHasCutout_noCutout() throws Exception {
assertTrue(NO_CUTOUT.isBoundsEmpty());
+ assertThat(NO_CUTOUT.getWaterfallInsets(), equalTo(Insets.NONE));
+ assertThat(NO_CUTOUT.getCutoutPath(), nullValue());
}
@Test
@@ -165,6 +170,59 @@
}
@Test
+ public void testGetCutoutPath() throws Exception {
+ final String cutoutSpecString = "L1,0 L1,1 L0,1 z";
+ final int displayWidth = 200;
+ final int displayHeight = 400;
+ final float density = 1f;
+ final DisplayCutout cutout = fromSpec(cutoutSpecString, displayWidth, displayHeight,
+ density, Insets.NONE);
+ assertThat(cutout.getCutoutPath(), notNullValue());
+ }
+
+ @Test
+ public void testGetCutoutPath_caches() throws Exception {
+ final String cutoutSpecString = "L1,0 L1,1 L0,1 z";
+ final int displayWidth = 200;
+ final int displayHeight = 400;
+ final float density = 1f;
+ final Path first = fromSpec(cutoutSpecString, displayWidth, displayHeight,
+ density, Insets.NONE).getCutoutPath();
+ final Path second = fromSpec(cutoutSpecString, displayWidth, displayHeight,
+ density, Insets.NONE).getCutoutPath();
+ assertThat(first, equalTo(second));
+ }
+
+ @Test
+ public void testGetCutoutPath_wontCacheIfCutoutPathParerInfoChanged() throws Exception {
+ final int displayWidth = 200;
+ final int displayHeight = 400;
+ final float density = 1f;
+ final Path first = fromSpec("L1,0 L1,1 L0,1 z", displayWidth, displayHeight,
+ density, Insets.NONE).getCutoutPath();
+ final Path second = fromSpec("L2,0 L2,2 L0,2 z", displayWidth, displayHeight,
+ density, Insets.NONE).getCutoutPath();
+ assertThat(first, not(equalTo(second)));
+ }
+
+ @Test
+ public void testGetCutoutPathParserInfo() throws Exception {
+ final String cutoutSpecString = "L1,0 L1,1 L0,1 z";
+ final int displayWidth = 200;
+ final int displayHeight = 400;
+ final float density = 1f;
+ final DisplayCutout cutout = fromSpec(cutoutSpecString, displayWidth, displayHeight,
+ density, Insets.NONE);
+ assertThat(displayWidth, equalTo(cutout.getCutoutPathParserInfo().getDisplayWidth()));
+ assertThat(displayHeight, equalTo(cutout.getCutoutPathParserInfo().getDisplayHeight()));
+ assertThat(density, equalTo(cutout.getCutoutPathParserInfo().getDensity()));
+ assertThat(cutoutSpecString.trim(),
+ equalTo(cutout.getCutoutPathParserInfo().getCutoutSpec()));
+ assertThat(0, equalTo(cutout.getCutoutPathParserInfo().getRotation()));
+ assertThat(1f, equalTo(cutout.getCutoutPathParserInfo().getScale()));
+ }
+
+ @Test
public void testHashCode() throws Exception {
assertEquals(mCutoutWithWaterfall.hashCode(), createCutoutWithWaterfall().hashCode());
assertNotEquals(mCutoutWithWaterfall.hashCode(), mCutoutNumbers.hashCode());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 1df2a4a..bb8a973 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -225,8 +225,9 @@
mTaskOrganizer.applyTransaction(wct);
// TODO(b/151449487): Only call callback once we enable synchronization
if (mListener != null) {
+ final int taskId = mTaskInfo.taskId;
mListenerExecutor.execute(() -> {
- mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated);
+ mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
});
}
}
@@ -256,8 +257,10 @@
}
if (mListener != null) {
+ final int taskId = taskInfo.taskId;
+ final ComponentName baseActivity = taskInfo.baseActivity;
mListenerExecutor.execute(() -> {
- mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity);
+ mListener.onTaskCreated(taskId, baseActivity);
});
}
}
@@ -267,8 +270,9 @@
if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
if (mListener != null) {
+ final int taskId = taskInfo.taskId;
mListenerExecutor.execute(() -> {
- mListener.onTaskRemovalStarted(taskInfo.taskId);
+ mListener.onTaskRemovalStarted(taskId);
});
}
mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
@@ -289,8 +293,9 @@
public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
if (mListener != null) {
+ final int taskId = taskInfo.taskId;
mListenerExecutor.execute(() -> {
- mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
+ mListener.onBackPressedOnTaskRoot(taskId);
});
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 3181dbf..58a4baf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -356,11 +356,11 @@
if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
return null;
}
- final Insets waterfallInsets =
- RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
if (rotation == ROTATION_0) {
return computeSafeInsets(cutout, displayWidth, displayHeight);
}
+ final Insets waterfallInsets =
+ RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
Rect[] cutoutRects = cutout.getBoundingRectsAll();
final Rect[] newBounds = new Rect[cutoutRects.length];
@@ -372,8 +372,12 @@
}
newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
}
+ final DisplayCutout.CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
+ final DisplayCutout.CutoutPathParserInfo newInfo = new DisplayCutout.CutoutPathParserInfo(
+ info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
+ info.getCutoutSpec(), rotation, info.getScale());
return computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets),
+ DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
rotated ? displayHeight : displayWidth,
rotated ? displayWidth : displayHeight);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index 4874d3c..a4cd3c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -47,6 +47,11 @@
}
@Override
+ public void removeAllCallbacks() {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ @Override
public void removeCallbacks(@NonNull Runnable r) {
mHandler.removeCallbacks(r);
}
@@ -55,9 +60,4 @@
public boolean hasCallback(Runnable r) {
return mHandler.hasCallbacks(r);
}
-
- @Override
- public Looper getLooper() {
- return mHandler.getLooper();
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index 1149cce..b736fb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -73,6 +73,11 @@
void executeDelayed(Runnable runnable, long delayMillis);
/**
+ * Removes all pending callbacks.
+ */
+ void removeAllCallbacks();
+
+ /**
* See {@link android.os.Handler#removeCallbacks}.
*/
void removeCallbacks(Runnable runnable);
@@ -81,9 +86,4 @@
* See {@link android.os.Handler#hasCallbacks(Runnable)}.
*/
boolean hasCallback(Runnable runnable);
-
- /**
- * Returns the looper that this executor is running on.
- */
- Looper getLooper();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index a74f476..37a91d0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -56,7 +56,7 @@
private final float[] mColor;
private final float mAlpha;
private final Rect mRect;
- private final Handler mHandler;
+ private final Executor mMainExecutor;
private final Point mDisplaySize = new Point();
private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@@ -76,13 +76,13 @@
@Override
public void onOneHandedAnimationStart(
OneHandedAnimationController.OneHandedTransitionAnimator animator) {
- mHandler.post(() -> showBackgroundPanelLayer());
+ mMainExecutor.execute(() -> showBackgroundPanelLayer());
}
};
@Override
public void onStopFinished(Rect bounds) {
- mHandler.post(() -> removeBackgroundPanelLayer());
+ mMainExecutor.execute(() -> removeBackgroundPanelLayer());
}
public OneHandedBackgroundPanelOrganizer(Context context, DisplayController displayController,
@@ -94,7 +94,7 @@
mColor = new float[]{defaultRGB, defaultRGB, defaultRGB};
mAlpha = res.getFloat(R.dimen.config_one_handed_background_alpha);
mRect = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
- mHandler = new Handler();
+ mMainExecutor = executor;
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
index 1ed121f..49b7e05 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
@@ -221,8 +221,14 @@
displaySize.y);
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"onehanded-gesture-offset", DEFAULT_DISPLAY);
- mInputEventReceiver = new EventReceiver(
- mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
+ try {
+ mMainExecutor.executeBlocking(() -> {
+ mInputEventReceiver = new EventReceiver(
+ mInputMonitor.getInputChannel(), Looper.myLooper());
+ });
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to create input event receiver", e);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
index 60709be..c7a49ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
@@ -132,8 +132,14 @@
if (mIsEnabled) {
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"onehanded-touch", DEFAULT_DISPLAY);
- mInputEventReceiver = new EventReceiver(
- mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
+ try {
+ mMainExecutor.executeBlocking(() -> {
+ mInputEventReceiver = new EventReceiver(
+ mInputMonitor.getInputChannel(), Looper.myLooper());
+ });
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to create input event receiver", e);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 7a634c3..6e3a20d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -147,7 +147,7 @@
// Choreographer.getSfInstance() must be called on the thread that the input event
// receiver should be receiving events
mInputEventReceiver = new InputEventReceiver(inputChannel,
- mMainExecutor.getLooper(), Choreographer.getSfInstance());
+ Looper.myLooper(), Choreographer.getSfInstance());
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 41cc59d..8fb358a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -212,8 +212,14 @@
// Register input event receiver
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"pip-resize", mDisplayId);
- mInputEventReceiver = new PipResizeInputEventReceiver(
- mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
+ try {
+ mMainExecutor.executeBlocking(() -> {
+ mInputEventReceiver = new PipResizeInputEventReceiver(
+ mInputMonitor.getInputChannel(), Looper.myLooper());
+ });
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to create input event receiver", e);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index c9f5ae2..2b8b53c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -95,7 +95,6 @@
private int mDeferResizeToNormalBoundsUntilRotation = -1;
private int mDisplayRotation;
- private final Handler mHandler = new Handler();
private final PipAccessibilityInteractionConnection mConnection;
// Behaviour states
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index 5f5c30b..bf84a6e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -40,6 +40,11 @@
}
@Override
+ public void removeAllCallbacks() {
+ mRunnables.clear();
+ }
+
+ @Override
public void removeCallbacks(Runnable r) {
mRunnables.remove(r);
}
@@ -49,11 +54,6 @@
return mRunnables.contains(r);
}
- @Override
- public Looper getLooper() {
- return null;
- }
-
public void flushAll() {
for (Runnable r : mRunnables) {
r.run();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
index 9219f15..bbe8891 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
@@ -33,6 +33,7 @@
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import org.junit.Before;
@@ -48,7 +49,7 @@
@RunWith(AndroidTestingRunner.class)
public class OneHandedTimeoutHandlerTest extends OneHandedTestCase {
private OneHandedTimeoutHandler mTimeoutHandler;
- private ShellExecutor mMainExecutor;
+ private TestShellExecutor mMainExecutor;
@Before
public void setUp() throws Exception {
@@ -104,34 +105,4 @@
mTimeoutHandler.resetTimer();
assertTrue(mTimeoutHandler.hasScheduledTimeout());
}
-
- private class TestShellExecutor implements ShellExecutor {
- private ArrayList<Runnable> mExecuted = new ArrayList<>();
- private ArrayList<Runnable> mDelayed = new ArrayList<>();
-
- @Override
- public void execute(Runnable runnable) {
- mExecuted.add(runnable);
- }
-
- @Override
- public void executeDelayed(Runnable r, long delayMillis) {
- mDelayed.add(r);
- }
-
- @Override
- public void removeCallbacks(Runnable r) {
- mDelayed.remove(r);
- }
-
- @Override
- public boolean hasCallback(Runnable r) {
- return mDelayed.contains(r);
- }
-
- @Override
- public Looper getLooper() {
- return Looper.myLooper();
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 93a8df4..cd3d6a8 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -26,8 +26,7 @@
private String mPackageName;
private long mTimeStarted;
private StringBuilder mState;
- // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO
- private boolean mSilenced;
+ private boolean mIsDisabled;
public AppOpItem(int code, int uid, String packageName, long timeStarted) {
this.mCode = code;
@@ -58,16 +57,16 @@
return mTimeStarted;
}
- public void setSilenced(boolean silenced) {
- mSilenced = silenced;
+ public void setDisabled(boolean misDisabled) {
+ this.mIsDisabled = misDisabled;
}
- public boolean isSilenced() {
- return mSilenced;
+ public boolean isDisabled() {
+ return mIsDisabled;
}
@Override
public String toString() {
- return mState.append(mSilenced).append(")").toString();
+ return mState.append(mIsDisabled).append(")").toString();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 1036c99..d8ca639 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.appops;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
import android.Manifest;
@@ -45,6 +47,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.util.Assert;
import java.io.FileDescriptor;
@@ -64,7 +67,8 @@
@SysUISingleton
public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController,
AppOpsManager.OnOpActiveChangedInternalListener,
- AppOpsManager.OnOpNotedListener, Dumpable {
+ AppOpsManager.OnOpNotedListener, IndividualSensorPrivacyController.Callback,
+ Dumpable {
// This is the minimum time that we will keep AppOps that are noted on record. If multiple
// occurrences of the same (op, package, uid) happen in a shorter interval, they will not be
@@ -77,8 +81,8 @@
private final AppOpsManager mAppOps;
private final AudioManager mAudioManager;
private final LocationManager mLocationManager;
- // TODO ntmyren: remove t
private final PackageManager mPackageManager;
+ private final IndividualSensorPrivacyController mSensorPrivacyController;
// mLocationProviderPackages are cached and updated only occasionally
private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000;
@@ -91,6 +95,7 @@
private final PermissionFlagsCache mFlagsCache;
private boolean mListening;
private boolean mMicMuted;
+ private boolean mCameraDisabled;
@GuardedBy("mActiveItems")
private final List<AppOpItem> mActiveItems = new ArrayList<>();
@@ -118,6 +123,7 @@
DumpManager dumpManager,
PermissionFlagsCache cache,
AudioManager audioManager,
+ IndividualSensorPrivacyController sensorPrivacyController,
BroadcastDispatcher dispatcher
) {
mDispatcher = dispatcher;
@@ -129,7 +135,10 @@
mCallbacksByCode.put(OPS[i], new ArraySet<>());
}
mAudioManager = audioManager;
- mMicMuted = audioManager.isMicrophoneMute();
+ mSensorPrivacyController = sensorPrivacyController;
+ mMicMuted = audioManager.isMicrophoneMute()
+ || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
+ mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA);
mLocationManager = context.getSystemService(LocationManager.class);
mPackageManager = context.getPackageManager();
dumpManager.registerDumpable(TAG, this);
@@ -147,6 +156,12 @@
mAppOps.startWatchingActive(OPS, this);
mAppOps.startWatchingNoted(OPS, this);
mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
+ mSensorPrivacyController.addCallback(this);
+
+ mMicMuted = mAudioManager.isMicrophoneMute()
+ || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
+ mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA);
+
mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
mAudioManager.getActiveRecordingConfigurations()));
mDispatcher.registerReceiverWithHandler(this,
@@ -156,6 +171,7 @@
mAppOps.stopWatchingActive(this);
mAppOps.stopWatchingNoted(this);
mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+ mSensorPrivacyController.removeCallback(this);
mBGHandler.removeCallbacksAndMessages(null); // null removes all
mDispatcher.unregisterReceiver(this);
@@ -235,11 +251,13 @@
if (item == null && active) {
item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
if (code == AppOpsManager.OP_RECORD_AUDIO) {
- item.setSilenced(isAnyRecordingPausedLocked(uid));
+ item.setDisabled(isAnyRecordingPausedLocked(uid));
+ } else if (code == AppOpsManager.OP_CAMERA) {
+ item.setDisabled(mCameraDisabled);
}
mActiveItems.add(item);
if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
- return !item.isSilenced();
+ return !item.isDisabled();
} else if (item != null && !active) {
mActiveItems.remove(item);
if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
@@ -409,7 +427,7 @@
AppOpItem item = mActiveItems.get(i);
if ((userId == UserHandle.USER_ALL
|| UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item) && !item.isSilenced()) {
+ && isUserVisible(item) && !item.isDisabled()) {
list.add(item);
}
}
@@ -512,22 +530,27 @@
return false;
}
- private void updateRecordingPausedStatus() {
+ private void updateSensorDisabledStatus() {
synchronized (mActiveItems) {
int size = mActiveItems.size();
for (int i = 0; i < size; i++) {
AppOpItem item = mActiveItems.get(i);
+
+ boolean paused = false;
if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
- boolean paused = isAnyRecordingPausedLocked(item.getUid());
- if (item.isSilenced() != paused) {
- item.setSilenced(paused);
- notifySuscribers(
- item.getCode(),
- item.getUid(),
- item.getPackageName(),
- !item.isSilenced()
- );
- }
+ paused = isAnyRecordingPausedLocked(item.getUid());
+ } else if (item.getCode() == AppOpsManager.OP_CAMERA) {
+ paused = mCameraDisabled;
+ }
+
+ if (item.isDisabled() != paused) {
+ item.setDisabled(paused);
+ notifySuscribers(
+ item.getCode(),
+ item.getUid(),
+ item.getPackageName(),
+ !item.isDisabled()
+ );
}
}
}
@@ -552,14 +575,27 @@
recordings.add(recording);
}
}
- updateRecordingPausedStatus();
+ updateSensorDisabledStatus();
}
};
@Override
public void onReceive(Context context, Intent intent) {
- mMicMuted = mAudioManager.isMicrophoneMute();
- updateRecordingPausedStatus();
+ mMicMuted = mAudioManager.isMicrophoneMute()
+ || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
+ updateSensorDisabledStatus();
+ }
+
+ @Override
+ public void onSensorBlockedChanged(int sensor, boolean blocked) {
+ mBGHandler.post(() -> {
+ if (sensor == INDIVIDUAL_SENSOR_CAMERA) {
+ mCameraDisabled = blocked;
+ } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) {
+ mMicMuted = mAudioManager.isMicrophoneMute() || blocked;
+ }
+ updateSensorDisabledStatus();
+ });
}
protected class H extends Handler {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index c2c6790..9be3566 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -23,7 +23,6 @@
import android.net.Uri;
import android.os.UserHandle;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -59,7 +58,6 @@
private final Executor mBgExecutor;
private final ImageExporter mImageExporter;
private final ImageTileSet mImageTileSet;
- private final LayoutInflater mLayoutInflater;
private ZonedDateTime mCaptureTime;
private UUID mRequestId;
@@ -81,7 +79,6 @@
mBgExecutor = bgExecutor;
mImageExporter = exporter;
mImageTileSet = new ImageTileSet();
- mLayoutInflater = mContext.getSystemService(LayoutInflater.class);
}
/**
@@ -114,7 +111,7 @@
mEdit.setOnClickListener(this::onClicked);
mShare.setOnClickListener(this::onClicked);
- mPreview.setImageDrawable(mImageTileSet.getDrawable());
+ //mPreview.setImageDrawable(mImageTileSet.getDrawable());
mConnection.start(this::startCapture);
}
@@ -242,6 +239,7 @@
if (mImageTileSet.isEmpty()) {
session.end(mCallback::onFinish);
} else {
+ mPreview.setImageDrawable(mImageTileSet.getDrawable());
mExportFuture = mImageExporter.export(
mBgExecutor, mRequestId, mImageTileSet.toBitmap(), mCaptureTime);
// The user chose an action already, link it to the result
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index 231fe08..32d15ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.policy;
-import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA;
-import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.IndividualSensor;
@@ -30,7 +30,8 @@
public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController {
- private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE};
+ private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA,
+ INDIVIDUAL_SENSOR_MICROPHONE};
private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
private final SparseBooleanArray mState = new SparseBooleanArray();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 02143a7..bc322f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.appops;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
+
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
@@ -49,6 +52,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import org.junit.Before;
import org.junit.Test;
@@ -81,6 +85,8 @@
private PermissionFlagsCache mFlagsCache;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private IndividualSensorPrivacyController mSensorPrivacyController;
@Mock(stubOnly = true)
private AudioManager mAudioManager;
@Mock()
@@ -118,12 +124,18 @@
when(mAudioManager.getActiveRecordingConfigurations())
.thenReturn(List.of(mPausedMockRecording));
+ when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA))
+ .thenReturn(false);
+ when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA))
+ .thenReturn(false);
+
mController = new AppOpsControllerImpl(
mContext,
mTestableLooper.getLooper(),
mDumpManager,
mFlagsCache,
mAudioManager,
+ mSensorPrivacyController,
mDispatcher
);
}
@@ -133,6 +145,7 @@
mController.setListening(true);
verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any());
+ verify(mSensorPrivacyController, times(1)).addCallback(mController);
}
@Test
@@ -140,6 +153,7 @@
mController.setListening(false);
verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
verify(mDispatcher, times(1)).unregisterReceiver(mController);
+ verify(mSensorPrivacyController, times(1)).removeCallback(mController);
}
@Test
@@ -476,6 +490,71 @@
AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
}
+ @Test
+ public void testAudioFilteredWhenMicDisabled() {
+ mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
+ mCallback);
+ mTestableLooper.processAllMessages();
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+ List<AppOpItem> list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
+ assertFalse(list.get(0).isDisabled());
+
+ // Add a camera op, and disable the microphone. The camera op should be the only op returned
+ mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, true);
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+ list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+
+
+ // Re enable the microphone, and verify the op returns
+ mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, false);
+ mTestableLooper.processAllMessages();
+
+ list = mController.getActiveAppOps();
+ assertEquals(2, list.size());
+ int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0;
+ assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(micIdx).getCode());
+ }
+
+ @Test
+ public void testCameraFilteredWhenCameraDisabled() {
+ mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
+ mCallback);
+ mTestableLooper.processAllMessages();
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+ List<AppOpItem> list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+ assertFalse(list.get(0).isDisabled());
+
+ // Add an audio op, and disable the camera. The audio op should be the only op returned
+ mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, true);
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+ list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
+
+ // Re enable the camera, and verify the op returns
+ mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, false);
+ mTestableLooper.processAllMessages();
+
+ list = mController.getActiveAppOps();
+ assertEquals(2, list.size());
+ int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 0 : 1;
+ assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode());
+ }
+
private class TestHandler extends AppOpsControllerImpl.H {
TestHandler(Looper looper) {
mController.super(looper);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d9ee9a3..13dc0b9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -710,9 +710,9 @@
}
}
- private void initialize() {
+ private void initialize(int displayState) {
mPowerState = new DisplayPowerState(mBlanker,
- mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId);
+ mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId, displayState);
if (mColorFadeEnabled) {
mColorFadeOnAnimator = ObjectAnimator.ofFloat(
@@ -812,11 +812,6 @@
mustNotify = !mDisplayReadyLocked;
}
- // Initialize things the first time the power state is changed.
- if (mustInitialize) {
- initialize();
- }
-
// Compute the basic display state using the policy.
// We might override this below based on other factors.
// Initialise brightness as invalid.
@@ -850,6 +845,11 @@
}
assert(state != Display.STATE_UNKNOWN);
+ // Initialize things the first time the power state is changed.
+ if (mustInitialize) {
+ initialize(state);
+ }
+
// Apply the proximity sensor.
if (mProximitySensor != null) {
if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 54f30a9..173adce 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -72,7 +72,8 @@
private Runnable mCleanListener;
- public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade, int displayId) {
+ DisplayPowerState(
+ DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState) {
mHandler = new Handler(true /*async*/);
mChoreographer = Choreographer.getInstance();
mBlanker = blanker;
@@ -81,14 +82,14 @@
mPhotonicModulator.start();
mDisplayId = displayId;
- // At boot time, we know that the screen is on and the electron beam
- // animation is not playing. We don't know the screen's brightness though,
+ // At boot time, we don't know the screen's brightness,
// so prepare to set it to a known state when the state is next applied.
- // Although we set the brightness to full on here, the display power controller
+ // Although we set the brightness here, the display power controller
// will reset the brightness to a new level immediately before the changes
// actually have a chance to be applied.
- mScreenState = Display.STATE_ON;
- mScreenBrightness = PowerManager.BRIGHTNESS_MAX;
+ mScreenState = displayState;
+ mScreenBrightness = (displayState != Display.STATE_OFF) ? PowerManager.BRIGHTNESS_MAX
+ : PowerManager.BRIGHTNESS_OFF_FLOAT;
scheduleScreenUpdate();
mColorFadePrepared = false;
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index b5b93d6..142f64f 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -74,7 +74,6 @@
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
@@ -1297,10 +1296,7 @@
return null;
}
- long currentNanos = SystemClock.elapsedRealtimeNanos();
- long deltaMs = NANOSECONDS.toMillis(
- location.getElapsedRealtimeAgeNanos(currentNanos));
- return new LocationTime(location.getTime() + deltaMs, currentNanos);
+ return new LocationTime(location.getTime(), location.getElapsedRealtimeNanos());
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c27e670..d2fc5b4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2153,6 +2153,10 @@
void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
boolean requireFullPermission, boolean checkShell,
boolean requirePermissionWhenSameUser, String message);
+ SigningDetails getSigningDetails(@NonNull String packageName);
+ SigningDetails getSigningDetails(int uid);
+ boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId);
+ boolean filterAppAccess(String packageName, int callingUid, int userId);
}
/**
@@ -4578,6 +4582,40 @@
throw new SecurityException(errorMessage);
}
+ public SigningDetails getSigningDetails(@NonNull String packageName) {
+ AndroidPackage p = mPackages.get(packageName);
+ if (p == null) {
+ return null;
+ }
+ return p.getSigningDetails();
+ }
+
+ public SigningDetails getSigningDetails(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ final Object obj = mSettings.getSettingLPr(appId);
+ if (obj != null) {
+ if (obj instanceof SharedUserSetting) {
+ return ((SharedUserSetting) obj).signatures.mSigningDetails;
+ } else if (obj instanceof PackageSetting) {
+ final PackageSetting ps = (PackageSetting) obj;
+ return ps.signatures.mSigningDetails;
+ }
+ }
+ return SigningDetails.UNKNOWN;
+ }
+
+ public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
+ PackageSetting ps = getPackageSetting(pkg.getPackageName());
+ return shouldFilterApplicationLocked(ps, callingUid,
+ userId);
+ }
+
+ public boolean filterAppAccess(String packageName, int callingUid, int userId) {
+ PackageSetting ps = getPackageSetting(packageName);
+ return shouldFilterApplicationLocked(ps, callingUid,
+ userId);
+ }
+
}
/**
@@ -4728,6 +4766,26 @@
return super.getPackageUidInternal(packageName, flags, userId, callingUid);
}
}
+ public SigningDetails getSigningDetails(@NonNull String packageName) {
+ synchronized (mLock) {
+ return super.getSigningDetails(packageName);
+ }
+ }
+ public SigningDetails getSigningDetails(int uid) {
+ synchronized (mLock) {
+ return super.getSigningDetails(uid);
+ }
+ }
+ public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
+ synchronized (mLock) {
+ return super.filterAppAccess(pkg, callingUid, userId);
+ }
+ }
+ public boolean filterAppAccess(String packageName, int callingUid, int userId) {
+ synchronized (mLock) {
+ return super.filterAppAccess(packageName, callingUid, userId);
+ }
+ }
}
@@ -26560,6 +26618,22 @@
return snapshotComputer().getPackage(uid);
}
+ private SigningDetails getSigningDetails(@NonNull String packageName) {
+ return snapshotComputer().getSigningDetails(packageName);
+ }
+
+ private SigningDetails getSigningDetails(int uid) {
+ return snapshotComputer().getSigningDetails(uid);
+ }
+
+ private boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
+ return snapshotComputer().filterAppAccess(pkg, callingUid, userId);
+ }
+
+ private boolean filterAppAccess(String packageName, int callingUid, int userId) {
+ return snapshotComputer().filterAppAccess(packageName, callingUid, userId);
+ }
+
private class PackageManagerInternalImpl extends PackageManagerInternal {
@Override
public List<ApplicationInfo> getInstalledApplications(int flags, int userId,
@@ -26615,29 +26689,11 @@
}
private SigningDetails getSigningDetails(@NonNull String packageName) {
- synchronized (mLock) {
- AndroidPackage p = mPackages.get(packageName);
- if (p == null) {
- return null;
- }
- return p.getSigningDetails();
- }
+ return PackageManagerService.this.getSigningDetails(packageName);
}
private SigningDetails getSigningDetails(int uid) {
- synchronized (mLock) {
- final int appId = UserHandle.getAppId(uid);
- final Object obj = mSettings.getSettingLPr(appId);
- if (obj != null) {
- if (obj instanceof SharedUserSetting) {
- return ((SharedUserSetting) obj).signatures.mSigningDetails;
- } else if (obj instanceof PackageSetting) {
- final PackageSetting ps = (PackageSetting) obj;
- return ps.signatures.mSigningDetails;
- }
- }
- return SigningDetails.UNKNOWN;
- }
+ return PackageManagerService.this.getSigningDetails(uid);
}
@Override
@@ -26652,20 +26708,12 @@
@Override
public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
- synchronized (mLock) {
- PackageSetting ps = getPackageSetting(pkg.getPackageName());
- return PackageManagerService.this.shouldFilterApplicationLocked(ps, callingUid,
- userId);
- }
+ return PackageManagerService.this.filterAppAccess(pkg, callingUid, userId);
}
@Override
public boolean filterAppAccess(String packageName, int callingUid, int userId) {
- synchronized (mLock) {
- PackageSetting ps = getPackageSetting(packageName);
- return PackageManagerService.this.shouldFilterApplicationLocked(ps, callingUid,
- userId);
- }
+ return PackageManagerService.this.filterAppAccess(packageName, callingUid, userId);
}
@Override
@@ -28304,6 +28352,13 @@
}
continue;
}
+ if (ps.appId < Process.FIRST_APPLICATION_UID) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: package is system, appId=" + ps.appId);
+ }
+ continue;
+ }
+
final AndroidPackage pkg = ps.getPkg();
if (pkg.getLongVersionCode() < perPackage.versionCodes.minVersionCode
|| pkg.getLongVersionCode() > perPackage.versionCodes.maxVersionCode) {
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 8c31d88..aff87111 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -3356,11 +3356,12 @@
// - or its signing certificate was rotated from the source package's certificate
// - or its signing certificate is a previous signing certificate of the defining
// package, and the defining package still trusts the old certificate for permissions
+ // - or it shares a common signing certificate in its lineage with the defining package,
+ // and the defining package still trusts the old certificate for permissions
// - or it shares the above relationships with the system package
final PackageParser.SigningDetails sourceSigningDetails =
getSourcePackageSigningDetails(bp);
- return pkg.getSigningDetails().hasAncestorOrSelf(sourceSigningDetails)
- || sourceSigningDetails.checkCapability(
+ return sourceSigningDetails.hasCommonSignerWithCapability(
pkg.getSigningDetails(),
PackageParser.SigningDetails.CertCapabilities.PERMISSION)
|| pkg.getSigningDetails().hasAncestorOrSelf(systemPackage.getSigningDetails())
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c073b43..89e7986 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3491,15 +3491,27 @@
/** {@inheritDoc} */
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
+ final int keyCode = event.getKeyCode();
+ final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+ boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
+ || event.isWakeKey();
+
if (!mSystemBooted) {
// If we have not yet booted, don't let key events do anything.
+ // Exception: Wake and power key events are forwarded to PowerManager to allow it to
+ // wake from quiescent mode during boot.
+ if (down && (keyCode == KeyEvent.KEYCODE_POWER
+ || keyCode == KeyEvent.KEYCODE_TV_POWER)) {
+ wakeUpFromPowerKey(event.getDownTime());
+ } else if (down && (isWakeKey || keyCode == KeyEvent.KEYCODE_WAKEUP)
+ && isWakeKeyWhenScreenOff(keyCode)) {
+ wakeUpFromWakeKey(event);
+ }
return 0;
}
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
- final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
- final int keyCode = event.getKeyCode();
final int displayId = event.getDisplayId();
final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0;
@@ -3518,8 +3530,6 @@
// Basic policy based on interactive state.
int result;
- boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
- || event.isWakeKey();
if (interactive || (isInjected && !isWakeKey)) {
// When the device is interactive or the key is injected pass the
// key to the application.
@@ -4740,7 +4750,7 @@
}
startedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
finishedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
- screenTurningOn(DEFAULT_DISPLAY, null);
+ screenTurningOn(DEFAULT_DISPLAY, mDefaultDisplayPolicy.getScreenOnListener());
screenTurnedOn(DEFAULT_DISPLAY);
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 54d0512..db4b6d0 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1036,12 +1036,12 @@
userActivityNoUpdateLocked(
now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
+ updatePowerStateLocked();
if (sQuiescent) {
goToSleepNoUpdateLocked(mClock.uptimeMillis(),
PowerManager.GO_TO_SLEEP_REASON_QUIESCENT,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
}
- updatePowerStateLocked();
}
}
}
@@ -1679,8 +1679,15 @@
Slog.d(TAG, "wakeUpNoUpdateLocked: eventTime=" + eventTime + ", uid=" + reasonUid);
}
- if (eventTime < mLastSleepTime || getWakefulnessLocked() == WAKEFULNESS_AWAKE
- || mForceSuspendActive || !mSystemReady) {
+ if (eventTime < mLastSleepTime || mForceSuspendActive || !mSystemReady) {
+ return false;
+ }
+
+ if (getWakefulnessLocked() == WAKEFULNESS_AWAKE) {
+ if (!mBootCompleted && sQuiescent) {
+ mDirty |= DIRTY_QUIESCENT;
+ return true;
+ }
return false;
}
@@ -2821,7 +2828,7 @@
*
* This function recalculates the display power state each time.
*
- * @return True if the display became ready.
+ * @return true if the display became ready.
*/
private boolean updateDisplayPowerStateLocked(int dirty) {
final boolean oldDisplayReady = mDisplayReady;
@@ -2830,7 +2837,11 @@
| DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED |
DIRTY_QUIESCENT)) != 0) {
if ((dirty & DIRTY_QUIESCENT) != 0) {
- sQuiescent = false;
+ if (mDisplayReady) {
+ sQuiescent = false;
+ } else {
+ mDirty |= DIRTY_QUIESCENT;
+ }
}
final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
@@ -5605,7 +5616,7 @@
* ignore the proximity sensor. We don't turn off the proximity sensor because
* we still want it to be reenabled if it's state changes.
*
- * @return True if the proximity sensor was successfully ignored and we should
+ * @return true if the proximity sensor was successfully ignored and we should
* consume the key event.
*/
private boolean interceptPowerKeyDownInternal(KeyEvent event) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f293fc3..0aaa1a1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -190,6 +190,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayCutout;
+import android.view.DisplayCutout.CutoutPathParserInfo;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IDisplayWindowInsetsController;
@@ -1934,18 +1935,22 @@
if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
return WmDisplayCutout.NO_CUTOUT;
}
- final Insets waterfallInsets =
- RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
if (rotation == ROTATION_0) {
return WmDisplayCutout.computeSafeInsets(
cutout, mInitialDisplayWidth, mInitialDisplayHeight);
}
+ final Insets waterfallInsets =
+ RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final Rect[] newBounds = mRotationUtil.getRotatedBounds(
cutout.getBoundingRectsAll(),
rotation, mInitialDisplayWidth, mInitialDisplayHeight);
+ final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
+ final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
+ info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
+ info.getCutoutSpec(), rotation, info.getScale());
return WmDisplayCutout.computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets),
+ DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
rotated ? mInitialDisplayHeight : mInitialDisplayWidth,
rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
}
@@ -4102,8 +4107,15 @@
* Callbacks when the given type of {@link WindowContainer} animation finished running in the
* hierarchy.
*/
- void onWindowAnimationFinished(int type) {
+ void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) {
if (type == ANIMATION_TYPE_APP_TRANSITION || type == ANIMATION_TYPE_RECENTS) {
+ // Unfreeze the insets state of the frozen target when the animation finished if exists.
+ final Task task = wc.asTask();
+ if (task != null) {
+ task.forAllWindows(w -> {
+ w.clearFrozenInsetsState();
+ }, true /* traverseTopToBottom */);
+ }
removeImeSurfaceImmediately();
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 398049f..267f677 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -74,7 +74,7 @@
private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>();
private final Consumer<WindowState> mDispatchInsetsChanged = w -> {
- if (w.isVisible()) {
+ if (w.isReadyToDispatchInsetsState()) {
w.notifyInsetsChanged();
}
};
@@ -117,7 +117,8 @@
final @InternalInsetsType int type = provider != null
? provider.getSource().getType() : ITYPE_INVALID;
return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(),
- isAboveIme(target));
+ isAboveIme(target),
+ target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() : mState);
}
InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
@@ -132,7 +133,7 @@
final @WindowingMode int windowingMode = token != null
? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
- return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token));
+ return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token), mState);
}
private boolean isAboveIme(WindowContainer target) {
@@ -180,9 +181,8 @@
* @see #getInsetsForWindowMetrics
*/
private InsetsState getInsetsForTarget(@InternalInsetsType int type,
- @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme) {
- InsetsState state = mState;
-
+ @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme,
+ @NonNull InsetsState state) {
if (type != ITYPE_INVALID) {
state = new InsetsState(state);
state.removeSource(type);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 03fca11..dd4ee877 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2684,6 +2684,14 @@
@Nullable ArrayList<WindowContainer> sources) {
final Task task = asTask();
if (task != null && !enter && !task.isHomeOrRecentsRootTask()) {
+ if (AppTransition.isClosingTransitOld(transit)) {
+ // Freezes the insets state when the window is in app exiting transition, to
+ // ensure the exiting window won't receive unexpected insets changes from the
+ // next window.
+ task.forAllWindows(w -> {
+ w.freezeInsetsState();
+ }, true /* traverseTopToBottom */);
+ }
mDisplayContent.showImeScreenshot();
}
final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
@@ -2831,7 +2839,7 @@
}
mSurfaceAnimationSources.clear();
if (mDisplayContent != null) {
- mDisplayContent.onWindowAnimationFinished(type);
+ mDisplayContent.onWindowAnimationFinished(this, type);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9f3188b..9a7823e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -713,6 +713,12 @@
private @Nullable InsetsSourceProvider mControllableInsetProvider;
private final InsetsState mRequestedInsetsState = new InsetsState();
+ /**
+ * Freeze the insets state in some cases that not necessarily keeps up-to-date to the client.
+ * (e.g app exiting transition)
+ */
+ private InsetsState mFrozenInsetsState;
+
@Nullable InsetsSourceProvider mPendingPositionChanged;
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
@@ -758,6 +764,33 @@
}
}
+ /**
+ * Set a freeze state for the window to ignore dispatching its insets state to the client.
+ *
+ * Used to keep the insets state for some use cases. (e.g. app exiting transition)
+ */
+ void freezeInsetsState() {
+ if (mFrozenInsetsState == null) {
+ mFrozenInsetsState = new InsetsState(getInsetsState(), true /* copySources */);
+ }
+ }
+
+ void clearFrozenInsetsState() {
+ mFrozenInsetsState = null;
+ }
+
+ InsetsState getFrozenInsetsState() {
+ return mFrozenInsetsState;
+ }
+
+ /**
+ * Check if the insets state of the window is ready to dispatch to the client when invoking
+ * {@link InsetsStateController#notifyInsetsChanged}.
+ */
+ boolean isReadyToDispatchInsetsState() {
+ return isVisible() && mFrozenInsetsState == null;
+ }
+
void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
@Rotation int rotation, boolean requested) {
// Invisible windows and the wallpaper do not participate in the seamless rotation animation
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 6fabc58..dfa6083 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -210,7 +210,17 @@
ErrorCode setUidReadTimeouts(const Control& control,
const std::vector<android::os::incremental::PerUidReadTimeouts>&
perUidReadTimeouts) const final {
- return -ENOTSUP;
+ std::vector<incfs::UidReadTimeouts> timeouts;
+ timeouts.resize(perUidReadTimeouts.size());
+ for (int i = 0, size = perUidReadTimeouts.size(); i < size; ++i) {
+ auto&& timeout = timeouts[i];
+ const auto& perUidTimeout = perUidReadTimeouts[i];
+ timeout.uid = perUidTimeout.uid;
+ timeout.minTimeUs = perUidTimeout.minTimeUs;
+ timeout.minPendingTimeUs = perUidTimeout.minPendingTimeUs;
+ timeout.maxPendingTimeUs = perUidTimeout.maxPendingTimeUs;
+ }
+ return incfs::setUidReadTimeouts(control, timeouts);
}
};
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 091e688..5453de1 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -45,7 +45,6 @@
import com.android.server.SystemService;
import com.android.server.people.data.DataManager;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -156,6 +155,13 @@
final IBinder mService = new IPeopleManager.Stub() {
@Override
+ public ConversationChannel getConversation(
+ String packageName, int userId, String shortcutId) {
+ enforceSystemRootOrSystemUI(getContext(), "get conversation");
+ return mDataManager.getConversation(packageName, userId, shortcutId);
+ }
+
+ @Override
public ParceledListSlice<ConversationChannel> getRecentConversations() {
enforceSystemRootOrSystemUI(getContext(), "get recent conversations");
return new ParceledListSlice<>(
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 7521415..9a9a171 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -222,33 +222,65 @@
mContext.getPackageName(), intentFilter, callingUserId);
}
+ /**
+ * Returns a {@link ConversationChannel} with the associated {@code shortcutId} if existent.
+ * Otherwise, returns null.
+ */
+ @Nullable
+ public ConversationChannel getConversation(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 getConversationChannel(packageData, shortcutId);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private ConversationChannel getConversationChannel(PackageData packageData, String shortcutId) {
+ ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
+ if (conversationInfo == null) {
+ return null;
+ }
+ int userId = packageData.getUserId();
+ String packageName = packageData.getPackageName();
+ ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
+ if (shortcutInfo == null) {
+ return null;
+ }
+ int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+ NotificationChannel parentChannel =
+ mNotificationManagerInternal.getNotificationChannel(packageName, uid,
+ conversationInfo.getParentNotificationChannelId());
+ NotificationChannelGroup parentChannelGroup = null;
+ if (parentChannel != null) {
+ parentChannelGroup =
+ mNotificationManagerInternal.getNotificationChannelGroup(packageName,
+ uid, parentChannel.getId());
+ }
+ return new ConversationChannel(shortcutInfo, uid, parentChannel,
+ parentChannelGroup,
+ conversationInfo.getLastEventTimestamp(),
+ hasActiveNotifications(packageName, userId, shortcutId));
+ }
+
/** Returns the cached non-customized recent conversations. */
public List<ConversationChannel> getRecentConversations(@UserIdInt int callingUserId) {
List<ConversationChannel> conversationChannels = new ArrayList<>();
forPackagesInProfile(callingUserId, packageData -> {
- String packageName = packageData.getPackageName();
- int userId = packageData.getUserId();
packageData.forAllConversations(conversationInfo -> {
if (!isCachedRecentConversation(conversationInfo)) {
return;
}
String shortcutId = conversationInfo.getShortcutId();
- ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
- int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
- NotificationChannel parentChannel =
- mNotificationManagerInternal.getNotificationChannel(packageName, uid,
- conversationInfo.getParentNotificationChannelId());
- if (shortcutInfo == null || parentChannel == null) {
+ ConversationChannel channel = getConversationChannel(packageData, shortcutId);
+ if (channel == null || channel.getParentNotificationChannel() == null) {
return;
}
- NotificationChannelGroup parentChannelGroup =
- mNotificationManagerInternal.getNotificationChannelGroup(packageName,
- uid, parentChannel.getId());
- conversationChannels.add(
- new ConversationChannel(shortcutInfo, uid, parentChannel,
- parentChannelGroup,
- conversationInfo.getLastEventTimestamp(),
- hasActiveNotifications(packageName, userId, shortcutId)));
+ conversationChannels.add(channel);
});
});
return conversationChannels;
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 63330d5..161d316 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
@@ -515,6 +515,85 @@
}
@Test
+ public void testGetConversationReturnsCustomizedConversation() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+
+ listenerService.onNotificationPosted(mStatusBarNotification);
+ 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);
+
+ assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID)).isNotNull();
+ }
+
+ @Test
+ public void testGetConversation() {
+ 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);
+ assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID)).isNotNull();
+ assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID + "1")).isNull();
+
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationPosted(mStatusBarNotification);
+
+ ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID);
+ assertThat(result).isNotNull();
+ assertEquals(shortcut.getId(), result.getShortcutInfo().getId());
+ assertEquals(1, result.getShortcutInfo().getPersons().length);
+ assertEquals(CONTACT_URI, result.getShortcutInfo().getPersons()[0].getUri());
+ assertEquals(mParentNotificationChannel.getId(),
+ result.getParentNotificationChannel().getId());
+ assertEquals(mStatusBarNotification.getPostTime(), result.getLastEventTimestamp());
+ assertTrue(result.hasActiveNotifications());
+ }
+
+ @Test
+ public void testGetConversationGetsPersonsData() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ 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);
+
+ verify(mShortcutServiceInternal).getShortcuts(
+ anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
+ mQueryFlagsCaptor.capture(), anyInt(), anyInt(), anyInt());
+ Integer queryFlags = mQueryFlagsCaptor.getValue();
+ assertThat(hasFlag(queryFlags, ShortcutQuery.FLAG_GET_PERSONS_DATA)).isTrue();
+ }
+
+ @Test
public void testNotificationChannelCreated() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
mDataManager.onUserUnlocked(USER_ID_SECONDARY);
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 533dc17..1d0b595 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -877,6 +877,22 @@
}
@Test
+ public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted()
+ throws Exception {
+ when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+ createService();
+ mService.systemReady(null);
+
+ mService.getBinderServiceInstance().wakeUp(mClock.now(),
+ PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
+
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+ DisplayPowerRequest.POLICY_BRIGHT);
+ }
+
+ @Test
public void testIsAmbientDisplayAvailable_available() throws Exception {
createService();
when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 78dd4b8..4bea9a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -27,7 +27,6 @@
import static android.os.Build.VERSION_CODES.Q;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_PRIVATE;
-import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
@@ -96,6 +95,7 @@
import android.app.WindowConfiguration;
import android.app.servertransaction.FixedRotationAdjustmentsItem;
import android.content.res.Configuration;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.metrics.LogMaker;
@@ -707,6 +707,7 @@
// same width and height.
final int displayWidth = dc.mInitialDisplayWidth;
final int displayHeight = dc.mInitialDisplayHeight;
+ final float density = dc.mInitialDisplayDensity;
final int cutoutWidth = 40;
final int cutoutHeight = 10;
final int left = (displayWidth - cutoutWidth) / 2;
@@ -714,9 +715,13 @@
final int right = (displayWidth + cutoutWidth) / 2;
final int bottom = cutoutHeight;
- final Rect r1 = new Rect(left, top, right, bottom);
+ final Rect zeroRect = new Rect();
+ final Rect[] bounds = new Rect[]{zeroRect, new Rect(left, top, right, bottom), zeroRect,
+ zeroRect};
+ final DisplayCutout.CutoutPathParserInfo info = new DisplayCutout.CutoutPathParserInfo(
+ displayWidth, displayHeight, density, "", Surface.ROTATION_0, 1f);
final DisplayCutout cutout = new WmDisplayCutout(
- fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom, BOUNDS_POSITION_TOP), null)
+ DisplayCutout.constructDisplayCutout(bounds, Insets.NONE, info), null)
.computeSafeInsets(displayWidth, displayHeight).getDisplayCutout();
dc.mInitialDisplayCutout = cutout;
@@ -731,9 +736,12 @@
// | | ---o
// | | |
// | | -------------
- final Rect r = new Rect(top, left, bottom, right);
+ final Rect[] bounds90 = new Rect[]{new Rect(top, left, bottom, right), zeroRect, zeroRect,
+ zeroRect};
+ final DisplayCutout.CutoutPathParserInfo info90 = new DisplayCutout.CutoutPathParserInfo(
+ displayWidth, displayHeight, density, "", Surface.ROTATION_90, 1f);
assertEquals(new WmDisplayCutout(
- fromBoundingRect(r.left, r.top, r.right, r.bottom, BOUNDS_POSITION_LEFT), null)
+ DisplayCutout.constructDisplayCutout(bounds90, Insets.NONE, info90), null)
.computeSafeInsets(displayHeight, displayWidth).getDisplayCutout(),
dc.getDisplayInfo().displayCutout);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index b0b8afd..df5b48a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -22,6 +22,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
@@ -978,6 +980,31 @@
assertEquals(200, listener.mConfiguration.densityDpi);
}
+ @Test
+ public void testFreezeInsetsStateWhenAppTransition() {
+ final Task stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+ task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
+ spyOn(win);
+ doReturn(true).when(task).okToAnimate();
+ ArrayList<WindowContainer> sources = new ArrayList<>();
+ sources.add(activity);
+
+ // Simulate the task applying the exit transition, verify the main window of the task
+ // will be set the frozen insets state.
+ task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
+ false /* isVoiceInteraction */, sources);
+ verify(win).freezeInsetsState();
+
+ // Simulate the task transition finished, verify the frozen insets state of the window
+ // will be reset.
+ task.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
+ task.mSurfaceAnimator.getAnimation());
+ verify(win).clearFrozenInsetsState();
+ }
+
/* Used so we can gain access to some protected members of the {@link WindowContainer} class */
private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
private final int mLayer;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 263aa19..3231f8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -810,4 +810,27 @@
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
}
+
+ @Test
+ public void testSetFreezeInsetsState() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ spyOn(app);
+ doReturn(true).when(app).isVisible();
+
+ // Set freezing the insets state to make the window ignore to dispatch insets changed.
+ final InsetsState expectedState = new InsetsState(app.getInsetsState(),
+ true /* copySources */);
+ app.freezeInsetsState();
+ assertEquals(expectedState, app.getFrozenInsetsState());
+ assertFalse(app.isReadyToDispatchInsetsState());
+ assertEquals(expectedState, app.getInsetsState());
+ mDisplayContent.getInsetsStateController().notifyInsetsChanged();
+ verify(app, never()).notifyInsetsChanged();
+
+ // Unfreeze the insets state to make the window can dispatch insets changed.
+ app.clearFrozenInsetsState();
+ assertTrue(app.isReadyToDispatchInsetsState());
+ mDisplayContent.getInsetsStateController().notifyInsetsChanged();
+ verify(app).notifyInsetsChanged();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
index 39976a5..b2646f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
@@ -150,9 +150,9 @@
@Test
public void computeSafeInsets_waterfall() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(
+ DisplayCutout.constructDisplayCutout(
new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT},
- Insets.of(1, 2, 3, 4)),
+ Insets.of(1, 2, 3, 4), null),
200, 400);
assertEquals(new Rect(1, 2, 3, 4), cutout.getDisplayCutout().getSafeInsets());
@@ -161,9 +161,9 @@
@Test
public void computeSafeInsets_cutoutTop_greaterThan_waterfallTop() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(
+ DisplayCutout.constructDisplayCutout(
new Rect[] {ZERO_RECT, new Rect(80, 0, 120, 30), ZERO_RECT, ZERO_RECT},
- Insets.of(0, 20, 0, 0)),
+ Insets.of(0, 20, 0, 0), null),
200, 400);
assertEquals(new Rect(0, 30, 0, 0), cutout.getDisplayCutout().getSafeInsets());
@@ -172,9 +172,9 @@
@Test
public void computeSafeInsets_cutoutTop_lessThan_waterfallTop() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(
+ DisplayCutout.constructDisplayCutout(
new Rect[] {ZERO_RECT, new Rect(80, 0, 120, 30), ZERO_RECT, ZERO_RECT},
- Insets.of(0, 40, 0, 0)),
+ Insets.of(0, 40, 0, 0), null),
200, 400);
assertEquals(new Rect(0, 40, 0, 0), cutout.getDisplayCutout().getSafeInsets());
@@ -183,9 +183,9 @@
@Test
public void computeSafeInsets_cutoutLeft_greaterThan_waterfallLeft() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(
+ DisplayCutout.constructDisplayCutout(
new Rect[] {new Rect(0, 180, 30, 220), ZERO_RECT, ZERO_RECT, ZERO_RECT},
- Insets.of(20, 0, 0, 0)),
+ Insets.of(20, 0, 0, 0), null),
200, 400);
assertEquals(new Rect(30, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
@@ -194,9 +194,9 @@
@Test
public void computeSafeInsets_cutoutLeft_lessThan_waterfallLeft() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(
+ DisplayCutout.constructDisplayCutout(
new Rect[] {new Rect(0, 180, 30, 220), ZERO_RECT, ZERO_RECT, ZERO_RECT},
- Insets.of(40, 0, 0, 0)),
+ Insets.of(40, 0, 0, 0), null),
200, 400);
assertEquals(new Rect(40, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
@@ -205,9 +205,9 @@
@Test
public void computeSafeInsets_cutoutBottom_greaterThan_waterfallBottom() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(
+ DisplayCutout.constructDisplayCutout(
new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(80, 370, 120, 400)},
- Insets.of(0, 0, 0, 20)),
+ Insets.of(0, 0, 0, 20), null),
200, 400);
assertEquals(new Rect(0, 0, 0, 30), cutout.getDisplayCutout().getSafeInsets());
@@ -216,9 +216,9 @@
@Test
public void computeSafeInsets_cutoutBottom_lessThan_waterfallBottom() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(
+ DisplayCutout.constructDisplayCutout(
new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(80, 370, 120, 400)},
- Insets.of(0, 0, 0, 40)),
+ Insets.of(0, 0, 0, 40), null),
200, 400);
assertEquals(new Rect(0, 0, 0, 40), cutout.getDisplayCutout().getSafeInsets());
@@ -227,9 +227,9 @@
@Test
public void computeSafeInsets_cutoutRight_greaterThan_waterfallRight() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(
+ DisplayCutout.constructDisplayCutout(
new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(170, 180, 200, 220), ZERO_RECT},
- Insets.of(0, 0, 20, 0)),
+ Insets.of(0, 0, 20, 0), null),
200, 400);
assertEquals(new Rect(0, 0, 30, 0), cutout.getDisplayCutout().getSafeInsets());
@@ -238,9 +238,9 @@
@Test
public void computeSafeInsets_cutoutRight_lessThan_waterfallRight() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- DisplayCutout.fromBoundsAndWaterfall(
+ DisplayCutout.constructDisplayCutout(
new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(170, 180, 200, 220), ZERO_RECT},
- Insets.of(0, 0, 40, 0)),
+ Insets.of(0, 0, 40, 0), null),
200, 400);
assertEquals(new Rect(0, 0, 40, 0), cutout.getDisplayCutout().getSafeInsets());