Merge "Fix invalid bubble predicate once and for all" into rvc-dev
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
index 483d2cc..9c1acaf 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
@@ -184,9 +184,8 @@
* @throws SecurityException when the caller is not allowed to create a session, such
* as when called from an Instant app.
* @throws IllegalArgumentException when {@code blobHandle} is invalid.
- * @throws IllegalStateException when a new session could not be created, such as when the
- * caller is trying to create too many sessions or when the
- * device is running low on space.
+ * @throws LimitExceededException when a new session could not be created, such as when the
+ * caller is trying to create too many sessions.
*/
public @IntRange(from = 1) long createSession(@NonNull BlobHandle blobHandle)
throws IOException {
@@ -194,6 +193,7 @@
return mService.createSession(blobHandle, mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
+ e.maybeRethrow(LimitExceededException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -302,8 +302,9 @@
* if the {@code leaseExpiryTimeMillis} is greater than the
* {@link BlobHandle#getExpiryTimeMillis()}.
* @throws LimitExceededException when a lease could not be acquired, such as when the
- * caller is trying to acquire leases on too much data. Apps
- * can avoid this by checking the remaining quota using
+ * caller is trying to acquire too many leases or acquire
+ * leases on too much data. Apps can avoid this by checking
+ * the remaining quota using
* {@link #getRemainingLeaseQuotaBytes()} before trying to
* acquire a lease.
*
@@ -362,8 +363,9 @@
* if the {@code leaseExpiryTimeMillis} is greater than the
* {@link BlobHandle#getExpiryTimeMillis()}.
* @throws LimitExceededException when a lease could not be acquired, such as when the
- * caller is trying to acquire leases on too much data. Apps
- * can avoid this by checking the remaining quota using
+ * caller is trying to acquire too many leases or acquire
+ * leases on too much data. Apps can avoid this by checking
+ * the remaining quota using
* {@link #getRemainingLeaseQuotaBytes()} before trying to
* acquire a lease.
*
@@ -415,8 +417,9 @@
* exist or the caller does not have access to it.
* @throws IllegalArgumentException when {@code blobHandle} is invalid.
* @throws LimitExceededException when a lease could not be acquired, such as when the
- * caller is trying to acquire leases on too much data. Apps
- * can avoid this by checking the remaining quota using
+ * caller is trying to acquire too many leases or acquire
+ * leases on too much data. Apps can avoid this by checking
+ * the remaining quota using
* {@link #getRemainingLeaseQuotaBytes()} before trying to
* acquire a lease.
*
@@ -462,8 +465,9 @@
* exist or the caller does not have access to it.
* @throws IllegalArgumentException when {@code blobHandle} is invalid.
* @throws LimitExceededException when a lease could not be acquired, such as when the
- * caller is trying to acquire leases on too much data. Apps
- * can avoid this by checking the remaining quota using
+ * caller is trying to acquire too many leases or acquire
+ * leases on too much data. Apps can avoid this by checking
+ * the remaining quota using
* {@link #getRemainingLeaseQuotaBytes()} before trying to
* acquire a lease.
*
@@ -757,6 +761,8 @@
* @throws SecurityException when the caller is not the owner of the session.
* @throws IllegalStateException when the caller tries to change access for a blob which is
* already committed.
+ * @throws LimitExceededException when the caller tries to explicitly allow too
+ * many packages using this API.
*/
public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate)
throws IOException {
@@ -764,6 +770,7 @@
mSession.allowPackageAccess(packageName, certificate);
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
+ e.maybeRethrow(LimitExceededException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index 4d29045..3d4154a2 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -398,6 +398,26 @@
return revocableFd.getRevocableFileDescriptor();
}
+ void destroy() {
+ revokeAllFds();
+ getBlobFile().delete();
+ }
+
+ private void revokeAllFds() {
+ synchronized (mRevocableFds) {
+ for (int i = 0, pkgCount = mRevocableFds.size(); i < pkgCount; ++i) {
+ final ArraySet<RevocableFileDescriptor> packageFds =
+ mRevocableFds.valueAt(i);
+ if (packageFds == null) {
+ continue;
+ }
+ for (int j = 0, fdCount = packageFds.size(); j < fdCount; ++j) {
+ packageFds.valueAt(j).revoke();
+ }
+ }
+ }
+ }
+
boolean shouldBeDeleted(boolean respectLeaseWaitTime) {
// Expired data blobs
if (getBlobHandle().isExpired()) {
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index 265479f..79cd1b1 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -141,6 +141,36 @@
public static long DELETE_ON_LAST_LEASE_DELAY_MS =
DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS;
+ /**
+ * Denotes the maximum number of active sessions per app at any time.
+ */
+ public static final String KEY_MAX_ACTIVE_SESSIONS = "max_active_sessions";
+ public static int DEFAULT_MAX_ACTIVE_SESSIONS = 250;
+ public static int MAX_ACTIVE_SESSIONS = DEFAULT_MAX_ACTIVE_SESSIONS;
+
+ /**
+ * Denotes the maximum number of committed blobs per app at any time.
+ */
+ public static final String KEY_MAX_COMMITTED_BLOBS = "max_committed_blobs";
+ public static int DEFAULT_MAX_COMMITTED_BLOBS = 1000;
+ public static int MAX_COMMITTED_BLOBS = DEFAULT_MAX_COMMITTED_BLOBS;
+
+ /**
+ * Denotes the maximum number of leased blobs per app at any time.
+ */
+ public static final String KEY_MAX_LEASED_BLOBS = "max_leased_blobs";
+ public static int DEFAULT_MAX_LEASED_BLOBS = 500;
+ public static int MAX_LEASED_BLOBS = DEFAULT_MAX_LEASED_BLOBS;
+
+ /**
+ * Denotes the maximum number of packages explicitly permitted to access a blob
+ * (permitted as part of creating a {@link BlobAccessMode}).
+ */
+ public static final String KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = "max_permitted_pks";
+ public static int DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = 300;
+ public static int MAX_BLOB_ACCESS_PERMITTED_PACKAGES =
+ DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES;
+
static void refresh(Properties properties) {
if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
return;
@@ -178,6 +208,19 @@
DELETE_ON_LAST_LEASE_DELAY_MS = properties.getLong(key,
DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS);
break;
+ case KEY_MAX_ACTIVE_SESSIONS:
+ MAX_ACTIVE_SESSIONS = properties.getInt(key, DEFAULT_MAX_ACTIVE_SESSIONS);
+ break;
+ case KEY_MAX_COMMITTED_BLOBS:
+ MAX_COMMITTED_BLOBS = properties.getInt(key, DEFAULT_MAX_COMMITTED_BLOBS);
+ break;
+ case KEY_MAX_LEASED_BLOBS:
+ MAX_LEASED_BLOBS = properties.getInt(key, DEFAULT_MAX_LEASED_BLOBS);
+ break;
+ case KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES:
+ MAX_BLOB_ACCESS_PERMITTED_PACKAGES = properties.getInt(key,
+ DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES);
+ break;
default:
Slog.wtf(TAG, "Unknown key in device config properties: " + key);
}
@@ -210,6 +253,15 @@
fout.println(String.format(dumpFormat, KEY_DELETE_ON_LAST_LEASE_DELAY_MS,
TimeUtils.formatDuration(DELETE_ON_LAST_LEASE_DELAY_MS),
TimeUtils.formatDuration(DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS)));
+ fout.println(String.format(dumpFormat, KEY_MAX_ACTIVE_SESSIONS,
+ MAX_ACTIVE_SESSIONS, DEFAULT_MAX_ACTIVE_SESSIONS));
+ fout.println(String.format(dumpFormat, KEY_MAX_COMMITTED_BLOBS,
+ MAX_COMMITTED_BLOBS, DEFAULT_MAX_COMMITTED_BLOBS));
+ fout.println(String.format(dumpFormat, KEY_MAX_LEASED_BLOBS,
+ MAX_LEASED_BLOBS, DEFAULT_MAX_LEASED_BLOBS));
+ fout.println(String.format(dumpFormat, KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES,
+ MAX_BLOB_ACCESS_PERMITTED_PACKAGES,
+ DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES));
}
}
@@ -288,6 +340,34 @@
return DeviceConfigProperties.DELETE_ON_LAST_LEASE_DELAY_MS;
}
+ /**
+ * Returns the maximum number of active sessions per app.
+ */
+ public static int getMaxActiveSessions() {
+ return DeviceConfigProperties.MAX_ACTIVE_SESSIONS;
+ }
+
+ /**
+ * Returns the maximum number of committed blobs per app.
+ */
+ public static int getMaxCommittedBlobs() {
+ return DeviceConfigProperties.MAX_COMMITTED_BLOBS;
+ }
+
+ /**
+ * Returns the maximum number of leased blobs per app.
+ */
+ public static int getMaxLeasedBlobs() {
+ return DeviceConfigProperties.MAX_LEASED_BLOBS;
+ }
+
+ /**
+ * Returns the maximum number of packages explicitly permitted to access a blob.
+ */
+ public static int getMaxPermittedPackages() {
+ return DeviceConfigProperties.MAX_BLOB_ACCESS_PERMITTED_PACKAGES;
+ }
+
@Nullable
public static File prepareBlobFile(long sessionId) {
final File blobsDir = prepareBlobsDir();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index a90536fe..f7468d8 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -35,6 +35,9 @@
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs;
import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs;
+import static com.android.server.blob.BlobStoreConfig.getMaxActiveSessions;
+import static com.android.server.blob.BlobStoreConfig.getMaxCommittedBlobs;
+import static com.android.server.blob.BlobStoreConfig.getMaxLeasedBlobs;
import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
@@ -124,6 +127,7 @@
import java.util.Objects;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -332,9 +336,26 @@
mKnownBlobIds.add(id);
}
+ @GuardedBy("mBlobsLock")
+ private int getSessionsCountLocked(int uid, String packageName) {
+ // TODO: Maintain a counter instead of traversing all the sessions
+ final AtomicInteger sessionsCount = new AtomicInteger(0);
+ forEachSessionInUser(session -> {
+ if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) {
+ sessionsCount.getAndIncrement();
+ }
+ }, UserHandle.getUserId(uid));
+ return sessionsCount.get();
+ }
+
private long createSessionInternal(BlobHandle blobHandle,
int callingUid, String callingPackage) {
synchronized (mBlobsLock) {
+ final int sessionsCount = getSessionsCountLocked(callingUid, callingPackage);
+ if (sessionsCount >= getMaxActiveSessions()) {
+ throw new LimitExceededException("Too many active sessions for the caller: "
+ + sessionsCount);
+ }
// TODO: throw if there is already an active session associated with blobHandle.
final long sessionId = generateNextSessionIdLocked();
final BlobStoreSession session = new BlobStoreSession(mContext,
@@ -408,10 +429,39 @@
}
}
+ @GuardedBy("mBlobsLock")
+ private int getCommittedBlobsCountLocked(int uid, String packageName) {
+ // TODO: Maintain a counter instead of traversing all the blobs
+ final AtomicInteger blobsCount = new AtomicInteger(0);
+ forEachBlobInUser((blobMetadata) -> {
+ if (blobMetadata.isACommitter(packageName, uid)) {
+ blobsCount.getAndIncrement();
+ }
+ }, UserHandle.getUserId(uid));
+ return blobsCount.get();
+ }
+
+ @GuardedBy("mBlobsLock")
+ private int getLeasedBlobsCountLocked(int uid, String packageName) {
+ // TODO: Maintain a counter instead of traversing all the blobs
+ final AtomicInteger blobsCount = new AtomicInteger(0);
+ forEachBlobInUser((blobMetadata) -> {
+ if (blobMetadata.isALeasee(packageName, uid)) {
+ blobsCount.getAndIncrement();
+ }
+ }, UserHandle.getUserId(uid));
+ return blobsCount.get();
+ }
+
private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId,
CharSequence description, long leaseExpiryTimeMillis,
int callingUid, String callingPackage) {
synchronized (mBlobsLock) {
+ final int leasesCount = getLeasedBlobsCountLocked(callingUid, callingPackage);
+ if (leasesCount >= getMaxLeasedBlobs()) {
+ throw new LimitExceededException("Too many leased blobs for the caller: "
+ + leasesCount);
+ }
final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
.get(blobHandle);
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
@@ -556,7 +606,11 @@
UserHandle.getUserId(callingUid));
userBlobs.entrySet().removeIf(entry -> {
final BlobMetadata blobMetadata = entry.getValue();
- return blobMetadata.getBlobId() == blobId;
+ if (blobMetadata.getBlobId() == blobId) {
+ deleteBlobLocked(blobMetadata);
+ return true;
+ }
+ return false;
});
writeBlobsInfoAsync();
}
@@ -607,11 +661,10 @@
switch (session.getState()) {
case STATE_ABANDONED:
case STATE_VERIFIED_INVALID:
- session.getSessionFile().delete();
synchronized (mBlobsLock) {
+ deleteSessionLocked(session);
getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
.remove(session.getSessionId());
- mActiveBlobIds.remove(session.getSessionId());
if (LOGV) {
Slog.v(TAG, "Session is invalid; deleted " + session);
}
@@ -626,6 +679,17 @@
break;
case STATE_VERIFIED_VALID:
synchronized (mBlobsLock) {
+ final int committedBlobsCount = getCommittedBlobsCountLocked(
+ session.getOwnerUid(), session.getOwnerPackageName());
+ if (committedBlobsCount >= getMaxCommittedBlobs()) {
+ Slog.d(TAG, "Failed to commit: too many committed blobs. count: "
+ + committedBlobsCount + "; blob: " + session);
+ session.sendCommitCallbackResult(COMMIT_RESULT_ERROR);
+ deleteSessionLocked(session);
+ getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
+ .remove(session.getSessionId());
+ break;
+ }
final int userId = UserHandle.getUserId(session.getOwnerUid());
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
userId);
@@ -656,7 +720,7 @@
} else {
blob.addOrReplaceCommitter(existingCommitter);
}
- Slog.d(TAG, "Error committing the blob", e);
+ Slog.d(TAG, "Error committing the blob: " + session, e);
FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED,
session.getOwnerUid(), blob.getBlobId(), blob.getSize(),
FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT);
@@ -670,8 +734,7 @@
}
// Delete redundant data from recommits.
if (session.getSessionId() != blob.getBlobId()) {
- session.getSessionFile().delete();
- mActiveBlobIds.remove(session.getSessionId());
+ deleteSessionLocked(session);
}
getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
.remove(session.getSessionId());
@@ -957,8 +1020,7 @@
userSessions.removeIf((sessionId, blobStoreSession) -> {
if (blobStoreSession.getOwnerUid() == uid
&& blobStoreSession.getOwnerPackageName().equals(packageName)) {
- blobStoreSession.getSessionFile().delete();
- mActiveBlobIds.remove(blobStoreSession.getSessionId());
+ deleteSessionLocked(blobStoreSession);
return true;
}
return false;
@@ -999,8 +1061,7 @@
if (userSessions != null) {
for (int i = 0, count = userSessions.size(); i < count; ++i) {
final BlobStoreSession session = userSessions.valueAt(i);
- session.getSessionFile().delete();
- mActiveBlobIds.remove(session.getSessionId());
+ deleteSessionLocked(session);
}
}
@@ -1076,8 +1137,7 @@
}
if (shouldRemove) {
- blobStoreSession.getSessionFile().delete();
- mActiveBlobIds.remove(blobStoreSession.getSessionId());
+ deleteSessionLocked(blobStoreSession);
deletedBlobIds.add(blobStoreSession.getSessionId());
}
return shouldRemove;
@@ -1089,13 +1149,29 @@
}
@GuardedBy("mBlobsLock")
+ private void deleteSessionLocked(BlobStoreSession blobStoreSession) {
+ blobStoreSession.destroy();
+ mActiveBlobIds.remove(blobStoreSession.getSessionId());
+ }
+
+ @GuardedBy("mBlobsLock")
private void deleteBlobLocked(BlobMetadata blobMetadata) {
- blobMetadata.getBlobFile().delete();
+ blobMetadata.destroy();
mActiveBlobIds.remove(blobMetadata.getBlobId());
}
void runClearAllSessions(@UserIdInt int userId) {
synchronized (mBlobsLock) {
+ for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
+ final int sessionUserId = mSessions.keyAt(i);
+ if (userId != UserHandle.USER_ALL && userId != sessionUserId) {
+ continue;
+ }
+ final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
+ for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
+ mActiveBlobIds.remove(userSessions.valueAt(j).getSessionId());
+ }
+ }
if (userId == UserHandle.USER_ALL) {
mSessions.clear();
} else {
@@ -1107,6 +1183,16 @@
void runClearAllBlobs(@UserIdInt int userId) {
synchronized (mBlobsLock) {
+ for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
+ final int blobUserId = mBlobsMap.keyAt(i);
+ if (userId != UserHandle.USER_ALL && userId != blobUserId) {
+ continue;
+ }
+ final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
+ for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
+ mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId());
+ }
+ }
if (userId == UserHandle.USER_ALL) {
mBlobsMap.clear();
} else {
@@ -1331,8 +1417,11 @@
+ "callingUid=" + callingUid + ", callingPackage=" + packageName);
}
- // TODO: Verify caller request is within limits (no. of calls/blob sessions/blobs)
- return createSessionInternal(blobHandle, callingUid, packageName);
+ try {
+ return createSessionInternal(blobHandle, callingUid, packageName);
+ } catch (LimitExceededException e) {
+ throw new ParcelableException(e);
+ }
}
@Override
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index baafff5..2f83be1 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -32,6 +32,7 @@
import static com.android.server.blob.BlobStoreConfig.TAG;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME;
+import static com.android.server.blob.BlobStoreConfig.getMaxPermittedPackages;
import static com.android.server.blob.BlobStoreConfig.hasSessionExpired;
import android.annotation.BytesLong;
@@ -43,7 +44,9 @@
import android.content.Context;
import android.os.Binder;
import android.os.FileUtils;
+import android.os.LimitExceededException;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.RevocableFileDescriptor;
import android.os.Trace;
@@ -76,7 +79,10 @@
import java.util.Arrays;
import java.util.Objects;
-/** TODO: add doc */
+/**
+ * Class to represent the state corresponding to an ongoing
+ * {@link android.app.blob.BlobStoreManager.Session}
+ */
@VisibleForTesting
class BlobStoreSession extends IBlobStoreSession.Stub {
@@ -326,6 +332,11 @@
throw new IllegalStateException("Not allowed to change access type in state: "
+ stateToString(mState));
}
+ if (mBlobAccessMode.getNumWhitelistedPackages() >= getMaxPermittedPackages()) {
+ throw new ParcelableException(new LimitExceededException(
+ "Too many packages permitted to access the blob: "
+ + mBlobAccessMode.getNumWhitelistedPackages()));
+ }
mBlobAccessMode.allowPackageAccess(packageName, certificate);
}
}
@@ -468,6 +479,11 @@
}
}
+ void destroy() {
+ revokeAllFds();
+ getSessionFile().delete();
+ }
+
private void revokeAllFds() {
synchronized (mRevocableFds) {
for (int i = mRevocableFds.size() - 1; i >= 0; --i) {
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index cc3017a..0c8c9a9 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -870,6 +870,14 @@
*/
public static final String PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS =
"android.media.mediaParser.overrideInBandCaptionDeclarations";
+ /**
+ * Sets whether a track for EMSG events should be exposed in case of parsing a container that
+ * supports them. {@code boolean} expected. Default value is {@link false}.
+ *
+ * @hide
+ */
+ public static final String PARAMETER_EXPOSE_EMSG_TRACK =
+ "android.media.mediaParser.exposeEmsgTrack";
// Private constants.
@@ -880,6 +888,7 @@
private static final String TS_MODE_MULTI_PMT = "multi_pmt";
private static final String TS_MODE_HLS = "hls";
private static final int BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY = 6;
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
@IntDef(
value = {
@@ -1309,6 +1318,10 @@
return new MatroskaExtractor(flags);
case PARSER_NAME_FMP4:
flags |=
+ getBooleanParameter(PARAMETER_EXPOSE_EMSG_TRACK)
+ ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK
+ : 0;
+ flags |=
getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS)
? FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS
: 0;
@@ -1674,6 +1687,9 @@
if (cryptoData != mLastReceivedCryptoData) {
mLastOutputCryptoInfo =
createNewCryptoInfoAndPopulateWithCryptoData(cryptoData);
+ // We are using in-band crypto info, so the IV will be ignored. But we prevent
+ // it from being null because toString assumes it non-null.
+ mLastOutputCryptoInfo.iv = EMPTY_BYTE_ARRAY;
}
} else /* We must populate the full CryptoInfo. */ {
// CryptoInfo.pattern is not accessible to the user, so the user needs to feed
@@ -1916,8 +1932,10 @@
// format for convenient use from ExoPlayer.
result.setString("crypto-mode-fourcc", format.drmInitData.schemeType);
}
+ if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
+ result.setLong("subsample-offset-us-long", format.subsampleOffsetUs);
+ }
// LACK OF SUPPORT FOR:
- // format.containerMimeType;
// format.id;
// format.metadata;
// format.stereoMode;
@@ -2102,6 +2120,7 @@
PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT, Boolean.class);
expectedTypeByParameterName.put(
PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS, Boolean.class);
+ expectedTypeByParameterName.put(PARAMETER_EXPOSE_EMSG_TRACK, Boolean.class);
// We do not check PARAMETER_EXPOSE_CAPTION_FORMATS here, and we do it in setParameters
// instead. Checking that the value is a List is insufficient to catch wrong parameter
// value types.
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index a5f0ac9..ca03343 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -6131,6 +6131,10 @@
*/
message ProcStats {
optional ProcessStatsSectionProto proc_stats_section = 1;
+ // Data pulled from device into this is sometimes sharded across multiple atoms to work around
+ // a size limit. When this happens, this shard ID will contain an increasing 1-indexed integer
+ // with the number of this shard.
+ optional int32 shard_id = 2;
}
/**
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6f8233d..c9031b7 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -763,23 +763,24 @@
@Override
public void revokeRuntimePermission(String packageName, String permName, UserHandle user) {
- if (DEBUG_TRACE_PERMISSION_UPDATES
- && shouldTraceGrant(packageName, permName, user.getIdentifier())) {
- Log.i(TAG, "App " + mContext.getPackageName() + " is revoking " + packageName + " "
- + permName + " for user " + user.getIdentifier(), new RuntimeException());
- }
- try {
- mPermissionManager
- .revokeRuntimePermission(packageName, permName, user.getIdentifier());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ revokeRuntimePermission(packageName, permName, user, null);
}
@Override
public void revokeRuntimePermission(String packageName, String permName, UserHandle user,
String reason) {
- // TODO evanseverson: impl
+ if (DEBUG_TRACE_PERMISSION_UPDATES
+ && shouldTraceGrant(packageName, permName, user.getIdentifier())) {
+ Log.i(TAG, "App " + mContext.getPackageName() + " is revoking " + packageName + " "
+ + permName + " for user " + user.getIdentifier() + " with reason " + reason,
+ new RuntimeException());
+ }
+ try {
+ mPermissionManager
+ .revokeRuntimePermission(packageName, permName, user.getIdentifier(), reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
@Override
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a828aac..86a3579 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1900,13 +1900,11 @@
@Override
public Object getSystemService(String name) {
- // We may override this API from outer context.
- final boolean isUiContext = isUiContext() || getOuterContext().isUiContext();
// Check incorrect Context usage.
- if (isUiComponent(name) && !isUiContext && vmIncorrectContextUseEnabled()) {
+ if (isUiComponent(name) && !isUiContext() && vmIncorrectContextUseEnabled()) {
final String errorMessage = "Tried to access visual service "
+ SystemServiceRegistry.getSystemServiceClassName(name)
- + " from a non-visual Context:" + getOuterContext();
+ + " from a non-visual Context. ";
final String message = "Visual services, such as WindowManager, WallpaperService or "
+ "LayoutInflater should be accessed from Activity or other visual Context. "
+ "Use an Activity or a Context created with "
@@ -2371,7 +2369,6 @@
context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(),
mResources.getLoaders()));
- context.mIsUiContext = isUiContext() || getOuterContext().isUiContext();
return context;
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e84c5e5..9459577 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -677,4 +677,10 @@
* Return whether the app freezer is supported (true) or not (false) by this system.
*/
boolean isAppFreezerSupported();
+
+
+ /**
+ * Kills uid with the reason of permission change.
+ */
+ void killUidForPermissionChange(int appId, int userId, String reason);
}
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 82e9881..ce51dba 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -294,7 +294,7 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- mPermissionManager.revokeRuntimePermission(packageName, permission, userId);
+ mPermissionManager.revokeRuntimePermission(packageName, permission, userId, null);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 30ee326..15625cd 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -680,7 +680,7 @@
* </table><br>
* </p>
*
- *<p>Devices capable of streaming concurrently with other devices as described by
+ *<p>BACKWARD_COMPATIBLE devices capable of streaming concurrently with other devices as described by
* {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds} have the
* following guaranteed streams (when streaming concurrently with other devices)</p>
*
@@ -696,10 +696,14 @@
* </table><br>
* </p>
*
+ * <p> Devices which are not backwards-compatible, support a mandatory single stream of size sVGA with image format {@code DEPTH16} during concurrent operation.
+ *
* <p> For guaranteed concurrent stream configurations:</p>
- * <p> s720p refers to the camera device's resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or
+ * <p> sVGA refers to the camera device's maximum resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or
+ * VGA resolution (640X480) whichever is lower. </p>
+ * <p> s720p refers to the camera device's maximum resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or
* 720p(1280X720) whichever is lower. </p>
- * <p> s1440p refers to the camera device's resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or
+ * <p> s1440p refers to the camera device's maximum resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or
* 1440p(1920X1440) whichever is lower. </p>
* <p>MONOCHROME-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}
* includes {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}) devices
@@ -707,6 +711,7 @@
* streams with {@code Y8} in all guaranteed stream combinations for the device's hardware level
* and capabilities.</p>
*
+ *
* <p>Devices capable of outputting HEIC formats ({@link StreamConfigurationMap#getOutputFormats}
* contains {@link android.graphics.ImageFormat#HEIC}) will support substituting {@code JPEG}
* streams with {@code HEIC} in all guaranteed stream combinations for the device's hardware
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 20d9c30..776d155 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -685,6 +685,12 @@
"Standard still image capture"),
};
+ private static StreamCombinationTemplate sConcurrentDepthOnlyStreamCombinations[] = {
+ new StreamCombinationTemplate(new StreamTemplate [] {
+ new StreamTemplate(ImageFormat.DEPTH16, SizeThreshold.VGA) },
+ "Depth capture for mesh based object rendering"),
+ };
+
/**
* Helper builder class to generate a list of available mandatory stream combinations.
* @hide
@@ -729,19 +735,21 @@
getAvailableMandatoryConcurrentStreamCombinations() {
// Since concurrent streaming support is optional, we mandate these stream
// combinations regardless of camera device capabilities.
+
+ StreamCombinationTemplate []chosenStreamCombinations = sConcurrentStreamCombinations;
if (!isColorOutputSupported()) {
- Log.v(TAG, "Device is not backward compatible!");
- throw new IllegalArgumentException("Camera device which is not BACKWARD_COMPATIBLE"
- + " cannot have mandatory concurrent streams");
+ Log.v(TAG, "Device is not backward compatible, depth streams are mandatory!");
+ chosenStreamCombinations = sConcurrentDepthOnlyStreamCombinations;
}
+ Size sizeVGAp = new Size(640, 480);
Size size720p = new Size(1280, 720);
Size size1440p = new Size(1920, 1440);
ArrayList<MandatoryStreamCombination> availableConcurrentStreamCombinations =
new ArrayList<MandatoryStreamCombination>();
availableConcurrentStreamCombinations.ensureCapacity(
- sConcurrentStreamCombinations.length);
- for (StreamCombinationTemplate combTemplate : sConcurrentStreamCombinations) {
+ chosenStreamCombinations.length);
+ for (StreamCombinationTemplate combTemplate : chosenStreamCombinations) {
ArrayList<MandatoryStreamInformation> streamsInfo =
new ArrayList<MandatoryStreamInformation>();
streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
@@ -753,6 +761,9 @@
case s1440p:
formatSize = size1440p;
break;
+ case VGA:
+ formatSize = sizeVGAp;
+ break;
default:
formatSize = size720p;
}
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 235b083..e231021 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -71,7 +71,7 @@
void grantRuntimePermission(String packageName, String permName, int userId);
- void revokeRuntimePermission(String packageName, String permName, int userId);
+ void revokeRuntimePermission(String packageName, String permName, int userId, String reason);
void resetRuntimePermissions();
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index ffeeb80..0d2d4d1 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -500,13 +500,12 @@
*/
public static ViewConfiguration get(Context context) {
if (!context.isUiContext() && vmIncorrectContextUseEnabled()) {
- final String errorMessage = "Tried to access UI constants from a non-visual Context:"
- + context;
+ final String errorMessage = "Tried to access UI constants from a non-visual Context.";
final String message = "UI constants, such as display metrics or window metrics, "
+ "must be accessed from Activity or other visual Context. "
+ "Use an Activity or a Context created with "
+ "Context#createWindowContext(int, Bundle), which are adjusted to the "
- + "configuration and visual bounds of an area on screen";
+ + "configuration and visual bounds of an area on screen.";
final Exception exception = new IllegalArgumentException(errorMessage);
StrictMode.onIncorrectContextUsed(message, exception);
Log.e(TAG, errorMessage + message, exception);
diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java
index 1b666aa..9712311 100644
--- a/core/java/android/widget/inline/InlineContentView.java
+++ b/core/java/android/widget/inline/InlineContentView.java
@@ -21,8 +21,8 @@
import android.annotation.TestApi;
import android.content.Context;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceControl;
@@ -156,7 +156,8 @@
@Override
public void onDraw() {
computeParentPositionAndScale();
- mSurfaceView.setVisibility(VISIBLE);
+ final int visibility = InlineContentView.this.isShown() ? VISIBLE : GONE;
+ mSurfaceView.setVisibility(visibility);
}
};
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index 82e99e6..861a056 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -128,6 +128,9 @@
android:layout_weight="1">
<!-- Header -->
+
+ <!-- Use layout_marginStart instead of paddingStart to work around strange
+ measurement behavior on lower display densities. -->
<LinearLayout
android:id="@+id/conversation_header"
android:layout_width="wrap_content"
@@ -135,11 +138,11 @@
android:orientation="horizontal"
android:paddingTop="16dp"
android:layout_marginBottom="2dp"
- android:paddingStart="@dimen/conversation_content_start"
+ android:layout_marginStart="@dimen/conversation_content_start"
>
<TextView
android:id="@+id/conversation_text"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index 493c85a..4eb6e42 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -41,19 +41,18 @@
/**
* Create an ephemeral key pair to use to establish a secure channel with a reader.
*
- * <p>Most applications will use only the public key, and only to send it to the reader,
- * allowing the private key to be used internally for {@link #encryptMessageToReader(byte[])}
- * and {@link #decryptMessageFromReader(byte[])}. The private key is also provided for
- * applications that wish to use a cipher suite that is not supported by
- * {@link IdentityCredentialStore}.
+ * <p>Applications should use this key-pair for the communications channel with the reader
+ * using a protocol / cipher-suite appropriate for the application. One example of such a
+ * protocol is the one used for Mobile Driving Licenses, see ISO 18013-5 section 9.2.1 "Session
+ * encryption".
*
* @return ephemeral key pair to use to establish a secure channel with a reader.
*/
public @NonNull abstract KeyPair createEphemeralKeyPair();
/**
- * Set the ephemeral public key provided by the reader. This must be called before
- * {@link #encryptMessageToReader} or {@link #decryptMessageFromReader} can be called.
+ * Set the ephemeral public key provided by the reader. If called, this must be called before
+ * {@link #getEntries(byte[], Map, byte[], byte[])} is called.
*
* @param readerEphemeralPublicKey The ephemeral public key provided by the reader to
* establish a secure session.
@@ -65,6 +64,11 @@
/**
* Encrypt a message for transmission to the reader.
*
+ * <p>Do not use. In this version of the API, this method produces an incorrect
+ * result. Instead, applications should implement message encryption/decryption themselves as
+ * detailed in the {@link #createEphemeralKeyPair()} method. In a future API-level, this
+ * method will be deprecated.
+ *
* @param messagePlaintext unencrypted message to encrypt.
* @return encrypted message.
*/
@@ -73,6 +77,11 @@
/**
* Decrypt a message received from the reader.
*
+ * <p>Do not use. In this version of the API, this method produces an incorrect
+ * result. Instead, applications should implement message encryption/decryption themselves as
+ * detailed in the {@link #createEphemeralKeyPair()} method. In a future API-level, this
+ * method will be deprecated.
+ *
* @param messageCiphertext encrypted message to decrypt.
* @return decrypted message.
* @throws MessageDecryptionException if the ciphertext couldn't be decrypted.
@@ -178,7 +187,7 @@
*
* <p>If {@code readerAuth} is not {@code null} it must be the bytes of a {@code COSE_Sign1}
* structure as defined in RFC 8152. For the payload nil shall be used and the
- * detached payload is the ReaderAuthentication CBOR described below.
+ * detached payload is the ReaderAuthenticationBytes CBOR described below.
* <pre>
* ReaderAuthentication = [
* "ReaderAuthentication",
@@ -186,7 +195,9 @@
* ItemsRequestBytes
* ]
*
- * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) ; Bytes of ItemsRequest
+ * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+ *
+ * ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
* </pre>
*
* <p>where {@code ItemsRequestBytes} are the bytes in the {@code requestMessage} parameter.
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
index 37de2c4..71860d2 100644
--- a/identity/java/android/security/identity/ResultData.java
+++ b/identity/java/android/security/identity/ResultData.java
@@ -68,8 +68,8 @@
* {@link #getMessageAuthenticationCode()} can be used to get a MAC.
*
* <p>The CBOR structure which is cryptographically authenticated is the
- * {@code DeviceAuthentication} structure according to the following
- * <a href="https://tools.ietf.org/html/draft-ietf-cbor-cddl-06">CDDL</a> schema:
+ * {@code DeviceAuthenticationBytes} structure according to the following
+ * <a href="https://tools.ietf.org/html/rfc8610">CDDL</a> schema:
*
* <pre>
* DeviceAuthentication = [
@@ -80,15 +80,9 @@
* ]
*
* DocType = tstr
- *
- * SessionTranscript = [
- * DeviceEngagementBytes,
- * EReaderKeyBytes
- * ]
- *
- * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
- * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+ * SessionTranscript = any
* DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+ * DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
* </pre>
*
* <p>where
@@ -115,7 +109,7 @@
public abstract @NonNull byte[] getAuthenticatedData();
/**
- * Returns a message authentication code over the {@code DeviceAuthentication} CBOR
+ * Returns a message authentication code over the {@code DeviceAuthenticationBytes} CBOR
* specified in {@link #getAuthenticatedData()}, to prove to the reader that the data
* is from a trusted credential.
*
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index b615885..6dc8322 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -137,6 +137,7 @@
final int desiredHeight, final int desiredHeightResId, @Nullable final String title) {
Objects.requireNonNull(key);
Objects.requireNonNull(shortcutInfo);
+ mMetadataShortcutId = shortcutInfo.getId();
mShortcutInfo = shortcutInfo;
mKey = key;
mFlags = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index e3fbdbc..468b9b1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -281,8 +281,10 @@
Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender())
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, requestCode,
- sharingChooserIntent, 0, null, UserHandle.CURRENT);
+
+ // cancel current pending intent (if any) since clipData isn't used for matching
+ PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
+ sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
// Create a share action for the notification
PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index be85906..a5d99e0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -82,8 +82,6 @@
import static android.os.Process.sendSignal;
import static android.os.Process.setThreadPriority;
import static android.os.Process.setThreadScheduler;
-import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED;
-import static android.permission.PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED;
import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
@@ -9202,16 +9200,31 @@
synchronized (this) {
final long identity = Binder.clearCallingIdentity();
try {
- boolean permissionChange = KILL_APP_REASON_PERMISSIONS_REVOKED.equals(reason)
- || KILL_APP_REASON_GIDS_CHANGED.equals(reason);
mProcessList.killPackageProcessesLocked(null /* packageName */, appId, userId,
ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
true /* callerWillRestart */, true /* doit */, true /* evenPersistent */,
false /* setRemoved */,
- permissionChange ? ApplicationExitInfo.REASON_PERMISSION_CHANGE
- : ApplicationExitInfo.REASON_OTHER,
- permissionChange ? ApplicationExitInfo.SUBREASON_UNKNOWN
- : ApplicationExitInfo.SUBREASON_KILL_UID,
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_KILL_UID,
+ reason != null ? reason : "kill uid");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void killUidForPermissionChange(int appId, int userId, String reason) {
+ enforceCallingPermission(Manifest.permission.KILL_UID, "killUid");
+ synchronized (this) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mProcessList.killPackageProcessesLocked(null /* packageName */, appId, userId,
+ ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
+ true /* callerWillRestart */, true /* doit */, true /* evenPersistent */,
+ false /* setRemoved */,
+ ApplicationExitInfo.REASON_PERMISSION_CHANGE,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
reason != null ? reason : "kill uid");
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f8ab6f4..5e908b2 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -422,6 +422,9 @@
private final boolean mUseFixedVolume;
+ // If absolute volume is supported in AVRCP device
+ private volatile boolean mAvrcpAbsVolSupported = false;
+
/**
* Default stream type used for volume control in the absence of playback
* e.g. user on homescreen, no app playing anything, presses hardware volume buttons, this
@@ -4994,7 +4997,7 @@
return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE;
}
if (audioSystemDeviceOut == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP
- && mDeviceBroker.isAvrcpAbsoluteVolumeSupported()) {
+ && mAvrcpAbsVolSupported) {
return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
}
return AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE;
@@ -5687,12 +5690,12 @@
}
// must be called while synchronized VolumeStreamState.class
- /*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) {
+ /*package*/ void applyDeviceVolume_syncVSS(int device) {
int index;
if (isFullyMuted()) {
index = 0;
} else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
- && isAvrcpAbsVolSupported) {
+ && mAvrcpAbsVolSupported) {
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
} else if (isFullVolumeDevice(device)) {
index = (mIndexMax + 5)/10;
@@ -5705,7 +5708,6 @@
}
public void applyAllVolumes() {
- final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
synchronized (VolumeStreamState.class) {
// apply device specific volumes first
int index;
@@ -5715,7 +5717,7 @@
if (isFullyMuted()) {
index = 0;
} else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
- && isAvrcpAbsVolSupported) {
+ && mAvrcpAbsVolSupported) {
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
} else if (isFullVolumeDevice(device)) {
index = (mIndexMax + 5)/10;
@@ -5955,7 +5957,6 @@
}
public void checkFixedVolumeDevices() {
- final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
synchronized (VolumeStreamState.class) {
// ignore settings for fixed volume devices: volume should always be at max or 0
if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
@@ -5966,7 +5967,7 @@
|| (isFixedVolumeDevice(device) && index != 0)) {
mIndexMap.put(device, mIndexMax);
}
- applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
+ applyDeviceVolume_syncVSS(device);
}
}
}
@@ -6130,11 +6131,9 @@
/*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
- final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
-
synchronized (VolumeStreamState.class) {
// Apply volume
- streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
+ streamState.applyDeviceVolume_syncVSS(device);
// Apply change to all streams using this one as alias
int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -6144,13 +6143,11 @@
// Make sure volume is also maxed out on A2DP device for aliased stream
// that may have a different device selected
int streamDevice = getDeviceForStream(streamType);
- if ((device != streamDevice) && isAvrcpAbsVolSupported
+ if ((device != streamDevice) && mAvrcpAbsVolSupported
&& AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)) {
- mStreamStates[streamType].applyDeviceVolume_syncVSS(device,
- isAvrcpAbsVolSupported);
+ mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
}
- mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice,
- isAvrcpAbsVolSupported);
+ mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
}
}
}
@@ -6458,6 +6455,7 @@
// address is not used for now, but may be used when multiple a2dp devices are supported
sVolumeLogger.log(new AudioEventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
+ address + " support=" + support));
+ mAvrcpAbsVolSupported = support;
mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
@@ -7415,8 +7413,7 @@
pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced);
pw.print(" mHasVibrator="); pw.println(mHasVibrator);
pw.print(" mVolumePolicy="); pw.println(mVolumePolicy);
- pw.print(" mAvrcpAbsVolSupported=");
- pw.println(mDeviceBroker.isAvrcpAbsoluteVolumeSupported());
+ pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
pw.print(" mIsSingleVolume="); pw.println(mIsSingleVolume);
pw.print(" mUseFixedVolume="); pw.println(mUseFixedVolume);
pw.print(" mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index de8ad6b..994cec2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -401,6 +401,7 @@
private boolean mDataLoaderFinished = false;
+ // TODO(b/159663586): should be protected by mLock
private IncrementalFileStorages mIncrementalFileStorages;
private static final FileFilter sAddedApkFilter = new FileFilter() {
@@ -1353,7 +1354,7 @@
private boolean markAsSealed(@NonNull IntentSender statusReceiver, boolean forTransfer) {
Objects.requireNonNull(statusReceiver);
- List<PackageInstallerSession> childSessions = getChildSessions();
+ List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
@@ -1436,7 +1437,11 @@
*
* <p> This method is handy to prevent potential deadlocks (b/123391593)
*/
- private @Nullable List<PackageInstallerSession> getChildSessions() {
+ private @Nullable List<PackageInstallerSession> getChildSessionsNotLocked() {
+ if (Thread.holdsLock(mLock)) {
+ Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+ + " is holding mLock", new Throwable());
+ }
List<PackageInstallerSession> childSessions = null;
if (isMultiPackage()) {
final int[] childSessionIds = getChildSessionIds();
@@ -1605,7 +1610,7 @@
return;
}
}
- List<PackageInstallerSession> childSessions = getChildSessions();
+ List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
synchronized (mLock) {
try {
sealLocked(childSessions);
@@ -1649,7 +1654,7 @@
throw new SecurityException("Can only transfer sessions that use public options");
}
- List<PackageInstallerSession> childSessions = getChildSessions();
+ List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
@@ -1701,7 +1706,7 @@
// outside of the lock, because reading the child
// sessions with the lock held could lead to deadlock
// (b/123391593).
- List<PackageInstallerSession> childSessions = getChildSessions();
+ List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
try {
synchronized (mLock) {
@@ -2602,6 +2607,8 @@
"Session " + sessionId + " is a child of multi-package session "
+ mParentSessionId + " and may not be abandoned directly.");
}
+
+ List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
synchronized (mLock) {
if (params.isStaged && mDestroyed) {
// If a user abandons staged session in an unsafe state, then system will try to
@@ -2625,7 +2632,7 @@
mCallback.onStagedSessionChanged(this);
return;
}
- cleanStageDir();
+ cleanStageDir(childSessions);
}
if (mRelinquished) {
@@ -3055,7 +3062,7 @@
mStagedSessionErrorMessage = errorMessage;
Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
}
- cleanStageDir();
+ cleanStageDirNotLocked();
mCallback.onStagedSessionChanged(this);
}
@@ -3070,7 +3077,7 @@
mStagedSessionErrorMessage = "";
Slog.d(TAG, "Marking session " + sessionId + " as applied");
}
- cleanStageDir();
+ cleanStageDirNotLocked();
mCallback.onStagedSessionChanged(this);
}
@@ -3128,20 +3135,37 @@
}
}
- private void cleanStageDir() {
- if (isMultiPackage()) {
- for (int childSessionId : getChildSessionIds()) {
- mSessionProvider.getSession(childSessionId).cleanStageDir();
+ /**
+ * <b>must not hold {@link #mLock}</b>
+ */
+ private void cleanStageDirNotLocked() {
+ if (Thread.holdsLock(mLock)) {
+ Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+ + " is holding mLock", new Throwable());
+ }
+ cleanStageDir(getChildSessionsNotLocked());
+ }
+
+ private void cleanStageDir(List<PackageInstallerSession> childSessions) {
+ if (childSessions != null) {
+ for (PackageInstallerSession childSession : childSessions) {
+ if (childSession != null) {
+ childSession.cleanStageDir();
+ }
}
} else {
- if (mIncrementalFileStorages != null) {
- mIncrementalFileStorages.cleanUp();
- mIncrementalFileStorages = null;
- }
- try {
- mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
- } catch (InstallerException ignored) {
- }
+ cleanStageDir();
+ }
+ }
+
+ private void cleanStageDir() {
+ if (mIncrementalFileStorages != null) {
+ mIncrementalFileStorages.cleanUp();
+ mIncrementalFileStorages = null;
+ }
+ try {
+ mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
+ } catch (InstallerException ignored) {
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 0dc4d13..1a7490e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2281,7 +2281,7 @@
if (grant) {
mPermissionManager.grantRuntimePermission(pkg, perm, translatedUserId);
} else {
- mPermissionManager.revokeRuntimePermission(pkg, perm, translatedUserId);
+ mPermissionManager.revokeRuntimePermission(pkg, perm, translatedUserId, null);
}
return 0;
}
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 8879ccb..4f0b689 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -330,13 +330,17 @@
mPackageManagerInt.writeSettings(true);
}
@Override
- public void onPermissionRevoked(int uid, int userId) {
+ public void onPermissionRevoked(int uid, int userId, String reason) {
mOnPermissionChangeListeners.onPermissionsChanged(uid);
// Critical; after this call the application should never have the permission
mPackageManagerInt.writeSettings(false);
final int appId = UserHandle.getAppId(uid);
- mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
+ if (reason == null) {
+ mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
+ } else {
+ mHandler.post(() -> killUid(appId, userId, reason));
+ }
}
@Override
public void onInstallPermissionRevoked() {
@@ -473,7 +477,7 @@
IActivityManager am = ActivityManager.getService();
if (am != null) {
try {
- am.killUid(appId, userId, reason);
+ am.killUidForPermissionChange(appId, userId, reason);
} catch (RemoteException e) {
/* ignore - same process */
}
@@ -1529,19 +1533,21 @@
}
@Override
- public void revokeRuntimePermission(String packageName, String permName, int userId) {
+ public void revokeRuntimePermission(String packageName, String permName, int userId,
+ String reason) {
final int callingUid = Binder.getCallingUid();
final boolean overridePolicy =
checkUidPermission(ADJUST_RUNTIME_PERMISSIONS_POLICY, callingUid)
== PackageManager.PERMISSION_GRANTED;
revokeRuntimePermissionInternal(permName, packageName, overridePolicy, callingUid, userId,
- mDefaultPermissionCallback);
+ reason, mDefaultPermissionCallback);
}
// TODO swap permission name and package name
private void revokeRuntimePermissionInternal(String permName, String packageName,
- boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
+ boolean overridePolicy, int callingUid, final int userId, String reason,
+ PermissionCallback callback) {
if (ApplicationPackageManager.DEBUG_TRACE_PERMISSION_UPDATES
&& ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
Log.i(TAG, "System is revoking " + packageName + " "
@@ -1632,7 +1638,7 @@
if (callback != null) {
callback.onPermissionRevoked(UserHandle.getUid(userId,
- UserHandle.getAppId(pkg.getUid())), userId);
+ UserHandle.getAppId(pkg.getUid())), userId, reason);
}
if (bp.isRuntime()) {
@@ -1706,7 +1712,7 @@
mDefaultPermissionCallback.onInstallPermissionGranted();
}
- public void onPermissionRevoked(int uid, int userId) {
+ public void onPermissionRevoked(int uid, int userId, String reason) {
revokedPermissions.add(IntPair.of(uid, userId));
syncUpdatedUsers.add(userId);
@@ -1819,7 +1825,7 @@
} else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
// Otherwise, reset the permission.
revokeRuntimePermissionInternal(permName, packageName, false, Process.SYSTEM_UID,
- userId, delayingPermCallback);
+ userId, null, delayingPermCallback);
}
}
@@ -2300,7 +2306,7 @@
try {
revokeRuntimePermissionInternal(permissionName, packageName,
- false, callingUid, userId, permissionCallback);
+ false, callingUid, userId, null, permissionCallback);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Could not revoke " + permissionName + " from "
+ packageName, e);
@@ -3886,7 +3892,7 @@
PackageManagerServiceUtils.getPermissionsState(mPackageManagerInt,
pkg);
if (!newPermissionsState.hasPermission(permission, userId)) {
- callback.onPermissionRevoked(pkg.getUid(), userId);
+ callback.onPermissionRevoked(pkg.getUid(), userId, null);
break;
}
}
@@ -4245,7 +4251,7 @@
overridePolicy,
Process.SYSTEM_UID,
userId,
- callback);
+ null, callback);
} catch (IllegalArgumentException e) {
Slog.e(TAG,
"Failed to revoke "
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 4412162..2e83b23 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -156,7 +156,7 @@
}
public void onInstallPermissionGranted() {
}
- public void onPermissionRevoked(int uid, @UserIdInt int userId) {
+ public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason) {
}
public void onInstallPermissionRevoked() {
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/README.md b/services/core/java/com/android/server/soundtrigger_middleware/README.md
new file mode 100644
index 0000000..416548d
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/README.md
@@ -0,0 +1,19 @@
+# Sound Trigger Middleware
+TODO: Add component description.
+
+## Notes about thread synchronization
+This component has some tricky thread synchronization considerations due to its layered design and
+due to the fact that it is involved in both in-bound and out-bound calls from / to
+external components. To avoid potential deadlocks, a strict locking order must be ensured whenever
+nesting locks. The order is:
+- `SoundTriggerMiddlewareValidation` lock.
+- Audio policy service lock. This one is external - it should be assumed to be held whenever we're
+ inside the `ExternalCaptureStateTracker.setCaptureState()` call stack *AND* to be acquired from
+ within our calls into `AudioSessionProvider.acquireSession()`.
+- `SoundTriggerModule` lock.
+
+This dictates careful consideration of callbacks going from `SoundTriggerModule` to
+`SoundTriggerMiddlewareValidation` and especially those coming from the `setCaptureState()` path.
+We always invoke those calls outside of the `SoundTriggerModule` lock, so we can lock
+`SoundTriggerMiddlewareValidation`. However, in the `setCaptureState()` case, we have to use atomics
+in `SoundTriggerMiddlewareValidation` and avoid the lock.
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index f4c77a0..5d25d2c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -47,6 +47,9 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -328,7 +331,7 @@
}
/** Activity state. */
- Activity activityState = Activity.LOADED;
+ private AtomicInteger mActivityState = new AtomicInteger(Activity.LOADED.ordinal());
/** Human-readable description of the model. */
final String description;
@@ -383,6 +386,14 @@
void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
parameterSupport.put(modelParam, range);
}
+
+ Activity getActivityState() {
+ return Activity.values()[mActivityState.get()];
+ }
+
+ void setActivityState(Activity activity) {
+ mActivityState.set(activity.ordinal());
+ }
}
/**
@@ -393,7 +404,13 @@
IBinder.DeathRecipient {
private final ISoundTriggerCallback mCallback;
private ISoundTriggerModule mDelegate;
- private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+ // While generally all the fields of this class must be changed under a lock, an exception
+ // is made for the specific case of changing a model state from ACTIVE to LOADED, which
+ // may happen as result of a recognition callback. This would happen atomically and is
+ // necessary in order to avoid deadlocks associated with locking from within callbacks
+ // possibly originating from the audio server.
+ private @NonNull
+ ConcurrentMap<Integer, ModelState> mLoadedModels = new ConcurrentHashMap<>();
private final int mHandle;
private ModuleStatus mState = ModuleStatus.ALIVE;
@@ -476,10 +493,9 @@
if (modelState == null) {
throw new IllegalStateException("Invalid handle: " + modelHandle);
}
- if (modelState.activityState
- != ModelState.Activity.LOADED) {
+ if (modelState.getActivityState() != ModelState.Activity.LOADED) {
throw new IllegalStateException("Model with handle: " + modelHandle
- + " has invalid state for unloading: " + modelState.activityState);
+ + " has invalid state for unloading: " + modelState.getActivityState());
}
// From here on, every exception isn't client's fault.
@@ -509,19 +525,21 @@
if (modelState == null) {
throw new IllegalStateException("Invalid handle: " + modelHandle);
}
- if (modelState.activityState
- != ModelState.Activity.LOADED) {
+ if (modelState.getActivityState() != ModelState.Activity.LOADED) {
throw new IllegalStateException("Model with handle: " + modelHandle
+ " has invalid state for starting recognition: "
- + modelState.activityState);
+ + modelState.getActivityState());
}
// From here on, every exception isn't client's fault.
try {
+ // Normally, we would set the state after the operation succeeds. However, since
+ // the activity state may be reset outside of the lock, we set it here first,
+ // and reset it in case of exception.
+ modelState.setActivityState(ModelState.Activity.ACTIVE);
mDelegate.startRecognition(modelHandle, config);
- modelState.activityState =
- ModelState.Activity.ACTIVE;
} catch (Exception e) {
+ modelState.setActivityState(ModelState.Activity.LOADED);
throw handleException(e);
}
}
@@ -548,8 +566,7 @@
// From here on, every exception isn't client's fault.
try {
mDelegate.stopRecognition(modelHandle);
- modelState.activityState =
- ModelState.Activity.LOADED;
+ modelState.setActivityState(ModelState.Activity.LOADED);
} catch (Exception e) {
throw handleException(e);
}
@@ -719,7 +736,7 @@
for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
pw.print(entry.getKey());
pw.print('\t');
- pw.print(entry.getValue().activityState.name());
+ pw.print(entry.getValue().getActivityState().name());
pw.print('\t');
pw.print(entry.getValue().description);
pw.println();
@@ -735,48 +752,61 @@
@Override
public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
- synchronized (SoundTriggerMiddlewareValidation.this) {
- if (event.status != RecognitionStatus.FORCED) {
- mLoadedModels.get(modelHandle).activityState =
- ModelState.Activity.LOADED;
+ // We cannot obtain a lock on SoundTriggerMiddlewareValidation.this, since this call
+ // might be coming from the audio server (via setCaptureState()) while it is holding
+ // a lock that is also acquired while loading / unloading models. Thus, we require a
+ // strict locking order here, where obtaining our lock must always come first.
+ // To avoid this problem, we use an atomic model activity state. There is a risk of the
+ // model not being in the mLoadedModels map here, since it might have been stopped /
+ // unloaded while the event was in flight.
+ if (event.status != RecognitionStatus.FORCED) {
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState != null) {
+ modelState.setActivityState(ModelState.Activity.LOADED);
}
- try {
- mCallback.onRecognition(modelHandle, event);
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback exception.", e);
- }
+ }
+ try {
+ mCallback.onRecognition(modelHandle, event);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
}
}
@Override
public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
- synchronized (SoundTriggerMiddlewareValidation.this) {
- if (event.common.status != RecognitionStatus.FORCED) {
- mLoadedModels.get(modelHandle).activityState =
- ModelState.Activity.LOADED;
+ // We cannot obtain a lock on SoundTriggerMiddlewareValidation.this, since this call
+ // might be coming from the audio server (via setCaptureState()) while it is holding
+ // a lock that is also acquired while loading / unloading models. Thus, we require a
+ // strict locking order here, where obtaining our lock must always come first.
+ // To avoid this problem, we use an atomic model activity state. There is a risk of the
+ // model not being in the mLoadedModels map here, since it might have been stopped /
+ // unloaded while the event was in flight.
+ if (event.common.status != RecognitionStatus.FORCED) {
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState != null) {
+ modelState.setActivityState(ModelState.Activity.LOADED);
}
- try {
- mCallback.onPhraseRecognition(modelHandle, event);
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback exception.", e);
- }
+ }
+ try {
+ mCallback.onPhraseRecognition(modelHandle, event);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
}
}
@Override
public void onRecognitionAvailabilityChange(boolean available) {
- synchronized (SoundTriggerMiddlewareValidation.this) {
- try {
- mCallback.onRecognitionAvailabilityChange(available);
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback exception.", e);
- }
+ // Not locking to avoid deadlocks (not affecting any state).
+ try {
+ mCallback.onRecognitionAvailabilityChange(available);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
}
}
@@ -804,10 +834,9 @@
// Gracefully stop all active recognitions and unload the models.
for (Map.Entry<Integer, ModelState> entry :
mLoadedModels.entrySet()) {
- if (entry.getValue().activityState
- == ModelState.Activity.ACTIVE) {
- mDelegate.stopRecognition(entry.getKey());
- }
+ // Idempotent call, no harm in calling even for models that are already
+ // stopped.
+ mDelegate.stopRecognition(entry.getKey());
mDelegate.unloadModel(entry.getKey());
}
// Detach.
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 522e5e1..f809ed4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -42,6 +42,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -153,18 +154,28 @@
*
* @param active true iff external capture is active.
*/
- synchronized void setExternalCaptureState(boolean active) {
- if (mProperties.concurrentCapture) {
- // If we support concurrent capture, we don't care about any of this.
- return;
- }
- mRecognitionAvailable = !active;
- if (!mRecognitionAvailable) {
- // Our module does not support recognition while a capture is active -
- // need to abort all active recognitions.
- for (Session session : mActiveSessions) {
- session.abortActiveRecognitions();
+ void setExternalCaptureState(boolean active) {
+ // We should never invoke callbacks while holding the lock, since this may deadlock with
+ // forward calls. Thus, we first gather all the callbacks we need to invoke while holding
+ // the lock, but invoke them after releasing it.
+ List<Runnable> callbacks = new LinkedList<>();
+
+ synchronized (this) {
+ if (mProperties.concurrentCapture) {
+ // If we support concurrent capture, we don't care about any of this.
+ return;
}
+ mRecognitionAvailable = !active;
+ if (!mRecognitionAvailable) {
+ // Our module does not support recognition while a capture is active -
+ // need to abort all active recognitions.
+ for (Session session : mActiveSessions) {
+ session.abortActiveRecognitions(callbacks);
+ }
+ }
+ }
+ for (Runnable callback : callbacks) {
+ callback.run();
}
for (Session session : mActiveSessions) {
session.notifyRecognitionAvailability();
@@ -329,9 +340,18 @@
@Override
public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+ // We should never invoke callbacks while holding the lock, since this may deadlock with
+ // forward calls. Thus, we first gather all the callbacks we need to invoke while holding
+ // the lock, but invoke them after releasing it.
+ List<Runnable> callbacks = new LinkedList<>();
+
synchronized (SoundTriggerModule.this) {
checkValid();
- mLoadedModels.get(modelHandle).startRecognition(config);
+ mLoadedModels.get(modelHandle).startRecognition(config, callbacks);
+ }
+
+ for (Runnable callback : callbacks) {
+ callback.run();
}
}
@@ -377,10 +397,12 @@
/**
* Abort all currently active recognitions.
+ * @param callbacks Will be appended with a list of callbacks that need to be invoked
+ * after this method returns, without holding the module lock.
*/
- private void abortActiveRecognitions() {
+ private void abortActiveRecognitions(@NonNull List<Runnable> callbacks) {
for (Model model : mLoadedModels.values()) {
- model.abortActiveRecognition();
+ model.abortActiveRecognition(callbacks);
}
}
@@ -475,10 +497,11 @@
return mSession.mSessionHandle;
}
- private void startRecognition(@NonNull RecognitionConfig config) {
+ private void startRecognition(@NonNull RecognitionConfig config,
+ @NonNull List<Runnable> callbacks) {
if (!mRecognitionAvailable) {
// Recognition is unavailable - send an abort event immediately.
- notifyAbort();
+ callbacks.add(this::notifyAbort);
return;
}
android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
@@ -525,8 +548,12 @@
ConversionUtil.aidl2hidlModelParameter(modelParam)));
}
- /** Abort the recognition, if active. */
- private void abortActiveRecognition() {
+ /**
+ * Abort the recognition, if active.
+ * @param callbacks Will be appended with a list of callbacks that need to be invoked
+ * after this method returns, without holding the module lock.
+ */
+ private void abortActiveRecognition(List<Runnable> callbacks) {
// If we're inactive, do nothing.
if (getState() != ModelState.ACTIVE) {
return;
@@ -535,7 +562,7 @@
stopRecognition();
// Notify the client that recognition has been aborted.
- notifyAbort();
+ callbacks.add(this::notifyAbort);
}
/** Notify the client that recognition has been aborted. */
@@ -577,42 +604,44 @@
public void recognitionCallback(
@NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
int cookie) {
+ RecognitionEvent aidlEvent =
+ ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
+ aidlEvent.captureSession = mSession.mSessionHandle;
synchronized (SoundTriggerModule.this) {
- RecognitionEvent aidlEvent =
- ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
- aidlEvent.captureSession = mSession.mSessionHandle;
- try {
- mCallback.onRecognition(mHandle, aidlEvent);
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
- }
if (aidlEvent.status != RecognitionStatus.FORCED) {
setState(ModelState.LOADED);
}
}
+ // The callback must be invoked outside of the lock.
+ try {
+ mCallback.onRecognition(mHandle, aidlEvent);
+ } catch (RemoteException e) {
+ // We're not expecting any exceptions here.
+ throw e.rethrowAsRuntimeException();
+ }
}
@Override
public void phraseRecognitionCallback(
@NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
int cookie) {
+ PhraseRecognitionEvent aidlEvent =
+ ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
+ aidlEvent.common.captureSession = mSession.mSessionHandle;
+
synchronized (SoundTriggerModule.this) {
- PhraseRecognitionEvent aidlEvent =
- ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
- aidlEvent.common.captureSession = mSession.mSessionHandle;
- try {
- mCallback.onPhraseRecognition(mHandle, aidlEvent);
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
- }
if (aidlEvent.common.status != RecognitionStatus.FORCED) {
setState(ModelState.LOADED);
}
}
+
+ // The callback must be invoked outside of the lock.
+ try {
+ mCallback.onPhraseRecognition(mHandle, aidlEvent);
+ } catch (RemoteException e) {
+ // We're not expecting any exceptions here.
+ throw e.rethrowAsRuntimeException();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 09fd33d..dbdef23 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -2575,11 +2575,17 @@
lastHighWaterMark, section, true, statsFiles, procStats);
procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
- for (ProtoOutputStream proto : protoStreams) {
- if (proto.getBytes().length > 0) {
+ for (int i = 0; i < protoStreams.length; i++) {
+ byte[] bytes = protoStreams[i].getBytes(); // cache the value
+ if (bytes.length > 0) {
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
- .writeByteArray(proto.getBytes())
+ .writeByteArray(bytes)
+ // This is a shard ID, and is specified in the metric definition to be
+ // a dimension. This will result in statsd using RANDOM_ONE_SAMPLE to
+ // keep all the shards, as it thinks each shard is a different dimension
+ // of data.
+ .writeInt(i)
.build();
pulledData.add(e);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 6fbfa68..16ca60d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -52,6 +52,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.PendingIntentRecord;
+import com.android.server.uri.NeededUriGrants;
import com.android.server.wm.ActivityStackSupervisor.PendingActivityLaunch;
import com.android.server.wm.ActivityStarter.DefaultFactory;
import com.android.server.wm.ActivityStarter.Factory;
@@ -402,6 +403,7 @@
// potentially acquire activity manager lock that leads to deadlock.
for (int i = 0; i < intents.length; i++) {
Intent intent = intents[i];
+ NeededUriGrants intentGrants = null;
// Refuse possible leaked file descriptors.
if (intent.hasFileDescriptors()) {
@@ -418,6 +420,14 @@
0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid);
aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId);
+ // Carefully collect grants without holding lock
+ if (aInfo != null) {
+ intentGrants = mSupervisor.mService.mUgmInternal
+ .checkGrantUriPermissionFromIntent(intent, filterCallingUid,
+ aInfo.applicationInfo.packageName,
+ UserHandle.getUserId(aInfo.applicationInfo.uid));
+ }
+
if (aInfo != null) {
if ((aInfo.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
@@ -433,6 +443,7 @@
? options
: null;
starters[i] = obtainStarter(intent, reason)
+ .setIntentGrants(intentGrants)
.setCaller(caller)
.setResolvedType(resolvedTypes[i])
.setActivityInfo(aInfo)
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3077997..25842f5 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2638,6 +2638,11 @@
return mRequest.intent;
}
+ ActivityStarter setIntentGrants(NeededUriGrants intentGrants) {
+ mRequest.intentGrants = intentGrants;
+ return this;
+ }
+
ActivityStarter setReason(String reason) {
mRequest.reason = reason;
return this;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1762b62..c8d9fe0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -351,13 +351,9 @@
}
private void updateVisibility() {
- // TODO(b/159699383): remove the client controlled check when the insets visibility can be
- // driven by the system UI.
final boolean isClientControlled = mControlTarget != null
&& mControlTarget.isClientControlled();
- mSource.setVisible(mServerVisible
- && ((!isClientControlled && mDisplayContent.inMultiWindowMode())
- || mClientVisible));
+ mSource.setVisible(mServerVisible && (!isClientControlled || mClientVisible));
ProtoLog.d(WM_DEBUG_IME,
"InsetsSource updateVisibility serverVisible: %s clientVisible: %s",
mServerVisible, mClientVisible);
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 7b69038..e230807 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -564,16 +564,23 @@
// Apps and their containers are not allowed to specify an orientation while using
// root tasks...except for the home stack if it is not resizable and currently
// visible (top of) its root task.
- if (mRootHomeTask != null && mRootHomeTask.isVisible()
- && !mRootHomeTask.isResizeable()) {
+ if (mRootHomeTask != null && !mRootHomeTask.isResizeable()) {
// Manually nest one-level because because getOrientation() checks fillsParent()
// which checks that requestedOverrideBounds() is empty. However, in this case,
// it is not empty because it's been overridden to maintain the fullscreen size
// within a smaller split-root.
final Task topHomeTask = mRootHomeTask.getTopMostTask();
- final int orientation = topHomeTask.getOrientation();
- if (orientation != SCREEN_ORIENTATION_UNSET) {
- return orientation;
+ final ActivityRecord topHomeActivity = topHomeTask.getTopNonFinishingActivity();
+ // If a home activity is in the process of launching and isn't yet visible we
+ // should still respect the stack's preferred orientation to ensure rotation occurs
+ // before the home activity finishes launching.
+ final boolean isHomeActivityLaunching = topHomeActivity != null
+ && topHomeActivity.mVisibleRequested;
+ if (topHomeTask.isVisible() || isHomeActivityLaunching) {
+ final int orientation = topHomeTask.getOrientation();
+ if (orientation != SCREEN_ORIENTATION_UNSET) {
+ return orientation;
+ }
}
}
return SCREEN_ORIENTATION_UNSPECIFIED;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 52fb941..564eecf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5142,8 +5142,8 @@
}
case WINDOW_STATE_BLAST_SYNC_TIMEOUT: {
synchronized (mGlobalLock) {
- final WindowState ws = (WindowState) msg.obj;
- ws.finishDrawing(null);
+ final WindowState ws = (WindowState) msg.obj;
+ ws.immediatelyNotifyBlastSync();
}
break;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index fbc5afa..46e1bf0 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -428,6 +428,9 @@
try {
callback.onTransactionReady(mSyncId, mergedTransaction);
} catch (RemoteException e) {
+ // If there's an exception when trying to send the mergedTransaction to the client, we
+ // should immediately apply it here so the transactions aren't lost.
+ mergedTransaction.apply();
}
mTransactionCallbacksByPendingSyncId.remove(mSyncId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 26a1fea..49ef4e4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2192,7 +2192,7 @@
void removeIfPossible() {
super.removeIfPossible();
removeIfPossible(false /*keepVisibleDeadWindow*/);
- finishDrawing(null);
+ immediatelyNotifyBlastSync();
}
private void removeIfPossible(boolean keepVisibleDeadWindow) {
@@ -5806,7 +5806,7 @@
// client will not render when visibility is GONE. Therefore, call finishDrawing here to
// prevent system server from blocking on a window that will not draw.
if (viewVisibility == View.GONE && mUsingBLASTSyncTransaction) {
- finishDrawing(null);
+ immediatelyNotifyBlastSync();
}
}
@@ -5844,7 +5844,6 @@
return mWinAnimator.finishDrawingLocked(postDrawTransaction);
}
- mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
if (postDrawTransaction != null) {
mBLASTSyncTransaction.merge(postDrawTransaction);
}
@@ -5853,8 +5852,9 @@
return mWinAnimator.finishDrawingLocked(null);
}
- @VisibleForTesting
- void notifyBlastSyncTransaction() {
+ private void notifyBlastSyncTransaction() {
+ mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
+
if (!mNotifyBlastOnSurfacePlacement || mWaitingListener == null) {
mNotifyBlastOnSurfacePlacement = false;
return;
@@ -5877,6 +5877,11 @@
mNotifyBlastOnSurfacePlacement = false;
}
+ void immediatelyNotifyBlastSync() {
+ finishDrawing(null);
+ notifyBlastSyncTransaction();
+ }
+
private boolean requestResizeForBlastSync() {
return useBLASTSync() && !mResizeForBlastSyncReported;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
index 7446289..4e2f9a4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
@@ -174,10 +174,10 @@
mService.handlePackageRemoved(TEST_PKG1, TEST_UID1);
// Verify sessions are removed
- verify(sessionFile1).delete();
- verify(sessionFile2, never()).delete();
- verify(sessionFile3, never()).delete();
- verify(sessionFile4).delete();
+ verify(session1).destroy();
+ verify(session2, never()).destroy();
+ verify(session3, never()).destroy();
+ verify(session4).destroy();
assertThat(mUserSessions.size()).isEqualTo(2);
assertThat(mUserSessions.get(sessionId1)).isNull();
@@ -193,9 +193,9 @@
verify(blobMetadata3).removeCommitter(TEST_PKG1, TEST_UID1);
verify(blobMetadata3).removeLeasee(TEST_PKG1, TEST_UID1);
- verify(blobFile1, never()).delete();
- verify(blobFile2).delete();
- verify(blobFile3).delete();
+ verify(blobMetadata1, never()).destroy();
+ verify(blobMetadata2).destroy();
+ verify(blobMetadata3).destroy();
assertThat(mUserBlobs.size()).isEqualTo(1);
assertThat(mUserBlobs.get(blobHandle1)).isNotNull();
@@ -272,9 +272,9 @@
mService.handleIdleMaintenanceLocked();
// Verify stale sessions are removed
- verify(sessionFile1).delete();
- verify(sessionFile2, never()).delete();
- verify(sessionFile3).delete();
+ verify(session1).destroy();
+ verify(session2, never()).destroy();
+ verify(session3).destroy();
assertThat(mUserSessions.size()).isEqualTo(1);
assertThat(mUserSessions.get(sessionId2)).isNotNull();
@@ -317,9 +317,9 @@
mService.handleIdleMaintenanceLocked();
// Verify stale blobs are removed
- verify(blobFile1).delete();
- verify(blobFile2, never()).delete();
- verify(blobFile3).delete();
+ verify(blobMetadata1).destroy();
+ verify(blobMetadata2, never()).destroy();
+ verify(blobMetadata3).destroy();
assertThat(mUserBlobs.size()).isEqualTo(1);
assertThat(mUserBlobs.get(blobHandle2)).isNotNull();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 786f8d8..8c3661b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -28,14 +28,17 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -207,6 +210,40 @@
false /* reuseCandidate */);
}
+ @Test
+ public void testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange() {
+ final RootWindowContainer rootWindowContainer = mWm.mAtmService.mRootWindowContainer;
+ final TaskDisplayArea defaultTaskDisplayArea =
+ rootWindowContainer.getDefaultTaskDisplayArea();
+
+ final ActivityStack rootHomeTask = defaultTaskDisplayArea.getRootHomeTask();
+ rootHomeTask.mResizeMode = RESIZE_MODE_UNRESIZEABLE;
+
+ final ActivityStack primarySplitTask =
+ new ActivityTestsBase.StackBuilder(rootWindowContainer)
+ .setTaskDisplayArea(defaultTaskDisplayArea)
+ .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setOnTop(true)
+ .setCreateActivity(true)
+ .build();
+ ActivityRecord primarySplitActivity = primarySplitTask.getTopNonFinishingActivity();
+ assertNotNull(primarySplitActivity);
+ primarySplitActivity.setState(RESUMED,
+ "testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange");
+
+ ActivityRecord homeActivity = rootHomeTask.getTopNonFinishingActivity();
+ if (homeActivity == null) {
+ homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+ .setStack(rootHomeTask).setCreateTask(true).build();
+ }
+ homeActivity.setVisible(false);
+ homeActivity.mVisibleRequested = true;
+ assertFalse(rootHomeTask.isVisible());
+
+ assertEquals(rootWindowContainer.getOrientation(), rootHomeTask.getOrientation());
+ }
+
private void assertGetOrCreateStack(int windowingMode, int activityType, Task candidateTask,
boolean reuseCandidate) {
final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7ce0c1e..341e209 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -729,7 +729,7 @@
// We should be rejected from the second sync since we are already
// in one.
assertEquals(false, bse.addToSyncSet(id2, task));
- finishAndNotifyDrawing(w);
+ w.immediatelyNotifyBlastSync();
assertEquals(true, bse.addToSyncSet(id2, task));
bse.setReady(id2);
}
@@ -753,7 +753,7 @@
// Since we have a window we have to wait for it to draw to finish sync.
verify(transactionListener, never())
.onTransactionReady(anyInt(), any());
- finishAndNotifyDrawing(w);
+ w.immediatelyNotifyBlastSync();
verify(transactionListener)
.onTransactionReady(anyInt(), any());
}
@@ -821,14 +821,14 @@
int id = bse.startSyncSet(transactionListener);
assertEquals(true, bse.addToSyncSet(id, task));
bse.setReady(id);
- finishAndNotifyDrawing(w);
+ w.immediatelyNotifyBlastSync();
// Since we have a child window we still shouldn't be done.
verify(transactionListener, never())
.onTransactionReady(anyInt(), any());
reset(transactionListener);
- finishAndNotifyDrawing(child);
+ child.immediatelyNotifyBlastSync();
// Ah finally! Done
verify(transactionListener)
.onTransactionReady(anyInt(), any());
@@ -1002,20 +1002,15 @@
verify(mockCallback, never()).onTransactionReady(anyInt(), any());
assertTrue(w1.useBLASTSync());
assertTrue(w2.useBLASTSync());
- finishAndNotifyDrawing(w1);
+ w1.immediatelyNotifyBlastSync();
// Even though one Window finished drawing, both windows should still be using blast sync
assertTrue(w1.useBLASTSync());
assertTrue(w2.useBLASTSync());
- finishAndNotifyDrawing(w2);
+ w2.immediatelyNotifyBlastSync();
verify(mockCallback).onTransactionReady(anyInt(), any());
assertFalse(w1.useBLASTSync());
assertFalse(w2.useBLASTSync());
}
-
- private void finishAndNotifyDrawing(WindowState ws) {
- ws.finishDrawing(null);
- ws.notifyBlastSyncTransaction();
- }
}