diff options
356 files changed, 8037 insertions, 2493 deletions
diff --git a/Android.bp b/Android.bp index 2318f7bbf73a..c89917975e8e 100644 --- a/Android.bp +++ b/Android.bp @@ -551,17 +551,25 @@ java_library { java_library { name: "framework-annotation-proc", - srcs: [":framework-all-sources"], + srcs: [ + ":framework-all-sources", + "core/java/**/*.logtags", + ], + sdk_version: "core_platform", libs: [ "app-compat-annotations", + "ext", + "icing-java-proto-lite", "unsupportedappusage", ], + installable: false, plugins: [ "unsupportedappusage-annotation-processor", "compat-changeid-annotation-processor", ], static_libs: [ + "framework-internal-utils", "exoplayer2-extractor", "android.hardware.wifi-V1.0-java-constants", ] diff --git a/apex/blobstore/TEST_MAPPING b/apex/blobstore/TEST_MAPPING index 4dc0c49380c8..cfe19a530b27 100644 --- a/apex/blobstore/TEST_MAPPING +++ b/apex/blobstore/TEST_MAPPING @@ -2,6 +2,14 @@ "presubmit": [ { "name": "CtsBlobStoreTestCases" + }, + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.blob" + } + ] } ] }
\ No newline at end of file diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java index f7e6a987ded3..f110b36c7e90 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java @@ -45,6 +45,10 @@ import java.util.Objects; public final class BlobHandle implements Parcelable { private static final String ALGO_SHA_256 = "SHA-256"; + private static final String[] SUPPORTED_ALGOS = { + ALGO_SHA_256 + }; + private static final int LIMIT_BLOB_TAG_LENGTH = 128; // characters /** @@ -104,14 +108,9 @@ public final class BlobHandle implements Parcelable { public static @NonNull BlobHandle create(@NonNull String algorithm, @NonNull byte[] digest, @NonNull CharSequence label, @CurrentTimeMillisLong long expiryTimeMillis, @NonNull String tag) { - Preconditions.checkNotNull(algorithm, "algorithm must not be null"); - Preconditions.checkNotNull(digest, "digest must not be null"); - Preconditions.checkNotNull(label, "label must not be null"); - Preconditions.checkArgumentNonnegative(expiryTimeMillis, - "expiryTimeMillis must not be negative"); - Preconditions.checkNotNull(tag, "tag must not be null"); - Preconditions.checkArgument(tag.length() <= LIMIT_BLOB_TAG_LENGTH, "tag too long"); - return new BlobHandle(algorithm, digest, label, expiryTimeMillis, tag); + final BlobHandle handle = new BlobHandle(algorithm, digest, label, expiryTimeMillis, tag); + handle.assertIsValid(); + return handle; } /** @@ -215,12 +214,47 @@ public final class BlobHandle implements Parcelable { } /** @hide */ - public void dump(IndentingPrintWriter fout) { - fout.println("algo: " + algorithm); - fout.println("digest: " + Base64.encodeToString(digest, Base64.NO_WRAP)); - fout.println("label: " + label); - fout.println("expiryMs: " + expiryTimeMillis); - fout.println("tag: " + tag); + public void dump(IndentingPrintWriter fout, boolean dumpFull) { + if (dumpFull) { + fout.println("algo: " + algorithm); + fout.println("digest: " + (dumpFull ? encodeDigest() : safeDigest())); + fout.println("label: " + label); + fout.println("expiryMs: " + expiryTimeMillis); + fout.println("tag: " + tag); + } else { + fout.println(toString()); + } + } + + /** @hide */ + public void assertIsValid() { + Preconditions.checkArgumentIsSupported(SUPPORTED_ALGOS, algorithm); + Preconditions.checkByteArrayNotEmpty(digest, "digest"); + Preconditions.checkStringNotEmpty(label, "label must not be null"); + Preconditions.checkArgumentNonnegative(expiryTimeMillis, + "expiryTimeMillis must not be negative"); + Preconditions.checkStringNotEmpty(tag, "tag must not be null"); + Preconditions.checkArgument(tag.length() <= LIMIT_BLOB_TAG_LENGTH, "tag too long"); + } + + @Override + public String toString() { + return "BlobHandle {" + + "algo:" + algorithm + "," + + "digest:" + safeDigest() + "," + + "label:" + label + "," + + "expiryMs:" + expiryTimeMillis + "," + + "tag:" + tag + + "}"; + } + + private String safeDigest() { + final String digestStr = encodeDigest(); + return digestStr.substring(0, 2) + ".." + digestStr.substring(digestStr.length() - 2); + } + + private String encodeDigest() { + return Base64.encodeToString(digest, Base64.NO_WRAP); } public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() { 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 e9838d6b9712..aba3e8cadfa3 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -48,6 +48,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; +import com.android.server.blob.BlobStoreManagerService.DumpArgs; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -156,6 +157,12 @@ class BlobMetadata { } } + boolean hasLeases() { + synchronized (mMetadataLock) { + return mLeasees.isEmpty(); + } + } + boolean isAccessAllowedForCaller(String callingPackage, int callingUid) { // TODO: verify blob is still valid (expiryTime is not elapsed) synchronized (mMetadataLock) { @@ -234,10 +241,10 @@ class BlobMetadata { return revocableFd.getRevocableFileDescriptor(); } - void dump(IndentingPrintWriter fout) { + void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { fout.println("blobHandle:"); fout.increaseIndent(); - blobHandle.dump(fout); + blobHandle.dump(fout, dumpArgs.shouldDumpFull()); fout.decreaseIndent(); fout.println("Committers:"); 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 fcc30e30dfaa..13f095e5a503 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -40,6 +40,7 @@ import android.annotation.IdRes; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.blob.BlobHandle; import android.app.blob.IBlobStoreManager; import android.app.blob.IBlobStoreSession; @@ -49,6 +50,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManagerInternal; +import android.content.res.ResourceId; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -67,6 +69,8 @@ import android.util.SparseArray; import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; @@ -91,6 +95,7 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Service responsible for maintaining and facilitating access to data blobs published by apps. @@ -112,20 +117,35 @@ public class BlobStoreManagerService extends SystemService { private final Context mContext; private final Handler mHandler; + private final Injector mInjector; private final SessionStateChangeListener mSessionStateChangeListener = new SessionStateChangeListener(); private PackageManagerInternal mPackageManagerInternal; + private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo; + private final Runnable mSaveSessionsRunnable = this::writeBlobSessions; + public BlobStoreManagerService(Context context) { + this(context, new Injector()); + } + + @VisibleForTesting + BlobStoreManagerService(Context context, Injector injector) { super(context); + mContext = context; + mInjector = injector; + mHandler = mInjector.initializeMessageHandler(); + } + private static Handler initializeMessageHandler() { final HandlerThread handlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /* allowIo */); handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper()); - Watchdog.getInstance().addThread(mHandler); + final Handler handler = new Handler(handlerThread.getLooper()); + Watchdog.getInstance().addThread(handler); + return handler; } @Override @@ -181,6 +201,20 @@ public class BlobStoreManagerService extends SystemService { return userBlobs; } + @VisibleForTesting + void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) { + synchronized (mBlobsLock) { + mSessions.put(userId, userSessions); + } + } + + @VisibleForTesting + void addUserBlobsForTest(ArrayMap<BlobHandle, BlobMetadata> userBlobs, int userId) { + synchronized (mBlobsLock) { + mBlobsMap.put(userId, userBlobs); + } + } + private long createSessionInternal(BlobHandle blobHandle, int callingUid, String callingPackage) { synchronized (mBlobsLock) { @@ -293,23 +327,23 @@ public class BlobStoreManagerService extends SystemService { case STATE_ABANDONED: case STATE_VERIFIED_INVALID: session.getSessionFile().delete(); - getUserSessionsLocked(UserHandle.getUserId(session.ownerUid)) - .remove(session.sessionId); + getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) + .remove(session.getSessionId()); break; case STATE_COMMITTED: session.verifyBlobData(); break; case STATE_VERIFIED_VALID: - final int userId = UserHandle.getUserId(session.ownerUid); + final int userId = UserHandle.getUserId(session.getOwnerUid()); final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId); - BlobMetadata blob = userBlobs.get(session.blobHandle); + BlobMetadata blob = userBlobs.get(session.getBlobHandle()); if (blob == null) { blob = new BlobMetadata(mContext, - session.sessionId, session.blobHandle, userId); - userBlobs.put(session.blobHandle, blob); + session.getSessionId(), session.getBlobHandle(), userId); + userBlobs.put(session.getBlobHandle(), blob); } - final Committer newCommitter = new Committer(session.ownerPackageName, - session.ownerUid, session.getBlobAccessMode()); + final Committer newCommitter = new Committer(session.getOwnerPackageName(), + session.getOwnerUid(), session.getBlobAccessMode()); final Committer existingCommitter = blob.getExistingCommitter(newCommitter); blob.addCommitter(newCommitter); try { @@ -319,8 +353,8 @@ public class BlobStoreManagerService extends SystemService { blob.addCommitter(existingCommitter); session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); } - getUserSessionsLocked(UserHandle.getUserId(session.ownerUid)) - .remove(session.sessionId); + getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) + .remove(session.getSessionId()); break; default: Slog.wtf(TAG, "Invalid session state: " @@ -399,17 +433,17 @@ public class BlobStoreManagerService extends SystemService { continue; } final SparseArray<String> userPackages = allPackages.get( - UserHandle.getUserId(session.ownerUid)); + UserHandle.getUserId(session.getOwnerUid())); if (userPackages != null - && session.ownerPackageName.equals( - userPackages.get(session.ownerUid))) { - getUserSessionsLocked(UserHandle.getUserId(session.ownerUid)).put( - session.sessionId, session); + && session.getOwnerPackageName().equals( + userPackages.get(session.getOwnerUid()))) { + getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())).put( + session.getSessionId(), session); } else { // Unknown package or the session data does not belong to this package. session.getSessionFile().delete(); } - mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.sessionId); + mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.getSessionId()); } } } catch (Exception e) { @@ -504,9 +538,9 @@ public class BlobStoreManagerService extends SystemService { } private void writeBlobsInfoAsync() { - mHandler.post(PooledLambda.obtainRunnable( - BlobStoreManagerService::writeBlobsInfo, - BlobStoreManagerService.this).recycleOnUse()); + if (!mHandler.hasCallbacks(mSaveBlobsInfoRunnable)) { + mHandler.post(mSaveBlobsInfoRunnable); + } } private void writeBlobSessions() { @@ -520,9 +554,9 @@ public class BlobStoreManagerService extends SystemService { } private void writeBlobSessionsAsync() { - mHandler.post(PooledLambda.obtainRunnable( - BlobStoreManagerService::writeBlobSessions, - BlobStoreManagerService.this).recycleOnUse()); + if (!mHandler.hasCallbacks(mSaveSessionsRunnable)) { + mHandler.post(mSaveSessionsRunnable); + } } private int getPackageUid(String packageName, int userId) { @@ -568,7 +602,8 @@ public class BlobStoreManagerService extends SystemService { return new AtomicFile(file, "blobs_index" /* commitLogTag */); } - private void handlePackageRemoved(String packageName, int uid) { + @VisibleForTesting + void handlePackageRemoved(String packageName, int uid) { synchronized (mBlobsLock) { // Clean up any pending sessions final LongSparseArray<BlobStoreSession> userSessions = @@ -576,25 +611,35 @@ public class BlobStoreManagerService extends SystemService { final ArrayList<Integer> indicesToRemove = new ArrayList<>(); for (int i = 0, count = userSessions.size(); i < count; ++i) { final BlobStoreSession session = userSessions.valueAt(i); - if (session.ownerUid == uid - && session.ownerPackageName.equals(packageName)) { + if (session.getOwnerUid() == uid + && session.getOwnerPackageName().equals(packageName)) { session.getSessionFile().delete(); indicesToRemove.add(i); } } for (int i = 0, count = indicesToRemove.size(); i < count; ++i) { - userSessions.removeAt(i); + userSessions.removeAt(indicesToRemove.get(i)); } + writeBlobSessionsAsync(); // Remove the package from the committer and leasee list final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(UserHandle.getUserId(uid)); + indicesToRemove.clear(); for (int i = 0, count = userBlobs.size(); i < count; ++i) { final BlobMetadata blobMetadata = userBlobs.valueAt(i); blobMetadata.removeCommitter(packageName, uid); blobMetadata.removeLeasee(packageName, uid); + // Delete the blob if it doesn't have any active leases. + if (!blobMetadata.hasLeases()) { + blobMetadata.getBlobFile().delete(); + indicesToRemove.add(i); + } + } + for (int i = 0, count = indicesToRemove.size(); i < count; ++i) { + userBlobs.removeAt(indicesToRemove.get(i)); } - // TODO: clean-up blobs which doesn't have any active leases. + writeBlobsInfoAsync(); } } @@ -620,6 +665,80 @@ public class BlobStoreManagerService extends SystemService { } } + void runClearAllSessions(@UserIdInt int userId) { + synchronized (mBlobsLock) { + if (userId == UserHandle.USER_ALL) { + mSessions.clear(); + } else { + mSessions.remove(userId); + } + writeBlobSessionsAsync(); + } + } + + void runClearAllBlobs(@UserIdInt int userId) { + synchronized (mBlobsLock) { + if (userId == UserHandle.USER_ALL) { + mBlobsMap.clear(); + } else { + mBlobsMap.remove(userId); + } + writeBlobsInfoAsync(); + } + } + + @GuardedBy("mBlobsLock") + private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { + for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { + final int userId = mSessions.keyAt(i); + if (!dumpArgs.shouldDumpUser(userId)) { + continue; + } + final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); + fout.println("List of sessions in user #" + + userId + " (" + userSessions.size() + "):"); + fout.increaseIndent(); + for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { + final long sessionId = userSessions.keyAt(j); + final BlobStoreSession session = userSessions.valueAt(j); + if (!dumpArgs.shouldDumpSession(session.getOwnerPackageName(), + session.getOwnerUid(), session.getSessionId())) { + continue; + } + fout.println("Session #" + sessionId); + fout.increaseIndent(); + session.dump(fout, dumpArgs); + fout.decreaseIndent(); + } + fout.decreaseIndent(); + } + } + + @GuardedBy("mBlobsLock") + private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final int userId = mBlobsMap.keyAt(i); + if (!dumpArgs.shouldDumpUser(userId)) { + continue; + } + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); + fout.println("List of blobs in user #" + + userId + " (" + userBlobs.size() + "):"); + fout.increaseIndent(); + for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { + final BlobMetadata blobMetadata = userBlobs.valueAt(j); + if (!dumpArgs.shouldDumpBlob(blobMetadata.blobId)) { + continue; + } + fout.println("Blob #" + blobMetadata.blobId); + fout.increaseIndent(); + blobMetadata.dump(fout, dumpArgs); + fout.decreaseIndent(); + } + fout.decreaseIndent(); + } + } + private class PackageChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -658,10 +777,9 @@ public class BlobStoreManagerService extends SystemService { @IntRange(from = 1) public long createSession(@NonNull BlobHandle blobHandle, @NonNull String packageName) { - Preconditions.checkNotNull(blobHandle, "blobHandle must not be null"); - Preconditions.checkNotNull(packageName, "packageName must not be null"); - // TODO: verify blobHandle.algorithm is sha-256 - // TODO: assert blobHandle is valid. + Objects.requireNonNull(blobHandle, "blobHandle must not be null"); + blobHandle.assertIsValid(); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -682,7 +800,7 @@ public class BlobStoreManagerService extends SystemService { @NonNull String packageName) { Preconditions.checkArgumentPositive(sessionId, "sessionId must be positive: " + sessionId); - Preconditions.checkNotNull(packageName, "packageName must not be null"); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -695,7 +813,7 @@ public class BlobStoreManagerService extends SystemService { @NonNull String packageName) { Preconditions.checkArgumentPositive(sessionId, "sessionId must be positive: " + sessionId); - Preconditions.checkNotNull(packageName, "packageName must not be null"); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -706,8 +824,9 @@ public class BlobStoreManagerService extends SystemService { @Override public ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle, @NonNull String packageName) { - Preconditions.checkNotNull(blobHandle, "blobHandle must not be null"); - Preconditions.checkNotNull(packageName, "packageName must not be null"); + Objects.requireNonNull(blobHandle, "blobHandle must not be null"); + blobHandle.assertIsValid(); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -727,24 +846,27 @@ public class BlobStoreManagerService extends SystemService { @Override public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId, - @CurrentTimeSecondsLong long leaseTimeoutSecs, @NonNull String packageName) { - Preconditions.checkNotNull(blobHandle, "blobHandle must not be null"); - Preconditions.checkNotNull(packageName, "packageName must not be null"); - Preconditions.checkArgumentPositive(descriptionResId, - "descriptionResId must be positive; value=" + descriptionResId); + @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) { + Objects.requireNonNull(blobHandle, "blobHandle must not be null"); + blobHandle.assertIsValid(); + Preconditions.checkArgument(ResourceId.isValid(descriptionResId), + "descriptionResId is not valid"); + Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis, + "leaseExpiryTimeMillis must not be negative"); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); - acquireLeaseInternal(blobHandle, descriptionResId, leaseTimeoutSecs, + acquireLeaseInternal(blobHandle, descriptionResId, leaseExpiryTimeMillis, callingUid, packageName); } @Override public void releaseLease(@NonNull BlobHandle blobHandle, @NonNull String packageName) { - Preconditions.checkNotNull(blobHandle, "blobHandle must not be null"); - Preconditions.checkNotNull(packageName, "packageName must not be null"); - + Objects.requireNonNull(blobHandle, "blobHandle must not be null"); + blobHandle.assertIsValid(); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -754,7 +876,7 @@ public class BlobStoreManagerService extends SystemService { @Override public void waitForIdle(@NonNull RemoteCallback remoteCallback) { - Preconditions.checkNotNull(remoteCallback, "remoteCallback must not be null"); + Objects.requireNonNull(remoteCallback, "remoteCallback must not be null"); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Caller is not allowed to call this; caller=" + Binder.getCallingUid()); @@ -766,47 +888,164 @@ public class BlobStoreManagerService extends SystemService { public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { // TODO: add proto-based version of this. - if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, writer)) return; + + final DumpArgs dumpArgs = DumpArgs.parse(args); final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); synchronized (mBlobsLock) { fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId); fout.println(); - for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { - final int userId = mSessions.keyAt(i); - final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); - fout.println("List of sessions in user #" - + userId + " (" + userSessions.size() + "):"); - fout.increaseIndent(); - for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { - final long sessionId = userSessions.keyAt(j); - final BlobStoreSession session = userSessions.valueAt(j); - fout.println("Session #" + sessionId); - fout.increaseIndent(); - session.dump(fout); - fout.decreaseIndent(); - } - fout.decreaseIndent(); + + if (dumpArgs.shouldDumpSessions()) { + dumpSessionsLocked(fout, dumpArgs); + fout.println(); + } + if (dumpArgs.shouldDumpBlobs()) { + dumpBlobsLocked(fout, dumpArgs); + fout.println(); } + } + } + + @Override + public int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + return (new BlobStoreManagerShellCommand(BlobStoreManagerService.this)).exec(this, + in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); + } + } + + static final class DumpArgs { + private boolean mDumpFull; + private final ArrayList<String> mDumpPackages = new ArrayList<>(); + private final ArrayList<Integer> mDumpUids = new ArrayList<>(); + private final ArrayList<Integer> mDumpUserIds = new ArrayList<>(); + private final ArrayList<Long> mDumpBlobIds = new ArrayList<>(); + private boolean mDumpOnlySelectedSections; + private boolean mDumpSessions; + private boolean mDumpBlobs; + + public boolean shouldDumpSession(String packageName, int uid, long blobId) { + if (!CollectionUtils.isEmpty(mDumpPackages) + && mDumpPackages.indexOf(packageName) < 0) { + return false; + } + if (!CollectionUtils.isEmpty(mDumpUids) + && mDumpUids.indexOf(uid) < 0) { + return false; + } + if (!CollectionUtils.isEmpty(mDumpBlobIds) + && mDumpBlobIds.indexOf(blobId) < 0) { + return false; + } + return true; + } + + public boolean shouldDumpSessions() { + if (!mDumpOnlySelectedSections) { + return true; + } + return mDumpSessions; + } + + public boolean shouldDumpBlobs() { + if (!mDumpOnlySelectedSections) { + return true; + } + return mDumpBlobs; + } + + public boolean shouldDumpBlob(long blobId) { + return CollectionUtils.isEmpty(mDumpBlobIds) + || mDumpBlobIds.indexOf(blobId) >= 0; + } + + public boolean shouldDumpFull() { + return mDumpFull; + } + + public boolean shouldDumpUser(int userId) { + return CollectionUtils.isEmpty(mDumpUserIds) + || mDumpUserIds.indexOf(userId) >= 0; + } + + private DumpArgs() {} - fout.print("\n\n"); - - for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { - final int userId = mBlobsMap.keyAt(i); - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); - fout.println("List of blobs in user #" - + userId + " (" + userBlobs.size() + "):"); - fout.increaseIndent(); - for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { - final BlobMetadata blobMetadata = userBlobs.valueAt(j); - fout.println("Blob #" + blobMetadata.blobId); - fout.increaseIndent(); - blobMetadata.dump(fout); - fout.decreaseIndent(); + public static DumpArgs parse(String[] args) { + final DumpArgs dumpArgs = new DumpArgs(); + if (args == null) { + return dumpArgs; + } + + for (int i = 0; i < args.length; ++i) { + final String opt = args[i]; + if ("--full".equals(opt) || "-f".equals(opt)) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { + dumpArgs.mDumpFull = true; } - fout.decreaseIndent(); + } else if ("--sessions".equals(opt)) { + dumpArgs.mDumpOnlySelectedSections = true; + dumpArgs.mDumpSessions = true; + } else if ("--blobs".equals(opt)) { + dumpArgs.mDumpOnlySelectedSections = true; + dumpArgs.mDumpBlobs = true; + } else if ("--package".equals(opt) || "-p".equals(opt)) { + dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName")); + } else if ("--uid".equals(opt) || "-u".equals(opt)) { + dumpArgs.mDumpUids.add(getIntArgRequired(args, ++i, "uid")); + } else if ("--user".equals(opt)) { + dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId")); + } else if ("--blob".equals(opt) || "-b".equals(opt)) { + dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId")); + } else { + // Everything else is assumed to be blob ids. + dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId")); } } + return dumpArgs; + } + + private static String getStringArgRequired(String[] args, int index, String argName) { + if (index >= args.length) { + throw new IllegalArgumentException("Missing " + argName); + } + return args[index]; + } + + private static int getIntArgRequired(String[] args, int index, String argName) { + if (index >= args.length) { + throw new IllegalArgumentException("Missing " + argName); + } + final int value; + try { + value = Integer.parseInt(args[index]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]); + } + return value; + } + + private static long getLongArgRequired(String[] args, int index, String argName) { + if (index >= args.length) { + throw new IllegalArgumentException("Missing " + argName); + } + final long value; + try { + value = Long.parseLong(args[index]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]); + } + return value; + } + } + + @VisibleForTesting + static class Injector { + public Handler initializeMessageHandler() { + return BlobStoreManagerService.initializeMessageHandler(); } } }
\ No newline at end of file diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java new file mode 100644 index 000000000000..3ac30f8fff6c --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java @@ -0,0 +1,111 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.blob; + +import android.os.ShellCommand; +import android.os.UserHandle; + +import java.io.PrintWriter; + +class BlobStoreManagerShellCommand extends ShellCommand { + + private final BlobStoreManagerService mService; + + BlobStoreManagerShellCommand(BlobStoreManagerService blobStoreManagerService) { + mService = blobStoreManagerService; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(null); + } + final PrintWriter pw = getOutPrintWriter(); + switch (cmd) { + case "clear-all-sessions": + return runClearAllSessions(pw); + case "clear-all-blobs": + return runClearAllBlobs(pw); + default: + return handleDefaultCommands(cmd); + } + } + + private int runClearAllSessions(PrintWriter pw) { + final ParsedArgs args = new ParsedArgs(); + args.userId = UserHandle.USER_ALL; + + if (parseOptions(pw, args) < 0) { + return -1; + } + + mService.runClearAllSessions(args.userId); + return 0; + } + + private int runClearAllBlobs(PrintWriter pw) { + final ParsedArgs args = new ParsedArgs(); + args.userId = UserHandle.USER_ALL; + + if (parseOptions(pw, args) < 0) { + return -1; + } + + mService.runClearAllBlobs(args.userId); + return 0; + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("BlobStore service (blob_store) commands:"); + pw.println("help"); + pw.println(" Print this help text."); + pw.println(); + pw.println("clear-all-sessions [-u | --user USER_ID]"); + pw.println(" Remove all sessions."); + pw.println(" Options:"); + pw.println(" -u or --user: specify which user's sessions to be removed;"); + pw.println(" If not specified, sessions in all users are removed."); + pw.println(); + pw.println("clear-all-blobs [-u | --user USER_ID]"); + pw.println(" Remove all blobs."); + pw.println(" Options:"); + pw.println(" -u or --user: specify which user's blobs to be removed;"); + pw.println(" If not specified, blobs in all users are removed."); + pw.println(); + } + + private int parseOptions(PrintWriter pw, ParsedArgs args) { + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-u": + case "--user": + args.userId = Integer.parseInt(getNextArgRequired()); + break; + default: + pw.println("Error: unknown option '" + opt + "'"); + return -1; + } + } + return 0; + } + + private static class ParsedArgs { + public int userId; + } +} 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 7d1c16653383..54a299722754 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -47,9 +47,11 @@ import android.util.ExceptionUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; +import com.android.server.blob.BlobStoreManagerService.DumpArgs; import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; import org.xmlpull.v1.XmlPullParser; @@ -62,9 +64,11 @@ import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Objects; /** TODO: add doc */ -public class BlobStoreSession extends IBlobStoreSession.Stub { +@VisibleForTesting +class BlobStoreSession extends IBlobStoreSession.Stub { static final int STATE_OPENED = 1; static final int STATE_CLOSED = 0; @@ -78,10 +82,10 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { private final Context mContext; private final SessionStateChangeListener mListener; - public final BlobHandle blobHandle; - public final long sessionId; - public final int ownerUid; - public final String ownerPackageName; + private final BlobHandle mBlobHandle; + private final long mSessionId; + private final int mOwnerUid; + private final String mOwnerPackageName; // Do not access this directly, instead use getSessionFile(). private File mSessionFile; @@ -101,15 +105,31 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle, int ownerUid, String ownerPackageName, SessionStateChangeListener listener) { this.mContext = context; - this.blobHandle = blobHandle; - this.sessionId = sessionId; - this.ownerUid = ownerUid; - this.ownerPackageName = ownerPackageName; + this.mBlobHandle = blobHandle; + this.mSessionId = sessionId; + this.mOwnerUid = ownerUid; + this.mOwnerPackageName = ownerPackageName; this.mListener = listener; } + public BlobHandle getBlobHandle() { + return mBlobHandle; + } + + public long getSessionId() { + return mSessionId; + } + + public int getOwnerUid() { + return mOwnerUid; + } + + public String getOwnerPackageName() { + return mOwnerPackageName; + } + boolean hasAccess(int callingUid, String callingPackageName) { - return ownerUid == callingUid && ownerPackageName.equals(callingPackageName); + return mOwnerUid == callingUid && mOwnerPackageName.equals(callingPackageName); } void open() { @@ -155,6 +175,8 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { @NonNull public ParcelFileDescriptor openWrite(@BytesLong long offsetBytes, @BytesLong long lengthBytes) { + Preconditions.checkArgumentNonnegative(offsetBytes, "offsetBytes must not be negative"); + assertCallerIsOwner(); synchronized (mSessionLock) { if (mState != STATE_OPENED) { @@ -242,7 +264,7 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) { assertCallerIsOwner(); - Preconditions.checkNotNull(packageName, "packageName must not be null"); + Objects.requireNonNull(packageName, "packageName must not be null"); synchronized (mSessionLock) { if (mState != STATE_OPENED) { throw new IllegalStateException("Not allowed to change access type in state: " @@ -280,7 +302,9 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { public boolean isPackageAccessAllowed(@NonNull String packageName, @NonNull byte[] certificate) { assertCallerIsOwner(); - Preconditions.checkNotNull(packageName, "packageName must not be null"); + Objects.requireNonNull(packageName, "packageName must not be null"); + Preconditions.checkByteArrayNotEmpty(certificate, "certificate"); + synchronized (mSessionLock) { if (mState != STATE_OPENED) { throw new IllegalStateException("Not allowed to get access type in state: " @@ -357,12 +381,12 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { void verifyBlobData() { byte[] actualDigest = null; try { - actualDigest = FileUtils.digest(getSessionFile(), blobHandle.algorithm); + actualDigest = FileUtils.digest(getSessionFile(), mBlobHandle.algorithm); } catch (IOException | NoSuchAlgorithmException e) { Slog.e(TAG, "Error computing the digest", e); } synchronized (mSessionLock) { - if (actualDigest != null && Arrays.equals(actualDigest, blobHandle.digest)) { + if (actualDigest != null && Arrays.equals(actualDigest, mBlobHandle.digest)) { mState = STATE_VERIFIED_VALID; // Commit callback will be sent once the data is persisted. } else { @@ -401,7 +425,7 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { @Nullable File getSessionFile() { if (mSessionFile == null) { - mSessionFile = BlobStoreConfig.prepareBlobFile(sessionId); + mSessionFile = BlobStoreConfig.prepareBlobFile(mSessionId); } return mSessionFile; } @@ -425,20 +449,20 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { private void assertCallerIsOwner() { final int callingUid = Binder.getCallingUid(); - if (callingUid != ownerUid) { - throw new SecurityException(ownerUid + " is not the session owner"); + if (callingUid != mOwnerUid) { + throw new SecurityException(mOwnerUid + " is not the session owner"); } } - void dump(IndentingPrintWriter fout) { + void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { synchronized (mSessionLock) { fout.println("state: " + stateToString(mState)); - fout.println("ownerUid: " + ownerUid); - fout.println("ownerPkg: " + ownerPackageName); + fout.println("ownerUid: " + mOwnerUid); + fout.println("ownerPkg: " + mOwnerPackageName); fout.println("blobHandle:"); fout.increaseIndent(); - blobHandle.dump(fout); + mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); fout.decreaseIndent(); fout.println("accessMode:"); @@ -452,12 +476,12 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { void writeToXml(@NonNull XmlSerializer out) throws IOException { synchronized (mSessionLock) { - XmlUtils.writeLongAttribute(out, ATTR_ID, sessionId); - XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, ownerPackageName); - XmlUtils.writeIntAttribute(out, ATTR_UID, ownerUid); + XmlUtils.writeLongAttribute(out, ATTR_ID, mSessionId); + XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, mOwnerPackageName); + XmlUtils.writeIntAttribute(out, ATTR_UID, mOwnerUid); out.startTag(null, TAG_BLOB_HANDLE); - blobHandle.writeToXml(out); + mBlobHandle.writeToXml(out); out.endTag(null, TAG_BLOB_HANDLE); out.startTag(null, TAG_ACCESS_MODE); diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl index bdd1da7bf3d3..b94928f09ae0 100644 --- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl +++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl @@ -61,4 +61,11 @@ interface IStatsCompanionService { /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */ oneway void triggerUidSnapshot(); + + /** + * Ask StatsCompanionService if the given permission is allowed for a particular process + * and user ID. statsd is incapable of doing this check itself because checkCallingPermission + * is not currently supported by libbinder_ndk. + */ + boolean checkPermission(String permission, int pid, int uid); } diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index 1e92826ee8a0..3e9a488fb5b8 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -516,6 +516,11 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } + @Override // Binder call + public boolean checkPermission(String permission, int pid, int uid) { + StatsCompanion.enforceStatsCompanionPermission(mContext); + return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED; + } // Statsd related code diff --git a/api/current.txt b/api/current.txt index 39b52545f1d5..5ac58d043660 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2875,9 +2875,17 @@ package android.accessibilityservice { method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>); field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14 field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13 + field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a + field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b + field public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; // 0x1c + field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19 field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15 field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17 field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16 + field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e + field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f + field public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; // 0x20 + field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18 field public static final int GESTURE_DOUBLE_TAP = 17; // 0x11 field public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18; // 0x12 @@ -8631,7 +8639,7 @@ package android.bluetooth { field public static final String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED"; field public static final String ACTION_ACL_DISCONNECTED = "android.bluetooth.device.action.ACL_DISCONNECTED"; field public static final String ACTION_ACL_DISCONNECT_REQUESTED = "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED"; - field public static final String ACTION_ALIAS_CHANGED = "android.bluetooth.action.ALIAS_CHANGED"; + field public static final String ACTION_ALIAS_CHANGED = "android.bluetooth.device.action.ALIAS_CHANGED"; field public static final String ACTION_BOND_STATE_CHANGED = "android.bluetooth.device.action.BOND_STATE_CHANGED"; field public static final String ACTION_CLASS_CHANGED = "android.bluetooth.device.action.CLASS_CHANGED"; field public static final String ACTION_FOUND = "android.bluetooth.device.action.FOUND"; @@ -26729,6 +26737,7 @@ package android.media { method public int getVolume(); method public int getVolumeHandling(); method public int getVolumeMax(); + method public boolean isSystemRoute(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2 field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1 @@ -31129,11 +31138,6 @@ package android.net.wifi { field @Deprecated public static final String[] strings; } - @Deprecated public static class WifiConfiguration.SuiteBCipher { - field @Deprecated public static final int ECDHE_ECDSA = 0; // 0x0 - field @Deprecated public static final int ECDHE_RSA = 1; // 0x1 - } - public class WifiEnterpriseConfig implements android.os.Parcelable { ctor public WifiEnterpriseConfig(); ctor public WifiEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig); @@ -31616,6 +31620,7 @@ package android.net.wifi.hotspot2 { method public android.net.wifi.hotspot2.pps.Credential getCredential(); method public android.net.wifi.hotspot2.pps.HomeSp getHomeSp(); method public long getSubscriptionExpirationTimeInMillis(); + method @NonNull public String getUniqueId() throws java.lang.IllegalStateException; method public boolean isOsuProvisioned(); method public void setCredential(android.net.wifi.hotspot2.pps.Credential); method public void setHomeSp(android.net.wifi.hotspot2.pps.HomeSp); @@ -31796,8 +31801,8 @@ package android.net.wifi.p2p { method public boolean isGroupOwner(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pGroup> CREATOR; - field public static final int PERSISTENT_NET_ID = -2; // 0xfffffffe - field public static final int TEMPORARY_NET_ID = -1; // 0xffffffff + field public static final int NETWORK_ID_PERSISTENT = -2; // 0xfffffffe + field public static final int NETWORK_ID_TEMPORARY = -1; // 0xffffffff } public class WifiP2pInfo implements android.os.Parcelable { @@ -46381,7 +46386,6 @@ package android.telephony { public final class BarringInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.telephony.BarringInfo.BarringServiceInfo getBarringServiceInfo(int); - method public boolean isServiceBarred(int); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int BARRING_SERVICE_TYPE_CS_FALLBACK = 5; // 0x5 field public static final int BARRING_SERVICE_TYPE_CS_SERVICE = 0; // 0x0 @@ -54811,7 +54815,6 @@ package android.view { method public final void removeOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener); method public boolean requestFeature(int); method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int); - method public void resetOnContentApplyWindowInsetsListener(); method public abstract void restoreHierarchyState(android.os.Bundle); method public abstract android.os.Bundle saveHierarchyState(); method public void setAllowEnterTransitionOverlap(boolean); @@ -54829,6 +54832,7 @@ package android.view { method public abstract void setContentView(android.view.View); method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams); method public abstract void setDecorCaptionShade(int); + method public void setDecorFitsSystemWindows(boolean); method protected void setDefaultWindowFormat(int); method public void setDimAmount(float); method public void setElevation(float); @@ -54850,7 +54854,6 @@ package android.view { method public abstract void setNavigationBarColor(@ColorInt int); method public void setNavigationBarContrastEnforced(boolean); method public void setNavigationBarDividerColor(@ColorInt int); - method public void setOnContentApplyWindowInsetsListener(@Nullable android.view.Window.OnContentApplyWindowInsetsListener); method public void setPreferMinimalPostProcessing(boolean); method public void setReenterTransition(android.transition.Transition); method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable); @@ -54945,10 +54948,6 @@ package android.view { method @Nullable public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int); } - public static interface Window.OnContentApplyWindowInsetsListener { - method @NonNull public android.util.Pair<android.graphics.Insets,android.view.WindowInsets> onContentApplyWindowInsets(@NonNull android.view.WindowInsets); - } - public static interface Window.OnFrameMetricsAvailableListener { method public void onFrameMetricsAvailable(android.view.Window, android.view.FrameMetrics, int); } @@ -55099,16 +55098,13 @@ package android.view { } public interface WindowInsetsController { - method public default void controlInputMethodAnimation(long, @Nullable android.view.animation.Interpolator, @NonNull android.view.WindowInsetsAnimationControlListener); method public void controlWindowInsetsAnimation(int, long, @Nullable android.view.animation.Interpolator, @NonNull android.view.WindowInsetsAnimationControlListener); method public int getSystemBarsAppearance(); method public int getSystemBarsBehavior(); method public void hide(int); - method public default void hideInputMethod(); method public void setSystemBarsAppearance(int, int); method public void setSystemBarsBehavior(int); method public void show(int); - method public default void showInputMethod(); field public static final int APPEARANCE_LIGHT_NAVIGATION_BARS = 16; // 0x10 field public static final int APPEARANCE_LIGHT_STATUS_BARS = 8; // 0x8 field public static final int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1; // 0x1 diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 1a2cb74a5aad..d11801b359c8 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -6,7 +6,7 @@ package android.app.timedetector { method public void addDebugInfo(@NonNull java.util.List<java.lang.String>); method public int describeContents(); method @NonNull public java.util.List<java.lang.String> getDebugInfo(); - method public int getPhoneId(); + method public int getSlotIndex(); method @Nullable public android.os.TimestampedValue<java.lang.Long> getUtcTime(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.timedetector.PhoneTimeSuggestion> CREATOR; @@ -34,8 +34,8 @@ package android.app.timezonedetector { method public int describeContents(); method @NonNull public java.util.List<java.lang.String> getDebugInfo(); method public int getMatchType(); - method public int getPhoneId(); method public int getQuality(); + method public int getSlotIndex(); method @Nullable public String getZoneId(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.timezonedetector.PhoneTimeZoneSuggestion> CREATOR; diff --git a/api/system-current.txt b/api/system-current.txt index d7a591dfa508..ff2d308da01c 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1482,8 +1482,9 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public long getDiscoveryEndMillis(); method public boolean isBleScanAlwaysAvailable(); method public boolean isLeEnabled(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeActiveDevice(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setActiveDevice(@Nullable android.bluetooth.BluetoothDevice, int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setScanMode(int, int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setScanMode(int); field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; @@ -1556,7 +1557,7 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setAlias(@NonNull String); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setAlias(@NonNull String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMessageAccessPermission(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, @NonNull byte[]); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int); @@ -4104,7 +4105,6 @@ package android.location { public class Location implements android.os.Parcelable { method public boolean isComplete(); method public void makeComplete(); - method public void setExtraLocation(@Nullable String, @Nullable android.location.Location); method public void setIsFromMockProvider(boolean); field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation"; } @@ -7514,6 +7514,7 @@ package android.net.wifi { method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus getNetworkSelectionStatus(); method @Deprecated @NonNull public String getPrintableSsid(); method @Deprecated @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings(); + method @Deprecated public int getRecentFailureReason(); method @Deprecated @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration(); method @Deprecated public boolean hasNoInternetAccess(); method @Deprecated public boolean isEphemeral(); @@ -7531,6 +7532,8 @@ package android.net.wifi { field @Deprecated public static final int METERED_OVERRIDE_NOT_METERED = 2; // 0x2 field @Deprecated public static final int RANDOMIZATION_NONE = 0; // 0x0 field @Deprecated public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1 + field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11 + field @Deprecated public static final int RECENT_FAILURE_NONE = 0; // 0x0 field @Deprecated public boolean allowAutojoin; field @Deprecated public int apBand; field @Deprecated public int carrierId; @@ -7546,7 +7549,6 @@ package android.net.wifi { field @Deprecated public int numAssociation; field @Deprecated public int numScorerOverride; field @Deprecated public int numScorerOverrideAndSwitchedNetwork; - field @Deprecated @NonNull public final android.net.wifi.WifiConfiguration.RecentFailure recentFailure; field @Deprecated public boolean requirePMF; field @Deprecated @Nullable public String saePasswordId; field @Deprecated public boolean shared; @@ -7592,12 +7594,6 @@ package android.net.wifi { method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionStatus(int); } - @Deprecated public static class WifiConfiguration.RecentFailure { - method @Deprecated public int getAssociationStatus(); - field @Deprecated public static final int NONE = 0; // 0x0 - field @Deprecated public static final int STATUS_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11 - } - public class WifiEnterpriseConfig implements android.os.Parcelable { method @Nullable public String[] getCaCertificateAliases(); method @NonNull public String getCaPath(); @@ -9173,7 +9169,12 @@ package android.os.storage { method @WorkerThread public void allocateBytes(java.io.FileDescriptor, long, @RequiresPermission int) throws java.io.IOException; method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException; method public static boolean hasIsolatedStorage(); + method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException; field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1 + field public static final int QUOTA_TYPE_MEDIA_AUDIO = 2; // 0x2 + field public static final int QUOTA_TYPE_MEDIA_IMAGE = 1; // 0x1 + field public static final int QUOTA_TYPE_MEDIA_NONE = 0; // 0x0 + field public static final int QUOTA_TYPE_MEDIA_VIDEO = 3; // 0x3 } public final class StorageVolume implements android.os.Parcelable { @@ -10111,6 +10112,7 @@ package android.service.autofill.augmented { ctor public AugmentedAutofillService(); method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]); method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]); + method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory(); method public void onConnected(); method public void onDisconnected(); method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback); @@ -10140,6 +10142,7 @@ package android.service.autofill.augmented { public static final class FillResponse.Builder { ctor public FillResponse.Builder(); method @NonNull public android.service.autofill.augmented.FillResponse build(); + method @NonNull public android.service.autofill.augmented.FillResponse.Builder setClientState(@Nullable android.os.Bundle); method @NonNull public android.service.autofill.augmented.FillResponse.Builder setFillWindow(@Nullable android.service.autofill.augmented.FillWindow); method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineSuggestions(@Nullable java.util.List<android.service.autofill.Dataset>); } diff --git a/api/test-current.txt b/api/test-current.txt index 1e81943705b4..fda3ab4084a8 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1271,7 +1271,6 @@ package android.location { public class Location implements android.os.Parcelable { method public void makeComplete(); - method public void setExtraLocation(@Nullable String, @Nullable android.location.Location); field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation"; } @@ -1387,6 +1386,10 @@ package android.media { method public int getMaxMacroBlocks(); } + public final class MediaRoute2Info implements android.os.Parcelable { + method @NonNull public String getOriginalId(); + } + public final class PlaybackParams implements android.os.Parcelable { method public int getAudioStretchMode(); method public android.media.PlaybackParams setAudioStretchMode(int); @@ -3101,6 +3104,7 @@ package android.service.autofill.augmented { ctor public AugmentedAutofillService(); method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]); method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]); + method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory(); method public void onConnected(); method public void onDisconnected(); method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback); @@ -3130,6 +3134,7 @@ package android.service.autofill.augmented { public static final class FillResponse.Builder { ctor public FillResponse.Builder(); method @NonNull public android.service.autofill.augmented.FillResponse build(); + method @NonNull public android.service.autofill.augmented.FillResponse.Builder setClientState(@Nullable android.os.Bundle); method @NonNull public android.service.autofill.augmented.FillResponse.Builder setFillWindow(@Nullable android.service.autofill.augmented.FillWindow); method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineSuggestions(@Nullable java.util.List<android.service.autofill.Dataset>); } diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index fdc7fce3b884..bd4397afaeb7 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -66,13 +66,11 @@ cc_defaults { "src/config/ConfigManager.cpp", "src/external/GpuStatsPuller.cpp", "src/external/Perfetto.cpp", - "src/external/PowerStatsPuller.cpp", "src/external/PullResultReceiver.cpp", "src/external/puller_util.cpp", "src/external/StatsCallbackPuller.cpp", "src/external/StatsPuller.cpp", "src/external/StatsPullerManager.cpp", - "src/external/SubsystemSleepStatePuller.cpp", "src/external/TrainInfoPuller.cpp", "src/FieldValue.cpp", "src/guardrail/StatsdStats.cpp", @@ -121,9 +119,6 @@ cc_defaults { static_libs: [ "android.frameworks.stats@1.0", - "android.hardware.power.stats@1.0", - "android.hardware.power@1.0", - "android.hardware.power@1.1", "libbase", "libcutils", "liblog", diff --git a/cmds/statsd/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp index bdfdb2e00ac0..30dfe3279829 100644 --- a/cmds/statsd/benchmark/log_event_benchmark.cpp +++ b/cmds/statsd/benchmark/log_event_benchmark.cpp @@ -39,7 +39,7 @@ static void BM_LogEventCreation(benchmark::State& state) { uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD]; size_t size = createAndParseStatsEvent(msg); while (state.KeepRunning()) { - benchmark::DoNotOptimize(LogEvent(msg, size, /*uid=*/ 1000)); + benchmark::DoNotOptimize(LogEvent(msg, size, /*uid=*/ 1000, /*pid=*/ 1001)); } } BENCHMARK(BM_LogEventCreation); diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index c1a8d69191d2..05281f79592f 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -30,7 +30,6 @@ #include <android-base/stringprintf.h> #include <android-base/strings.h> #include <binder/IPCThreadState.h> -#include <binder/IServiceManager.h> #include <binder/PermissionController.h> #include <cutils/multiuser.h> #include <dirent.h> @@ -77,6 +76,25 @@ static binder::Status exception(uint32_t code, const std::string& msg) { return binder::Status::fromExceptionCode(code, String8(msg.c_str())); } + +static bool checkPermission(const char* permission) { + sp<IStatsCompanionService> scs = getStatsCompanionService(); + if (scs == nullptr) { + return false; + } + + bool success; + pid_t pid = IPCThreadState::self()->getCallingPid(); + uid_t uid = IPCThreadState::self()->getCallingUid(); + + binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success); + if (!status.isOk()) { + return false; + } + return success; +} + + binder::Status checkUid(uid_t expectedUid) { uid_t uid = IPCThreadState::self()->getCallingUid(); if (uid == expectedUid || uid == AID_ROOT) { @@ -97,11 +115,11 @@ binder::Status checkDumpAndUsageStats(const String16& packageName) { } // Caller must be granted these permissions - if (!checkCallingPermission(String16(kPermissionDump))) { + if (!checkPermission(kPermissionDump)) { return exception(binder::Status::EX_SECURITY, StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionDump)); } - if (!checkCallingPermission(String16(kPermissionUsage))) { + if (!checkPermission(kPermissionUsage)) { return exception(binder::Status::EX_SECURITY, StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionUsage)); } @@ -285,7 +303,7 @@ status_t StatsService::onTransact(uint32_t code, const Parcel& data, Parcel* rep * TODO: Come up with a more robust method of enacting <serviceutils/PriorityDumper.h>. */ status_t StatsService::dump(int fd, const Vector<String16>& args) { - if (!checkCallingPermission(String16(kPermissionDump))) { + if (!checkPermission(kPermissionDump)) { return PERMISSION_DENIED; } int lastArg = args.size() - 1; @@ -914,7 +932,7 @@ status_t StatsService::cmd_clear_puller_cache(int out) { IPCThreadState* ipc = IPCThreadState::self(); VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid()); - if (checkCallingPermission(String16(kPermissionDump))) { + if (checkPermission(kPermissionDump)) { int cleared = mPullerManager->ForceClearPullerCache(); dprintf(out, "Puller removed %d cached data!\n", cleared); return NO_ERROR; @@ -927,7 +945,7 @@ status_t StatsService::cmd_print_logs(int out, const Vector<String8>& args) { IPCThreadState* ipc = IPCThreadState::self(); VLOG("StatsService::cmd_print_logs with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid()); - if (checkCallingPermission(String16(kPermissionDump))) { + if (checkPermission(kPermissionDump)) { bool enabled = true; if (args.size() >= 2) { enabled = atoi(args[1].c_str()) != 0; @@ -1314,12 +1332,12 @@ Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& tra // Root, system, and shell always have access if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) { // Caller must be granted these permissions - if (!checkCallingPermission(String16(kPermissionDump))) { + if (!checkPermission(kPermissionDump)) { return exception(binder::Status::EX_SECURITY, StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionDump)); } - if (!checkCallingPermission(String16(kPermissionUsage))) { + if (!checkPermission(kPermissionUsage)) { return exception(binder::Status::EX_SECURITY, StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionUsage)); @@ -1410,12 +1428,12 @@ Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackType // Root, system, and shell always have access if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) { // Caller must be granted these permissions - if (!checkCallingPermission(String16(kPermissionDump))) { + if (!checkPermission(kPermissionDump)) { return exception(binder::Status::EX_SECURITY, StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionDump)); } - if (!checkCallingPermission(String16(kPermissionUsage))) { + if (!checkPermission(kPermissionUsage)) { return exception(binder::Status::EX_SECURITY, StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionUsage)); diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp index 6257771b5b0d..1a11f0e37311 100644 --- a/cmds/statsd/src/external/StatsCallbackPuller.cpp +++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp @@ -68,7 +68,7 @@ bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { for (const StatsEventParcel& parcel: output) { shared_ptr<LogEvent> event = make_shared<LogEvent>( const_cast<uint8_t*>(parcel.buffer.data()), parcel.buffer.size(), - /*uid=*/-1, /*useNewSchema=*/true); + /*uid=*/-1, /*pid=*/-1, /*useNewSchema=*/true); sharedData->push_back(event); } *pullSuccess = success; diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 668c11ef55f5..fef213dad60e 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -33,9 +33,7 @@ #include "../stats_log_util.h" #include "../statscompanion_util.h" #include "GpuStatsPuller.h" -#include "PowerStatsPuller.h" #include "StatsCallbackPuller.h" -#include "SubsystemSleepStatePuller.h" #include "TrainInfoPuller.h" #include "statslog.h" @@ -55,12 +53,6 @@ const int64_t NO_ALARM_UPDATE = INT64_MAX; StatsPullerManager::StatsPullerManager() : kAllPullAtomInfo({ - // subsystem_sleep_state - {{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE}, new SubsystemSleepStatePuller()}, - - // on_device_power_measurement - {{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT}, new PowerStatsPuller()}, - // TrainInfo. {{.atomTag = android::util::TRAIN_INFO}, new TrainInfoPuller()}, diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 3827b9e21b70..9a0693a84e65 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -37,11 +37,12 @@ using std::vector; // Msg is expected to begin at the start of the serialized atom -- it should not // include the android_log_header_t or the StatsEventTag. -LogEvent::LogEvent(uint8_t* msg, uint32_t len, uint32_t uid) +LogEvent::LogEvent(uint8_t* msg, uint32_t len, int32_t uid, int32_t pid) : mBuf(msg), mRemainingLen(len), mLogdTimestampNs(time(nullptr)), - mLogUid(uid) + mLogUid(uid), + mLogPid(pid) { #ifdef NEW_ENCODING_SCHEME initNew(); @@ -52,8 +53,13 @@ LogEvent::LogEvent(uint8_t* msg, uint32_t len, uint32_t uid) #endif } -LogEvent::LogEvent(uint8_t* msg, uint32_t len, uint32_t uid, bool useNewSchema) - : mBuf(msg), mRemainingLen(len), mLogdTimestampNs(time(nullptr)), mLogUid(uid) { +LogEvent::LogEvent(uint8_t* msg, uint32_t len, int32_t uid, int32_t pid, bool useNewSchema) + : mBuf(msg), + mRemainingLen(len), + mLogdTimestampNs(time(nullptr)), + mLogUid(uid), + mLogPid(pid) +{ if (useNewSchema) { initNew(); } else { @@ -66,6 +72,7 @@ LogEvent::LogEvent(uint8_t* msg, uint32_t len, uint32_t uid, bool useNewSchema) LogEvent::LogEvent(const LogEvent& event) { mTagId = event.mTagId; mLogUid = event.mLogUid; + mLogPid = event.mLogPid; mElapsedTimestampNs = event.mElapsedTimestampNs; mLogdTimestampNs = event.mLogdTimestampNs; mValues = event.mValues; @@ -146,6 +153,7 @@ LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requi mElapsedTimestampNs = getElapsedRealtimeNs(); mTagId = android::util::BINARY_PUSH_STATE_CHANGED; mLogUid = android::IPCThreadState::self()->getCallingUid(); + mLogPid = android::IPCThreadState::self()->getCallingPid(); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode))); diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 463a1b68f885..583dae2f1b21 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -71,12 +71,12 @@ public: /** * Read a LogEvent from the socket */ - explicit LogEvent(uint8_t* msg, uint32_t len, uint32_t uid); + explicit LogEvent(uint8_t* msg, uint32_t len, int32_t uid, int32_t pid); /** * Temp constructor to use for pulled atoms until we flip the socket schema. */ - explicit LogEvent(uint8_t* msg, uint32_t len, uint32_t uid, bool useNewSchema); + explicit LogEvent(uint8_t* msg, uint32_t len, int32_t uid, int32_t pid, bool useNewSchema); /** * Constructs a LogEvent with synthetic data for testing. Must call init() before reading. @@ -123,9 +123,17 @@ public: */ inline int GetTagId() const { return mTagId; } - inline uint32_t GetUid() const { - return mLogUid; - } + /** + * Get the uid of the logging client. + * Returns -1 if the uid is unknown/has not been set. + */ + inline int32_t GetUid() const { return mLogUid; } + + /** + * Get the pid of the logging client. + * Returns -1 if the pid is unknown/has not been set. + */ + inline int32_t GetPid() const { return mLogPid; } /** * Get the nth value, starting at 1. @@ -305,9 +313,14 @@ private: // The elapsed timestamp set by statsd log writer. int64_t mElapsedTimestampNs; + // The atom tag of the event. int mTagId; - uint32_t mLogUid; + // The uid of the logging client (defaults to -1). + int32_t mLogUid = -1; + + // The pid of the logging client (defaults to -1). + int32_t mLogPid = -1; }; void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut); diff --git a/cmds/statsd/src/socket/StatsSocketListener.cpp b/cmds/statsd/src/socket/StatsSocketListener.cpp index 4308a1107039..cdb4d3a10f27 100755 --- a/cmds/statsd/src/socket/StatsSocketListener.cpp +++ b/cmds/statsd/src/socket/StatsSocketListener.cpp @@ -126,9 +126,10 @@ bool StatsSocketListener::onDataAvailable(SocketClient* cli) { uint8_t* msg = ptr + sizeof(uint32_t); uint32_t len = n - sizeof(uint32_t); uint32_t uid = cred->uid; + uint32_t pid = cred->pid; int64_t oldestTimestamp; - if (!mQueue->push(std::make_unique<LogEvent>(msg, len, uid), &oldestTimestamp)) { + if (!mQueue->push(std::make_unique<LogEvent>(msg, len, uid, pid), &oldestTimestamp)) { StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp); } diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index 1cf9fb681d61..35b0396e2fb1 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -57,9 +57,11 @@ TEST(LogEventTest, TestPrimitiveParsing) { size_t size; uint8_t* buf = stats_event_get_buffer(event, &size); - LogEvent logEvent(buf, size, /*uid=*/ 1000); + LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); const vector<FieldValue>& values = logEvent.getValues(); EXPECT_EQ(4, values.size()); @@ -103,9 +105,11 @@ TEST(LogEventTest, TestStringAndByteArrayParsing) { size_t size; uint8_t* buf = stats_event_get_buffer(event, &size); - LogEvent logEvent(buf, size, /*uid=*/ 1000); + LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); const vector<FieldValue>& values = logEvent.getValues(); EXPECT_EQ(2, values.size()); @@ -136,9 +140,11 @@ TEST(LogEventTest, TestEmptyString) { size_t size; uint8_t* buf = stats_event_get_buffer(event, &size); - LogEvent logEvent(buf, size, /*uid=*/ 1000); + LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); const vector<FieldValue>& values = logEvent.getValues(); EXPECT_EQ(1, values.size()); @@ -162,9 +168,11 @@ TEST(LogEventTest, TestByteArrayWithNullCharacter) { size_t size; uint8_t* buf = stats_event_get_buffer(event, &size); - LogEvent logEvent(buf, size, /*uid=*/ 1000); + LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); const vector<FieldValue>& values = logEvent.getValues(); EXPECT_EQ(1, values.size()); @@ -196,9 +204,11 @@ TEST(LogEventTest, TestKeyValuePairs) { size_t size; uint8_t* buf = stats_event_get_buffer(event, &size); - LogEvent logEvent(buf, size, /*uid=*/ 1000); + LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); const vector<FieldValue>& values = logEvent.getValues(); EXPECT_EQ(8, values.size()); // 2 FieldValues per key-value pair @@ -260,9 +270,11 @@ TEST(LogEventTest, TestAttributionChain) { size_t size; uint8_t* buf = stats_event_get_buffer(event, &size); - LogEvent logEvent(buf, size, /*uid=*/ 1000); + LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); const vector<FieldValue>& values = logEvent.getValues(); EXPECT_EQ(4, values.size()); // 2 per attribution node diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java index 47fc7e1cdf13..9cf1de93e344 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -19,9 +19,17 @@ package android.accessibilityservice; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD; @@ -89,7 +97,15 @@ public final class AccessibilityGestureEvent implements Parcelable { GESTURE_SWIPE_RIGHT, GESTURE_SWIPE_RIGHT_AND_UP, GESTURE_SWIPE_RIGHT_AND_LEFT, - GESTURE_SWIPE_RIGHT_AND_DOWN + GESTURE_SWIPE_RIGHT_AND_DOWN, + GESTURE_2_FINGER_SWIPE_DOWN, + GESTURE_2_FINGER_SWIPE_LEFT, + GESTURE_2_FINGER_SWIPE_RIGHT, + GESTURE_2_FINGER_SWIPE_UP, + GESTURE_3_FINGER_SWIPE_DOWN, + GESTURE_3_FINGER_SWIPE_LEFT, + GESTURE_3_FINGER_SWIPE_RIGHT, + GESTURE_3_FINGER_SWIPE_UP }) @Retention(RetentionPolicy.SOURCE) public @interface GestureId {} @@ -167,6 +183,14 @@ public final class AccessibilityGestureEvent implements Parcelable { case GESTURE_SWIPE_UP_AND_LEFT: return "GESTURE_SWIPE_UP_AND_LEFT"; case GESTURE_SWIPE_UP_AND_DOWN: return "GESTURE_SWIPE_UP_AND_DOWN"; case GESTURE_SWIPE_UP_AND_RIGHT: return "GESTURE_SWIPE_UP_AND_RIGHT"; + case GESTURE_2_FINGER_SWIPE_DOWN: return "GESTURE_2_FINGER_SWIPE_DOWN"; + case GESTURE_2_FINGER_SWIPE_LEFT: return "GESTURE_2_FINGER_SWIPE_LEFT"; + case GESTURE_2_FINGER_SWIPE_RIGHT: return "GESTURE_2_FINGER_SWIPE_RIGHT"; + case GESTURE_2_FINGER_SWIPE_UP: return "GESTURE_2_FINGER_SWIPE_UP"; + case GESTURE_3_FINGER_SWIPE_DOWN: return "GESTURE_3_FINGER_SWIPE_DOWN"; + case GESTURE_3_FINGER_SWIPE_LEFT: return "GESTURE_3_FINGER_SWIPE_LEFT"; + case GESTURE_3_FINGER_SWIPE_RIGHT: return "GESTURE_3_FINGER_SWIPE_RIGHT"; + case GESTURE_3_FINGER_SWIPE_UP: return "GESTURE_3_FINGER_SWIPE_UP"; default: return Integer.toHexString(eventType); } } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 27cd2857f38f..2165fb35a0e5 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -349,6 +349,46 @@ public abstract class AccessibilityService extends Service { public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; /** + * The user has performed a two-finger swipe up gesture on the touch screen. + */ + public static final int GESTURE_2_FINGER_SWIPE_UP = 25; + + /** + * The user has performed a two-finger swipe down gesture on the touch screen. + */ + public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; + + /** + * The user has performed a two-finger swipe left gesture on the touch screen. + */ + public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; + + /** + * The user has performed a two-finger swipe right gesture on the touch screen. + */ + public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; + + /** + * The user has performed a three-finger swipe up gesture on the touch screen. + */ + public static final int GESTURE_3_FINGER_SWIPE_UP = 29; + + /** + * The user has performed a three-finger swipe down gesture on the touch screen. + */ + public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; + + /** + * The user has performed a three-finger swipe left gesture on the touch screen. + */ + public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; + + /** + * The user has performed a three-finger swipe right gesture on the touch screen. + */ + public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; + + /** * The {@link Intent} that must be declared as handled by the service. */ public static final String SERVICE_INTERFACE = diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index dd9a2bcf9c2c..58bff7f4dc04 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -158,6 +158,24 @@ public class ActivityTaskManager { } }; + /** @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public static ITaskOrganizerController getTaskOrganizerController() { + return ITaskOrganizerControllerSingleton.get(); + } + + private static final Singleton<ITaskOrganizerController> ITaskOrganizerControllerSingleton = + new Singleton<ITaskOrganizerController>() { + @Override + protected ITaskOrganizerController create() { + try { + return getService().getTaskOrganizerController(); + } catch (RemoteException e) { + return null; + } + } + }; + /** * Sets the windowing mode for a specific task. Only works on tasks of type * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java index 19d158dedd06..28a21f767283 100644 --- a/core/java/android/app/AppCompatCallbacks.java +++ b/core/java/android/app/AppCompatCallbacks.java @@ -18,7 +18,6 @@ package android.app; import android.compat.Compatibility; import android.os.Process; -import android.util.StatsLog; import com.android.internal.compat.ChangeReporter; @@ -46,20 +45,20 @@ public final class AppCompatCallbacks extends Compatibility.Callbacks { mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length); Arrays.sort(mDisabledChanges); mChangeReporter = new ChangeReporter( - StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS); + ChangeReporter.SOURCE_APP_PROCESS); } protected void reportChange(long changeId) { - reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED); + reportChange(changeId, ChangeReporter.STATE_LOGGED); } protected boolean isChangeEnabled(long changeId) { if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) { // Not present in the disabled array - reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED); + reportChange(changeId, ChangeReporter.STATE_ENABLED); return true; } - reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED); + reportChange(changeId, ChangeReporter.STATE_DISABLED); return false; } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index c09aa1ff05a8..71cb4a403365 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -618,36 +618,22 @@ public class ApplicationPackageManager extends PackageManager { return hasSystemFeature(name, 0); } - private boolean hasSystemFeatureUncached(String name, int version) { - try { - return mPM.hasSystemFeature(name, version); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - // Make this cache relatively large. There are many system features and - // none are ever invalidated. MPTS tests suggests that the cache should - // hold at least 150 entries. - private static final int SYS_FEATURE_CACHE_SIZE = 256; - private static final String CACHE_KEY_SYS_FEATURE_PROPERTY = "cache_key.has_system_feature"; - - private class SystemFeatureQuery { + private class HasSystemFeatureQuery { public final String name; public final int version; - public SystemFeatureQuery(String n, int v) { + public HasSystemFeatureQuery(String n, int v) { name = n; version = v; } @Override public String toString() { - return String.format("SystemFeatureQuery(name=\"%s\", version=%d)", + return String.format("HasSystemFeatureQuery(name=\"%s\", version=%d)", name, version); } @Override public boolean equals(Object o) { - if (o instanceof SystemFeatureQuery) { - SystemFeatureQuery r = (SystemFeatureQuery) o; + if (o instanceof HasSystemFeatureQuery) { + HasSystemFeatureQuery r = (HasSystemFeatureQuery) o; return Objects.equals(name, r.name) && version == r.version; } else { return false; @@ -655,33 +641,41 @@ public class ApplicationPackageManager extends PackageManager { } @Override public int hashCode() { - return Objects.hashCode(name) + version; + return Objects.hashCode(name) * 13 + version; } } - private final PropertyInvalidatedCache<SystemFeatureQuery, Boolean> mSysFeatureCache = - new PropertyInvalidatedCache<SystemFeatureQuery, Boolean>( - SYS_FEATURE_CACHE_SIZE, - CACHE_KEY_SYS_FEATURE_PROPERTY) { + // Make this cache relatively large. There are many system features and + // none are ever invalidated. MPTS tests suggests that the cache should + // hold at least 150 entries. + private final static PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean> + mHasSystemFeatureCache = + new PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>( + 256, "cache_key.has_system_feature") { @Override - protected Boolean recompute(SystemFeatureQuery query) { - return hasSystemFeatureUncached(query.name, query.version); + protected Boolean recompute(HasSystemFeatureQuery query) { + try { + return ActivityThread.currentActivityThread().getPackageManager(). + hasSystemFeature(query.name, query.version); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } }; @Override public boolean hasSystemFeature(String name, int version) { - return mSysFeatureCache.query(new SystemFeatureQuery(name, version)).booleanValue(); + return mHasSystemFeatureCache.query(new HasSystemFeatureQuery(name, version)); } /** @hide */ - public void disableSysFeatureCache() { - mSysFeatureCache.disableLocal(); + public void disableHasSystemFeatureCache() { + mHasSystemFeatureCache.disableLocal(); } /** @hide */ - public static void invalidateSysFeatureCache() { - PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SYS_FEATURE_PROPERTY); + public static void invalidateHasSystemFeatureCache() { + mHasSystemFeatureCache.invalidateCache(); } @Override diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 04d3e39b29fc..57cd8941a398 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -750,7 +750,7 @@ class ContextImpl extends Context { if (type != null) { dirs = Environment.buildPaths(dirs, type); } - return ensureExternalDirsExistOrFilter(dirs); + return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */); } } @@ -765,7 +765,7 @@ class ContextImpl extends Context { public File[] getObbDirs() { synchronized (mSync) { File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName()); - return ensureExternalDirsExistOrFilter(dirs); + return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */); } } @@ -809,7 +809,10 @@ class ContextImpl extends Context { public File[] getExternalCacheDirs() { synchronized (mSync) { File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName()); - return ensureExternalDirsExistOrFilter(dirs); + // We don't try to create cache directories in-process, because they need special + // setup for accurate quota tracking. This ensures the cache dirs are always + // created through StorageManagerService. + return ensureExternalDirsExistOrFilter(dirs, false /* tryCreateInProcess */); } } @@ -817,7 +820,7 @@ class ContextImpl extends Context { public File[] getExternalMediaDirs() { synchronized (mSync) { File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName()); - return ensureExternalDirsExistOrFilter(dirs); + return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */); } } @@ -2804,24 +2807,24 @@ class ContextImpl extends Context { * Ensure that given directories exist, trying to create them if missing. If * unable to create, they are filtered by replacing with {@code null}. */ - private File[] ensureExternalDirsExistOrFilter(File[] dirs) { + private File[] ensureExternalDirsExistOrFilter(File[] dirs, boolean tryCreateInProcess) { final StorageManager sm = getSystemService(StorageManager.class); final File[] result = new File[dirs.length]; for (int i = 0; i < dirs.length; i++) { File dir = dirs[i]; if (!dir.exists()) { - if (!dir.mkdirs()) { - // recheck existence in case of cross-process race - if (!dir.exists()) { - // Failing to mkdir() may be okay, since we might not have - // enough permissions; ask vold to create on our behalf. - try { + try { + if (!tryCreateInProcess || !dir.mkdirs()) { + // recheck existence in case of cross-process race + if (!dir.exists()) { + // Failing to mkdir() may be okay, since we might not have + // enough permissions; ask vold to create on our behalf. sm.mkdirs(dir); - } catch (Exception e) { - Log.w(TAG, "Failed to ensure " + dir + ": " + e); - dir = null; } } + } catch (Exception e) { + Log.w(TAG, "Failed to ensure " + dir + ": " + e); + dir = null; } } result[i] = dir; diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 85fa7c1cdb54..503f5c56c617 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -29,6 +29,7 @@ import android.app.IProcessObserver; import android.app.IRequestFinishCallback; import android.app.IServiceConnection; import android.app.IStopUserCallback; +import android.app.ITaskOrganizerController; import android.app.ITaskStackListener; import android.app.IUiAutomationConnection; import android.app.IUidObserver; @@ -71,7 +72,6 @@ import android.view.IRecentsAnimationRunner; import android.view.ITaskOrganizer; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; -import android.view.WindowContainerTransaction; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IKeyguardDismissCallback; @@ -123,8 +123,6 @@ interface IActivityTaskManager { int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options, IBinder permissionToken, boolean ignoreTargetSecurity, int userId); - void registerTaskOrganizer(in ITaskOrganizer organizer, int windowingMode); - boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType, int userId); @@ -224,7 +222,6 @@ interface IActivityTaskManager { void setTaskResizeable(int taskId, int resizeableMode); void toggleFreeformWindowingMode(in IBinder token); void resizeTask(int taskId, in Rect bounds, int resizeMode); - void applyContainerTransaction(in WindowContainerTransaction t); void moveStackToDisplay(int stackId, int displayId); void removeStack(int stackId); @@ -364,6 +361,11 @@ interface IActivityTaskManager { in Rect tempOtherTaskBounds, in Rect tempOtherTaskInsetBounds); /** + * Returns an interface enabling the management of task organizers. + */ + ITaskOrganizerController getTaskOrganizerController(); + + /** * Sets whether we are currently in an interactive split screen resize operation where we * are changing the docked stack size. */ diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl new file mode 100644 index 000000000000..168f782d02a6 --- /dev/null +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.app.ActivityManager; +import android.view.ITaskOrganizer; +import android.view.IWindowContainer; +import android.view.WindowContainerTransaction; + +/** @hide */ +interface ITaskOrganizerController { + + /** + * Register a TaskOrganizer to manage tasks as they enter the given windowing mode. + * If there was already a TaskOrganizer for this windowing mode it will be evicted + * and receive taskVanished callbacks in the process. + */ + void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode); + + /** Apply multiple WindowContainer operations at once. */ + void applyContainerTransaction(in WindowContainerTransaction t); + + /** Creates a persistent root task in WM for a particular windowing-mode. */ + ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode); + + /** Deletes a persistent root task in WM */ + boolean deleteRootTask(IWindowContainer task); + + /** Get the root task which contains the current ime target */ + IWindowContainer getImeTarget(int display); + + /** + * Set's the root task to launch new tasks into on a display. {@code null} means no launch root + * and thus new tasks just end up directly on the display. + */ + void setLaunchRoot(int displayId, in IWindowContainer root); +} diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index fe9c64038909..662ca6eb2c19 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -16,6 +16,8 @@ package android.app; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -25,6 +27,7 @@ import android.content.res.Configuration; import android.os.Parcel; import android.os.RemoteException; import android.util.Log; +import android.view.IWindowContainer; /** * Stores information about a particular Task. @@ -138,6 +141,19 @@ public class TaskInfo { @UnsupportedAppUsage public final Configuration configuration = new Configuration(); + /** + * Used as an opaque identifier for this task. + * @hide + */ + @NonNull + public IWindowContainer token; + + /** + * The activity type of the top activity in this task. + * @hide + */ + public @WindowConfiguration.ActivityType int topActivityType; + TaskInfo() { // Do nothing } @@ -160,6 +176,11 @@ public class TaskInfo { } } + /** @hide */ + public boolean isResizable() { + return resizeMode != RESIZE_MODE_UNRESIZEABLE; + } + /** * Reads the TaskInfo from a parcel. */ @@ -186,6 +207,8 @@ public class TaskInfo { supportsSplitScreenMultiWindow = source.readBoolean(); resizeMode = source.readInt(); configuration.readFromParcel(source); + token = IWindowContainer.Stub.asInterface(source.readStrongBinder()); + topActivityType = source.readInt(); } /** @@ -221,6 +244,8 @@ public class TaskInfo { dest.writeBoolean(supportsSplitScreenMultiWindow); dest.writeInt(resizeMode); configuration.writeToParcel(dest, flags); + dest.writeStrongInterface(token); + dest.writeInt(topActivityType); } @Override @@ -234,6 +259,8 @@ public class TaskInfo { + " numActivities=" + numActivities + " lastActiveTime=" + lastActiveTime + " supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow - + " resizeMode=" + resizeMode; + + " resizeMode=" + resizeMode + + " token=" + token + + " topActivityType=" + topActivityType; } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index f71d78b40242..3676a9b2548f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -328,7 +328,7 @@ public class DevicePolicyManager { * modified by the user and the only way of resetting the device is via factory reset. * * <p>From version {@link android.os.Build.VERSION_CODES#Q}, the admin app can choose - * whether to set up a fully managed device or a work profile. For the admin app to support + * whether to set up a fully managed device or a managed profile. For the admin app to support * this, it must have an activity with intent filter {@link #ACTION_GET_PROVISIONING_MODE} and * another one with intent filter {@link #ACTION_ADMIN_POLICY_COMPLIANCE}. For example: * <pre> @@ -1781,12 +1781,13 @@ public class DevicePolicyManager { /** * Grants access to selection of KeyChain certificates on behalf of requesting apps. * Once granted the app will start receiving - * DelegatedAdminReceiver.onChoosePrivateKeyAlias. The caller (PO/DO) will + * {@link DelegatedAdminReceiver#onChoosePrivateKeyAlias}. The caller (PO/DO) will * no longer receive {@link DeviceAdminReceiver#onChoosePrivateKeyAlias}. * There can be at most one app that has this delegation. * If another app already had delegated certificate selection access, * it will lose the delegation when a new app is delegated. - * + * <p> The delegaetd app can also call {@link #grantKeyPairToApp} and + * {@link #revokeKeyPairFromApp} to directly grant KeyCain keys to other apps. * <p> Can be granted by Device Owner or Profile Owner. */ public static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection"; @@ -4313,7 +4314,7 @@ public class DevicePolicyManager { * additionally call this method on the parent instance. * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the * entire device, while calling it on the current profile instance would relinquish the device - * for personal use, removing the work profile and all policies set by the profile owner. + * for personal use, removing the managed profile and all policies set by the profile owner. * * @param flags Bit mask of additional options: currently supported flags are * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA}, @@ -4339,7 +4340,7 @@ public class DevicePolicyManager { * additionally call this method on the parent instance. * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the * entire device, while calling it on the current profile instance would relinquish the device - * for personal use, removing the work profile and all policies set by the profile owner. + * for personal use, removing the managed profile and all policies set by the profile owner. * * @param flags Bit mask of additional options: currently supported flags are * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and @@ -11584,12 +11585,14 @@ public class DevicePolicyManager { * #setCrossProfilePackages(ComponentName, Set)}.</li> * <li>The default package names set by the OEM that are allowed to request user consent for * cross-profile communication without being explicitly enabled by the admin, via - * {@link com.android.internal.R.array#cross_profile_apps}</li> + * {@link com.android.internal.R.array#cross_profile_apps} and + * {@link com.android.internal.R.array#vendor_cross_profile_apps}.</li> * </ul> * * @return the combined set of whitelisted package names set via - * {@link #setCrossProfilePackages(ComponentName, Set)} and - * {@link com.android.internal.R.array#cross_profile_apps} + * {@link #setCrossProfilePackages(ComponentName, Set)}, + * {@link com.android.internal.R.array#cross_profile_apps}, + * and {@link com.android.internal.R.array#vendor_cross_profile_apps}. * * @hide */ @@ -11599,7 +11602,7 @@ public class DevicePolicyManager { permission.INTERACT_ACROSS_PROFILES }) public @NonNull Set<String> getAllCrossProfilePackages() { - throwIfParentInstance("getDefaultCrossProfilePackages"); + throwIfParentInstance("getAllCrossProfilePackages"); if (mService != null) { try { return new ArraySet<>(mService.getAllCrossProfilePackages()); @@ -11611,6 +11614,26 @@ public class DevicePolicyManager { } /** + * Returns the default package names set by the OEM that are allowed to request user consent for + * cross-profile communication without being explicitly enabled by the admin, via + * {@link com.android.internal.R.array#cross_profile_apps} and + * {@link com.android.internal.R.array#vendor_cross_profile_apps}. + * + * @hide + */ + public @NonNull Set<String> getDefaultCrossProfilePackages() { + throwIfParentInstance("getDefaultCrossProfilePackages"); + if (mService != null) { + try { + return new ArraySet<>(mService.getDefaultCrossProfilePackages()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return Collections.emptySet(); + } + + /** * Returns whether the device is being used as a managed kiosk. These requirements are as * follows: * <ul> diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 7fd0ae4a1a00..d2672eb3e2b2 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -457,6 +457,7 @@ interface IDevicePolicyManager { List<String> getCrossProfilePackages(in ComponentName admin); List<String> getAllCrossProfilePackages(); + List<String> getDefaultCrossProfilePackages(); boolean isManagedKiosk(); boolean isUnattendedManagedKiosk(); diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java index bd649f88f40a..eab88383fa1d 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java +++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java @@ -57,19 +57,19 @@ public final class PhoneTimeSuggestion implements Parcelable { } }; - private final int mPhoneId; + private final int mSlotIndex; @Nullable private final TimestampedValue<Long> mUtcTime; @Nullable private ArrayList<String> mDebugInfo; private PhoneTimeSuggestion(Builder builder) { - mPhoneId = builder.mPhoneId; + mSlotIndex = builder.mSlotIndex; mUtcTime = builder.mUtcTime; mDebugInfo = builder.mDebugInfo != null ? new ArrayList<>(builder.mDebugInfo) : null; } private static PhoneTimeSuggestion createFromParcel(Parcel in) { - int phoneId = in.readInt(); - PhoneTimeSuggestion suggestion = new PhoneTimeSuggestion.Builder(phoneId) + int slotIndex = in.readInt(); + PhoneTimeSuggestion suggestion = new PhoneTimeSuggestion.Builder(slotIndex) .setUtcTime(in.readParcelable(null /* classLoader */)) .build(); @SuppressWarnings("unchecked") @@ -87,17 +87,17 @@ public final class PhoneTimeSuggestion implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mPhoneId); + dest.writeInt(mSlotIndex); dest.writeParcelable(mUtcTime, 0); dest.writeList(mDebugInfo); } /** - * Returns an identifier for the source of this suggestion. When a device has several "phones", - * i.e. sim slots or equivalent, it is used to identify which one. + * Returns an identifier for the source of this suggestion. When a device has several sim slots + * or equivalent, it is used to identify which one the suggestion is from. */ - public int getPhoneId() { - return mPhoneId; + public int getSlotIndex() { + return mSlotIndex; } /** @@ -152,19 +152,19 @@ public final class PhoneTimeSuggestion implements Parcelable { return false; } PhoneTimeSuggestion that = (PhoneTimeSuggestion) o; - return mPhoneId == that.mPhoneId + return mSlotIndex == that.mSlotIndex && Objects.equals(mUtcTime, that.mUtcTime); } @Override public int hashCode() { - return Objects.hash(mPhoneId, mUtcTime); + return Objects.hash(mSlotIndex, mUtcTime); } @Override public String toString() { return "PhoneTimeSuggestion{" - + "mPhoneId='" + mPhoneId + '\'' + + "mSlotIndex='" + mSlotIndex + '\'' + ", mUtcTime=" + mUtcTime + ", mDebugInfo=" + mDebugInfo + '}'; @@ -177,13 +177,13 @@ public final class PhoneTimeSuggestion implements Parcelable { */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class Builder { - private final int mPhoneId; + private final int mSlotIndex; @Nullable private TimestampedValue<Long> mUtcTime; @Nullable private List<String> mDebugInfo; - /** Creates a builder with the specified {@code phoneId}. */ - public Builder(int phoneId) { - mPhoneId = phoneId; + /** Creates a builder with the specified {@code slotIndex}. */ + public Builder(int slotIndex) { + mSlotIndex = slotIndex; } /** Returns the builder for call chaining. */ diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java index d71ffcb9f772..ebaf951130ca 100644 --- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java +++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java @@ -66,12 +66,12 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Creates an empty time zone suggestion, i.e. one that will cancel previous suggestions with - * the same {@code phoneId}. + * the same {@code slotIndex}. */ @NonNull public static PhoneTimeZoneSuggestion createEmptySuggestion( - int phoneId, @NonNull String debugInfo) { - return new Builder(phoneId).addDebugInfo(debugInfo).build(); + int slotIndex, @NonNull String debugInfo) { + return new Builder(slotIndex).addDebugInfo(debugInfo).build(); } /** @hide */ @@ -135,7 +135,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * The ID of the phone this suggestion is associated with. For multiple-sim devices this * helps to establish source so filtering / stickiness can be implemented. */ - private final int mPhoneId; + private final int mSlotIndex; /** * The suggestion. {@code null} means there is no current suggestion and any previous suggestion @@ -165,7 +165,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { private List<String> mDebugInfo; private PhoneTimeZoneSuggestion(Builder builder) { - mPhoneId = builder.mPhoneId; + mSlotIndex = builder.mSlotIndex; mZoneId = builder.mZoneId; mMatchType = builder.mMatchType; mQuality = builder.mQuality; @@ -175,8 +175,8 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { @SuppressWarnings("unchecked") private static PhoneTimeZoneSuggestion createFromParcel(Parcel in) { // Use the Builder so we get validation during build(). - int phoneId = in.readInt(); - PhoneTimeZoneSuggestion suggestion = new Builder(phoneId) + int slotIndex = in.readInt(); + PhoneTimeZoneSuggestion suggestion = new Builder(slotIndex) .setZoneId(in.readString()) .setMatchType(in.readInt()) .setQuality(in.readInt()) @@ -190,7 +190,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mPhoneId); + dest.writeInt(mSlotIndex); dest.writeString(mZoneId); dest.writeInt(mMatchType); dest.writeInt(mQuality); @@ -203,11 +203,11 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { } /** - * Returns an identifier for the source of this suggestion. When a device has several "phones", - * i.e. sim slots or equivalent, it is used to identify which one. + * Returns an identifier for the source of this suggestion. When a device has several sim slots + * or equivalent, it is used to identify which one the suggestion is from. */ - public int getPhoneId() { - return mPhoneId; + public int getSlotIndex() { + return mSlotIndex; } /** @@ -282,7 +282,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { return false; } PhoneTimeZoneSuggestion that = (PhoneTimeZoneSuggestion) o; - return mPhoneId == that.mPhoneId + return mSlotIndex == that.mSlotIndex && mMatchType == that.mMatchType && mQuality == that.mQuality && Objects.equals(mZoneId, that.mZoneId); @@ -290,13 +290,13 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { @Override public int hashCode() { - return Objects.hash(mPhoneId, mZoneId, mMatchType, mQuality); + return Objects.hash(mSlotIndex, mZoneId, mMatchType, mQuality); } @Override public String toString() { return "PhoneTimeZoneSuggestion{" - + "mPhoneId=" + mPhoneId + + "mSlotIndex=" + mSlotIndex + ", mZoneId='" + mZoneId + '\'' + ", mMatchType=" + mMatchType + ", mQuality=" + mQuality @@ -311,14 +311,14 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class Builder { - private final int mPhoneId; + private final int mSlotIndex; @Nullable private String mZoneId; @MatchType private int mMatchType; @Quality private int mQuality; @Nullable private List<String> mDebugInfo; - public Builder(int phoneId) { - mPhoneId = phoneId; + public Builder(int slotIndex) { + mSlotIndex = slotIndex; } /** diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 325fd9418af7..b73d311dd52e 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1766,6 +1766,45 @@ public final class BluetoothAdapter { } /** + * Removes the active device for the grouping of @ActiveDeviceUse specified + * + * @param profiles represents the purpose for which we are setting this as the active device. + * Possible values are: + * {@link BluetoothAdapter#ACTIVE_DEVICE_AUDIO}, + * {@link BluetoothAdapter#ACTIVE_DEVICE_PHONE_CALL}, + * {@link BluetoothAdapter#ACTIVE_DEVICE_ALL} + * @return false on immediate error, true otherwise + * @throws IllegalArgumentException if device is null or profiles is not one of + * {@link ActiveDeviceUse} + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean removeActiveDevice(@ActiveDeviceUse int profiles) { + if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL + && profiles != ACTIVE_DEVICE_ALL) { + Log.e(TAG, "Invalid profiles param value in removeActiveDevice"); + throw new IllegalArgumentException("Profiles must be one of " + + "BluetoothAdapter.ACTIVE_DEVICE_AUDIO, " + + "BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL, or " + + "BluetoothAdapter.ACTIVE_DEVICE_ALL"); + } + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.removeActiveDevice(profiles); + } + } catch (RemoteException e) { + Log.e(TAG, "", e); + } finally { + mServiceLock.readLock().unlock(); + } + + return false; + } + + /** + * Sets device as the active devices for the profiles passed into the function * * @param device is the remote bluetooth device * @param profiles represents the purpose for which we are setting this as the active device. @@ -1774,18 +1813,26 @@ public final class BluetoothAdapter { * {@link BluetoothAdapter#ACTIVE_DEVICE_PHONE_CALL}, * {@link BluetoothAdapter#ACTIVE_DEVICE_ALL} * @return false on immediate error, true otherwise + * @throws IllegalArgumentException if device is null or profiles is not one of + * {@link ActiveDeviceUse} * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setActiveDevice(@Nullable BluetoothDevice device, + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setActiveDevice(@NonNull BluetoothDevice device, @ActiveDeviceUse int profiles) { + if (device == null) { + Log.e(TAG, "setActiveDevice: Null device passed as parameter"); + throw new IllegalArgumentException("device cannot be null"); + } if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL && profiles != ACTIVE_DEVICE_ALL) { Log.e(TAG, "Invalid profiles param value in setActiveDevice"); - return false; + throw new IllegalArgumentException("Profiles must be one of " + + "BluetoothAdapter.ACTIVE_DEVICE_AUDIO, " + + "BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL, or " + + "BluetoothAdapter.ACTIVE_DEVICE_ALL"); } - try { mServiceLock.readLock().lock(); if (mService != null) { diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 12dc814c3416..5b60b85f4721 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -179,9 +180,10 @@ public final class BluetoothDevice implements Parcelable { * <p>Always contains the extra field {@link #EXTRA_DEVICE}. * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ + @SuppressLint("ActionValue") @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_ALIAS_CHANGED = - "android.bluetooth.action.ALIAS_CHANGED"; + "android.bluetooth.device.action.ALIAS_CHANGED"; /** * Broadcast Action: Indicates a change in the bond state of a remote @@ -1091,7 +1093,7 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setAlias(@NonNull String alias) { final IBluetooth service = sService; if (service == null) { diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index a385771484fd..7615b87c7102 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -91,7 +91,7 @@ public class CameraDeviceImpl extends CameraDevice private boolean mIdle = true; /** map request IDs to callback/request data */ - private final SparseArray<CaptureCallbackHolder> mCaptureCallbackMap = + private SparseArray<CaptureCallbackHolder> mCaptureCallbackMap = new SparseArray<CaptureCallbackHolder>(); private int mRepeatingRequestId = REQUEST_ID_NONE; @@ -123,7 +123,7 @@ public class CameraDeviceImpl extends CameraDevice * An object tracking received frame numbers. * Updated when receiving callbacks from ICameraDeviceCallbacks. */ - private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker(); + private FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker(); private CameraCaptureSessionCore mCurrentSession; private int mNextSessionId = 0; @@ -892,6 +892,7 @@ public class CameraDeviceImpl extends CameraDevice HashSet<Integer> offlineStreamIds = new HashSet<Integer>(); SparseArray<OutputConfiguration> offlineConfiguredOutputs = new SparseArray<OutputConfiguration>(); + CameraOfflineSession ret; synchronized(mInterfaceLock) { if (mOfflineSessionImpl != null) { @@ -919,15 +920,20 @@ public class CameraDeviceImpl extends CameraDevice offlineStreamIds.add(streamId); } + stopRepeating(); mOfflineSessionImpl = new CameraOfflineSessionImpl(mCameraId, mCharacteristics, executor, listener, offlineConfiguredOutputs, - mConfiguredInput, mFrameNumberTracker, mCaptureCallbackMap, + mConfiguredInput, mConfiguredOutputs, mFrameNumberTracker, mCaptureCallbackMap, mRequestLastFrameNumbersList); + ret = mOfflineSessionImpl; mOfflineSwitchService = Executors.newSingleThreadExecutor(); mConfiguredOutputs.clear(); mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(REQUEST_ID_NONE, null); + mIdle = true; + mCaptureCallbackMap = new SparseArray<CaptureCallbackHolder>(); + mFrameNumberTracker = new FrameNumberTracker(); mCurrentSession.closeWithoutDraining(); mCurrentSession = null; @@ -949,11 +955,13 @@ public class CameraDeviceImpl extends CameraDevice mOfflineSessionImpl.setRemoteSession(remoteOfflineSession); } catch (CameraAccessException e) { mOfflineSessionImpl.notifyFailedSwitch(); + } finally { + mOfflineSessionImpl = null; } } }); - return mOfflineSessionImpl; + return ret; } public boolean supportsOfflineProcessing(Surface surface) { diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java index 1db377a61465..1d9d644c9306 100644 --- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java @@ -63,6 +63,7 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession private SimpleEntry<Integer, InputConfiguration> mOfflineInput = new SimpleEntry<>(REQUEST_ID_NONE, null); private SparseArray<OutputConfiguration> mOfflineOutputs = new SparseArray<>(); + private SparseArray<OutputConfiguration> mConfiguredOutputs = new SparseArray<>(); final Object mInterfaceLock = new Object(); // access from this class and Session only! @@ -96,6 +97,7 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession Executor offlineExecutor, CameraOfflineSessionCallback offlineCallback, SparseArray<OutputConfiguration> offlineOutputs, SimpleEntry<Integer, InputConfiguration> offlineInput, + SparseArray<OutputConfiguration> configuredOutputs, FrameNumberTracker frameNumberTracker, SparseArray<CaptureCallbackHolder> callbackMap, List<RequestLastFrameNumbersHolder> frameNumberList) { if ((cameraId == null) || (characteristics == null)) { @@ -117,6 +119,7 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession mOfflineRequestLastFrameNumbersList.addAll(frameNumberList); mFrameNumberTracker = frameNumberTracker; mCaptureCallbackMap = callbackMap; + mConfiguredOutputs = configuredOutputs; mOfflineOutputs = offlineOutputs; mOfflineInput = offlineInput; mOfflineExecutor = checkNotNull(offlineExecutor, "offline executor must not be null"); @@ -137,9 +140,6 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession @Override public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) { synchronized(mInterfaceLock) { - if (mRemoteSession == null) { - return; // Camera already closed - } switch (errorCode) { case CameraDeviceCallbacks.ERROR_CAMERA_REQUEST: @@ -177,6 +177,11 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession @Override public void onDeviceIdle() { synchronized(mInterfaceLock) { + if (mRemoteSession == null) { + Log.v(TAG, "Ignoring idle state notifications during offline switches"); + return; + } + Runnable idleDispatch = new Runnable() { @Override public void run() { @@ -203,8 +208,6 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession final CaptureCallbackHolder holder; synchronized(mInterfaceLock) { - if (mRemoteSession == null) return; // Camera already closed - // Get the callback for this frame ID, if there is one holder = CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId); @@ -269,8 +272,6 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession long frameNumber = resultExtras.getFrameNumber(); synchronized(mInterfaceLock) { - if (mRemoteSession == null) return; // Camera already closed - // TODO: Handle CameraCharacteristics access from CaptureResult correctly. result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE, mCharacteristics.get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE)); @@ -445,8 +446,12 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession if (errorCode == ERROR_CAMERA_BUFFER) { // Because 1 stream id could map to multiple surfaces, we need to specify both // streamId and surfaceId. - OutputConfiguration config = mOfflineOutputs.get( - resultExtras.getErrorStreamId()); + OutputConfiguration config; + if ((mRemoteSession == null) && !isClosed()) { + config = mConfiguredOutputs.get(resultExtras.getErrorStreamId()); + } else { + config = mOfflineOutputs.get(resultExtras.getErrorStreamId()); + } if (config == null) { Log.v(TAG, String.format( "Stream %d has been removed. Skipping buffer lost callback", @@ -538,11 +543,6 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession final Executor executor; final CameraCaptureSession.CaptureCallback callback; synchronized(mInterfaceLock) { - if (mRemoteSession == null) { - Log.w(TAG, "Camera closed while checking sequences"); - return; - } - int index = mCaptureCallbackMap.indexOfKey(requestId); holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null; @@ -575,7 +575,7 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession } // Call onCaptureSequenceCompleted - if ((sequenceCompleted) && (callback != null) && (executor == null)) { + if ((sequenceCompleted) && (callback != null) && (executor != null)) { Runnable resultDispatch = new Runnable() { @Override public void run() { @@ -592,7 +592,12 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession } finally { Binder.restoreCallingIdentity(ident); } + + if (mCaptureCallbackMap.size() == 0) { + getCallbacks().onDeviceIdle(); + } } + } } @@ -686,9 +691,7 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession Runnable closeDispatch = new Runnable() { @Override public void run() { - if (!isClosed()) { - mOfflineCallback.onClosed(CameraOfflineSessionImpl.this); - } + mOfflineCallback.onClosed(CameraOfflineSessionImpl.this); } }; diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java index 3e995b624112..ece5c28884fa 100644 --- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java +++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java @@ -194,6 +194,11 @@ public class AmbientDisplayConfiguration { return !TextUtils.isEmpty(ambientDisplayComponent()); } + /** {@hide} */ + public boolean dozeSuppressed(int user) { + return boolSettingDefaultOff(Settings.Secure.SUPPRESS_DOZE, user); + } + private boolean alwaysOnDisplayAvailable() { return mContext.getResources().getBoolean(R.bool.config_dozeAlwaysOnDisplayAvailable); } diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index b13e4b72aa22..140363c48227 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -25,13 +25,16 @@ import android.os.Binder; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; /** @@ -57,6 +60,11 @@ import java.util.concurrent.Executor; * </ul> */ public class ConnectivityDiagnosticsManager { + /** @hide */ + @VisibleForTesting + public static final Map<ConnectivityDiagnosticsCallback, ConnectivityDiagnosticsBinder> + sCallbacks = new ConcurrentHashMap<>(); + private final Context mContext; private final IConnectivityManager mService; @@ -631,8 +639,9 @@ public class ConnectivityDiagnosticsManager { /** * Registers a ConnectivityDiagnosticsCallback with the System. * - * <p>Only apps that offer network connectivity to the user are allowed to register callbacks. - * This includes: + * <p>Only apps that offer network connectivity to the user should be registering callbacks. + * These are the only apps whose callbacks will be invoked by the system. Apps considered to + * meet these conditions include: * * <ul> * <li>Carrier apps with active subscriptions @@ -640,15 +649,14 @@ public class ConnectivityDiagnosticsManager { * <li>WiFi Suggesters * </ul> * - * <p>Callbacks will be limited to receiving notifications for networks over which apps provide - * connectivity. + * <p>Callbacks registered by apps not meeting the above criteria will not be invoked. * * <p>If a registering app loses its relevant permissions, any callbacks it registered will * silently stop receiving callbacks. * - * <p>Each register() call <b>MUST</b> use a unique ConnectivityDiagnosticsCallback instance. If - * a single instance is registered with multiple NetworkRequests, an IllegalArgumentException - * will be thrown. + * <p>Each register() call <b>MUST</b> use a ConnectivityDiagnosticsCallback instance that is + * not currently registered. If a ConnectivityDiagnosticsCallback instance is registered with + * multiple NetworkRequests, an IllegalArgumentException will be thrown. * * @param request The NetworkRequest that will be used to match with Networks for which * callbacks will be fired @@ -657,15 +665,21 @@ public class ConnectivityDiagnosticsManager { * System * @throws IllegalArgumentException if the same callback instance is registered with multiple * NetworkRequests - * @throws SecurityException if the caller does not have appropriate permissions to register a - * callback */ public void registerConnectivityDiagnosticsCallback( @NonNull NetworkRequest request, @NonNull Executor e, @NonNull ConnectivityDiagnosticsCallback callback) { - // TODO(b/143187964): implement ConnectivityDiagnostics functionality - throw new UnsupportedOperationException("registerCallback() not supported yet"); + final ConnectivityDiagnosticsBinder binder = new ConnectivityDiagnosticsBinder(callback, e); + if (sCallbacks.putIfAbsent(callback, binder) != null) { + throw new IllegalArgumentException("Callback is currently registered"); + } + + try { + mService.registerConnectivityDiagnosticsCallback(binder, request); + } catch (RemoteException exception) { + exception.rethrowFromSystemServer(); + } } /** @@ -678,7 +692,15 @@ public class ConnectivityDiagnosticsManager { */ public void unregisterConnectivityDiagnosticsCallback( @NonNull ConnectivityDiagnosticsCallback callback) { - // TODO(b/143187964): implement ConnectivityDiagnostics functionality - throw new UnsupportedOperationException("registerCallback() not supported yet"); + // unconditionally removing from sCallbacks prevents race conditions here, since remove() is + // atomic. + final ConnectivityDiagnosticsBinder binder = sCallbacks.remove(callback); + if (binder == null) return; + + try { + mService.unregisterConnectivityDiagnosticsCallback(binder); + } catch (RemoteException exception) { + exception.rethrowFromSystemServer(); + } } } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 3ea64f13fe6b..bb1dafc50484 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2309,6 +2309,121 @@ public class StorageManager { private static final String XATTR_CACHE_GROUP = "user.cache_group"; private static final String XATTR_CACHE_TOMBSTONE = "user.cache_tombstone"; + + // Matches AID_MEDIA_RW in android_filesystem_config.h + private static final int QUOTA_PROJECT_ID_MEDIA_NONE = 1023; + + // Matches AID_MEDIA_IMAGE in android_filesystem_config.h + private static final int QUOTA_PROJECT_ID_MEDIA_IMAGE = 1057; + + // Matches AID_MEDIA_AUDIO in android_filesystem_config.h + private static final int QUOTA_PROJECT_ID_MEDIA_AUDIO = 1055; + + // Matches AID_MEDIA_VIDEO in android_filesystem_config.h + private static final int QUOTA_PROJECT_ID_MEDIA_VIDEO = 1056; + + /** + * Constant for use with + * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file + * is not a media file. + * + * @hide + */ + @SystemApi + public static final int QUOTA_TYPE_MEDIA_NONE = 0; + + /** + * Constant for use with + * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file + * is an image file. + * + * @hide + */ + @SystemApi + public static final int QUOTA_TYPE_MEDIA_IMAGE = 1; + + /** + * Constant for use with + * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file + * is an audio file. + * + * @hide + */ + @SystemApi + public static final int QUOTA_TYPE_MEDIA_AUDIO = 2; + + /** + * Constant for use with + * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file + * is a video file. + * + * @hide + */ + @SystemApi + public static final int QUOTA_TYPE_MEDIA_VIDEO = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "QUOTA_TYPE_" }, value = { + QUOTA_TYPE_MEDIA_NONE, + QUOTA_TYPE_MEDIA_AUDIO, + QUOTA_TYPE_MEDIA_VIDEO, + QUOTA_TYPE_MEDIA_IMAGE, + }) + public @interface QuotaType {} + + private static native boolean setQuotaProjectId(String path, long projectId); + + /** + * Let StorageManager know that the quota type for a file on external storage should + * be updated. Android tracks quotas for various media types. Consequently, this should be + * called on first creation of a new file on external storage, and whenever the + * media type of the file is updated later. + * + * This API doesn't require any special permissions, though typical implementations + * will require being called from an SELinux domain that allows setting file attributes + * related to quota (eg the GID or project ID). + * + * The default platform user of this API is the MediaProvider process, which is + * responsible for managing all of external storage. + * + * @param path the path to the file for which we should update the quota type + * @param quotaType the quota type of the file; this is based on the + * {@code QuotaType} constants, eg + * {@code StorageManager.QUOTA_TYPE_MEDIA_AUDIO} + * + * @throws IllegalArgumentException if {@code quotaType} does not correspond to a valid + * quota type. + * @throws IOException if the quota type could not be updated. + * + * @hide + */ + @SystemApi + public void updateExternalStorageFileQuotaType(@NonNull File path, + @QuotaType int quotaType) throws IOException { + long projectId; + final String filePath = path.getCanonicalPath(); + switch (quotaType) { + case QUOTA_TYPE_MEDIA_NONE: + projectId = QUOTA_PROJECT_ID_MEDIA_NONE; + break; + case QUOTA_TYPE_MEDIA_AUDIO: + projectId = QUOTA_PROJECT_ID_MEDIA_AUDIO; + break; + case QUOTA_TYPE_MEDIA_VIDEO: + projectId = QUOTA_PROJECT_ID_MEDIA_VIDEO; + break; + case QUOTA_TYPE_MEDIA_IMAGE: + projectId = QUOTA_PROJECT_ID_MEDIA_IMAGE; + break; + default: + throw new IllegalArgumentException("Invalid quota type: " + quotaType); + } + if (!setQuotaProjectId(filePath, projectId)) { + throw new IOException("Failed to update quota type for " + filePath); + } + } + /** {@hide} */ private static void setCacheBehavior(File path, String name, boolean enabled) throws IOException { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 605c585215a6..b62142c01c9b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2417,6 +2417,10 @@ public final class Settings { public static class NameValueTable implements BaseColumns { public static final String NAME = "name"; public static final String VALUE = "value"; + // A flag indicating whether the current value of a setting should be preserved during + // restore. + /** @hide */ + public static final String IS_PRESERVED_IN_RESTORE = "is_preserved_in_restore"; protected static boolean putString(ContentResolver resolver, Uri uri, String name, String value) { diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index 6334d9d765c8..368c94cdc790 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -31,6 +31,7 @@ import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; import android.os.Build; +import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; @@ -39,6 +40,7 @@ import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.service.autofill.Dataset; +import android.service.autofill.FillEventHistory; import android.service.autofill.augmented.PresentationParams.SystemPopupPresentationParams; import android.util.Log; import android.util.Pair; @@ -319,6 +321,31 @@ public abstract class AugmentedAutofillService extends Service { pw.print(getClass().getName()); pw.println(": nothing to dump"); } + /** + * Gets the inline augmented autofill events that happened after the last + * {@link #onFillRequest(FillRequest, CancellationSignal, FillController, FillCallback)} call. + * + * <p>The history is not persisted over reboots, and it's cleared every time the service + * replies to a + * {@link #onFillRequest(FillRequest, CancellationSignal, FillController, FillCallback)} + * by calling {@link FillCallback#onSuccess(FillResponse)}. Hence, the service should call + * {@link #getFillEventHistory() before finishing the {@link FillCallback}. + * + * <p>Also note that the events from the dropdown suggestion UI is not stored in the history + * since the service owns the UI. + * + * @return The history or {@code null} if there are no events. + */ + @Nullable public final FillEventHistory getFillEventHistory() { + final AutofillManager afm = getSystemService(AutofillManager.class); + + if (afm == null) { + return null; + } else { + return afm.getFillEventHistory(); + } + } + /** @hide */ static final class AutofillProxy { @@ -487,9 +514,10 @@ public abstract class AugmentedAutofillService extends Service { } } - public void onInlineSuggestionsDataReady(@NonNull List<Dataset> inlineSuggestionsData) { + public void onInlineSuggestionsDataReady(@NonNull List<Dataset> inlineSuggestionsData, + @Nullable Bundle clientState) { try { - mCallback.onSuccess(inlineSuggestionsData.toArray(new Dataset[]{})); + mCallback.onSuccess(inlineSuggestionsData.toArray(new Dataset[]{}), clientState); } catch (RemoteException e) { Log.e(TAG, "Error calling back with the inline suggestions data: " + e); } @@ -511,7 +539,8 @@ public abstract class AugmentedAutofillService extends Service { } } try { - mCallback.onSuccess(/* mInlineSuggestionsData= */null); + mCallback.onSuccess(/* inlineSuggestionsData= */null, /* clientState=*/ + null); } catch (RemoteException e) { Log.e(TAG, "Error reporting success: " + e); } diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index b767799847d7..d0ffd7b4d8d4 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -60,7 +60,7 @@ public final class FillCallback { List<Dataset> inlineSuggestions = response.getInlineSuggestions(); if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) { - mProxy.onInlineSuggestionsDataReady(inlineSuggestions); + mProxy.onInlineSuggestionsDataReady(inlineSuggestions, response.getClientState()); return; } diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java index e8e6ff05da03..68ba63ac35a8 100644 --- a/core/java/android/service/autofill/augmented/FillResponse.java +++ b/core/java/android/service/autofill/augmented/FillResponse.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.os.Bundle; import android.service.autofill.Dataset; import com.android.internal.util.DataClass; @@ -50,6 +51,13 @@ public final class FillResponse { @DataClass.PluralOf("inlineSuggestion") private @Nullable List<Dataset> mInlineSuggestions; + /** + * The client state that {@link AugmentedAutofillService} implementation can put anything in to + * identify the request and the response when calling + * {@link AugmentedAutofillService#getFillEventHistory()}. + */ + private @Nullable Bundle mClientState; + private static FillWindow defaultFillWindow() { return null; } @@ -58,6 +66,10 @@ public final class FillResponse { return null; } + private static Bundle defaultClientState() { + return null; + } + /** @hide */ abstract static class BaseBuilder { @@ -82,9 +94,11 @@ public final class FillResponse { @DataClass.Generated.Member /* package-private */ FillResponse( @Nullable FillWindow fillWindow, - @Nullable List<Dataset> inlineSuggestions) { + @Nullable List<Dataset> inlineSuggestions, + @Nullable Bundle clientState) { this.mFillWindow = fillWindow; this.mInlineSuggestions = inlineSuggestions; + this.mClientState = clientState; // onConstructed(); // You can define this method to get a callback } @@ -111,6 +125,18 @@ public final class FillResponse { } /** + * The client state that {@link AugmentedAutofillService} implementation can put anything in to + * identify the request and the response when calling + * {@link AugmentedAutofillService#getFillEventHistory()}. + * + * @hide + */ + @DataClass.Generated.Member + public @Nullable Bundle getClientState() { + return mClientState; + } + + /** * A builder for {@link FillResponse} */ @SuppressWarnings("WeakerAccess") @@ -119,6 +145,7 @@ public final class FillResponse { private @Nullable FillWindow mFillWindow; private @Nullable List<Dataset> mInlineSuggestions; + private @Nullable Bundle mClientState; private long mBuilderFieldsSet = 0L; @@ -157,10 +184,23 @@ public final class FillResponse { return this; } + /** + * The client state that {@link AugmentedAutofillService} implementation can put anything in to + * identify the request and the response when calling + * {@link AugmentedAutofillService#getFillEventHistory()}. + */ + @DataClass.Generated.Member + public @NonNull Builder setClientState(@Nullable Bundle value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mClientState = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull FillResponse build() { checkNotUsed(); - mBuilderFieldsSet |= 0x4; // Mark builder used + mBuilderFieldsSet |= 0x8; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mFillWindow = defaultFillWindow(); @@ -168,14 +208,18 @@ public final class FillResponse { if ((mBuilderFieldsSet & 0x2) == 0) { mInlineSuggestions = defaultInlineSuggestions(); } + if ((mBuilderFieldsSet & 0x4) == 0) { + mClientState = defaultClientState(); + } FillResponse o = new FillResponse( mFillWindow, - mInlineSuggestions); + mInlineSuggestions, + mClientState); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x4) != 0) { + if ((mBuilderFieldsSet & 0x8) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -183,10 +227,10 @@ public final class FillResponse { } @DataClass.Generated( - time = 1577476012370L, + time = 1580335256422L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/service/autofill/augmented/FillResponse.java", - inputSignatures = "private @android.annotation.Nullable android.service.autofill.augmented.FillWindow mFillWindow\nprivate @com.android.internal.util.DataClass.PluralOf(\"inlineSuggestion\") @android.annotation.Nullable java.util.List<android.service.autofill.Dataset> mInlineSuggestions\nprivate static android.service.autofill.augmented.FillWindow defaultFillWindow()\nprivate static java.util.List<android.service.autofill.Dataset> defaultInlineSuggestions()\nclass FillResponse extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genHiddenGetters=true)\nabstract android.service.autofill.augmented.FillResponse.Builder addInlineSuggestion(android.service.autofill.Dataset)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "private @android.annotation.Nullable android.service.autofill.augmented.FillWindow mFillWindow\nprivate @com.android.internal.util.DataClass.PluralOf(\"inlineSuggestion\") @android.annotation.Nullable java.util.List<android.service.autofill.Dataset> mInlineSuggestions\nprivate @android.annotation.Nullable android.os.Bundle mClientState\nprivate static android.service.autofill.augmented.FillWindow defaultFillWindow()\nprivate static java.util.List<android.service.autofill.Dataset> defaultInlineSuggestions()\nprivate static android.os.Bundle defaultClientState()\nclass FillResponse extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genHiddenGetters=true)\nabstract android.service.autofill.augmented.FillResponse.Builder addInlineSuggestion(android.service.autofill.Dataset)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl index 3ccb3114a10f..31e77f358782 100644 --- a/core/java/android/service/autofill/augmented/IFillCallback.aidl +++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl @@ -16,6 +16,7 @@ package android.service.autofill.augmented; +import android.os.Bundle; import android.os.ICancellationSignal; import android.service.autofill.Dataset; @@ -27,7 +28,7 @@ import android.service.autofill.Dataset; */ interface IFillCallback { void onCancellable(in ICancellationSignal cancellation); - void onSuccess(in @nullable Dataset[] mInlineSuggestionsData); + void onSuccess(in @nullable Dataset[] inlineSuggestionsData, in @nullable Bundle clientState); boolean isCompleted(); void cancel(); } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 06fccaf8ea81..5fdac810af29 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -47,6 +47,9 @@ public class FeatureFlagUtils { /** @hide */ public static final String BACKUP_NO_KV_DATA_CHANGE_CALLS = "backup_enable_no_data_notification_calls"; + /** @hide */ + public static final String SETTINGS_DO_NOT_RESTORE_PRESERVED = + "settings_do_not_restore_preserved"; private static final Map<String, String> DEFAULT_FLAGS; @@ -68,6 +71,11 @@ public class FeatureFlagUtils { // Disabled until backup transports support it. DEFAULT_FLAGS.put(BACKUP_NO_KV_DATA_CHANGE_CALLS, "false"); + // Disabled by default until b/148278926 is resolved. This flags guards a feature + // introduced in R and will be removed in the next release (b/148367230). + DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "false"); + + DEFAULT_FLAGS.put("settings_tether_all_in_one", "false"); } /** diff --git a/core/java/android/view/ITaskOrganizer.aidl b/core/java/android/view/ITaskOrganizer.aidl index e92aafed6f22..5ccdd30a9d97 100644 --- a/core/java/android/view/ITaskOrganizer.aidl +++ b/core/java/android/view/ITaskOrganizer.aidl @@ -26,8 +26,7 @@ import android.app.ActivityManager; * {@hide} */ oneway interface ITaskOrganizer { - void taskAppeared(in IWindowContainer container, - in ActivityManager.RunningTaskInfo taskInfo); + void taskAppeared(in ActivityManager.RunningTaskInfo taskInfo); void taskVanished(in IWindowContainer container); /** @@ -35,4 +34,19 @@ oneway interface ITaskOrganizer { * ActivityTaskManagerService#applyTaskOrganizerTransaction */ void transactionReady(int id, in SurfaceControl.Transaction t); + + /** + * Will fire when core attributes of a Task's info change. Relevant properties include the + * {@link WindowConfiguration.ActivityType} and whether it is resizable. + * + * This is used, for example, during split-screen. The flow for starting is: Something sends an + * Intent with windowingmode. Then WM finds a matching root task and launches the new task into + * it. This causes the root task's info to change because now it has a task when it didn't + * before. The default Divider implementation interprets this as a request to enter + * split-screen mode and will move all other Tasks into the secondary root task. When WM + * applies this change, it triggers an info change in the secondary root task because it now + * has children. The Divider impl looks at the info and can see that the secondary root task + * has adopted an ActivityType of HOME and proceeds to show the minimized dock UX. + */ + void onTaskInfoChanged(in ActivityManager.RunningTaskInfo info); }
\ No newline at end of file diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 23ba097f2b6d..f2263693897b 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -17,6 +17,7 @@ package android.view; import static android.view.InsetsState.ITYPE_IME; +import static android.view.InsetsState.toPublicType; import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; @@ -44,6 +45,12 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { */ private boolean mShowOnNextImeRender; + /** + * Tracks whether we have an outstanding request from the IME to show, but weren't able to + * execute it because we didn't have control yet. + */ + private boolean mImeRequestedShow; + public ImeInsetsSourceConsumer( InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { @@ -81,13 +88,14 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { public void onWindowFocusLost() { super.onWindowFocusLost(); getImm().unregisterImeConsumer(this); + mImeRequestedShow = false; } @Override - public void setControl(@Nullable InsetsSourceControl control) { - super.setControl(control); - if (control == null) { - hide(); + public void show(boolean fromIme) { + super.show(fromIme); + if (fromIme) { + mImeRequestedShow = true; } } @@ -99,7 +107,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { public @ShowResult int requestShow(boolean fromIme) { // TODO: ResultReceiver for IME. // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag. - if (fromIme) { + + // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching + // this code here means that we now got control, so we can start the animation immediately. + if (fromIme || mImeRequestedShow) { + mImeRequestedShow = false; return ShowResult.SHOW_IMMEDIATELY; } @@ -115,6 +127,15 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { getImm().notifyImeHidden(); } + @Override + public void setControl(@Nullable InsetsSourceControl control, int[] showTypes, + int[] hideTypes) { + super.setControl(control, showTypes, hideTypes); + if (control == null) { + hide(); + } + } + private boolean isDummyOrEmptyEditor(EditorInfo info) { // TODO(b/123044812): Handle dummy input gracefully in IME Insets API return info == null || (info.fieldId <= 0 && info.inputType <= 0); diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 5c24047a2c19..0653b06e4b9d 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -16,8 +16,6 @@ package android.view; -import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; -import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsState.ISIDE_BOTTOM; import static android.view.InsetsState.ISIDE_FLOATING; import static android.view.InsetsState.ISIDE_LEFT; @@ -188,7 +186,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll @Override public void finish(boolean shown) { - if (mCancelled) { + if (mCancelled || mFinished) { return; } setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */); @@ -210,6 +208,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mListener.onCancelled(); } + public boolean isCancelled() { + return mCancelled; + } + InsetsAnimation getAnimation() { return mAnimation; } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 6f76497572d7..22d6f37dbd95 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -39,6 +39,7 @@ import android.util.Log; import android.util.Pair; import android.util.Property; import android.util.SparseArray; +import android.view.InputDevice.MotionRange; import android.view.InsetsSourceConsumer.ShowResult; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; @@ -69,7 +70,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private static final int ANIMATION_DURATION_HIDE_MS = 340; private static final int PENDING_CONTROL_TIMEOUT_MS = 2000; - static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); + public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); /** * Layout mode during insets animation: The views should be laid out as if the changing inset @@ -77,7 +78,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * be called as if the changing insets types are shown, which will result in the views being * laid out as if the insets are fully shown. */ - static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0; + public static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0; /** * Layout mode during insets animation: The views should be laid out as if the changing inset @@ -85,7 +86,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * be called as if the changing insets types are hidden, which will result in the views being * laid out as if the insets are fully hidden. */ - static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1; + public static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1; /** * Determines the behavior of how the views should be laid out during an insets animation that @@ -148,7 +149,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void set(WindowInsetsAnimationController controller, Insets value) { controller.setInsetsAndAlpha( - value, 1f /* alpha */, (((DefaultAnimationControlListener) + value, 1f /* alpha */, (((InternalAnimationControlListener) ((InsetsAnimationControlImpl) controller).getListener()) .getRawFraction())); } @@ -165,7 +166,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private ObjectAnimator mAnimator; protected boolean mShow; - InternalAnimationControlListener(boolean show) { + public InternalAnimationControlListener(boolean show) { mShow = show; } @@ -213,7 +214,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration(); } - protected long getDurationMs() { + /** + * To get the animation duration in MS. + */ + public long getDurationMs() { if (mAnimator != null) { return mAnimator.getDuration(); } @@ -273,7 +277,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final String TAG = "InsetsControllerImpl"; private final InsetsState mState = new InsetsState(); - private final InsetsState mTmpState = new InsetsState(); + private final InsetsState mLastDispachedState = new InsetsState(); private final Rect mFrame = new Rect(); private final BiFunction<InsetsController, Integer, InsetsSourceConsumer> mConsumerCreator; @@ -367,15 +371,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return mState; } - boolean onStateChanged(InsetsState state) { - if (mState.equals(state)) { + public InsetsState getLastDispatchedState() { + return mLastDispachedState; + } + + @VisibleForTesting + public boolean onStateChanged(InsetsState state) { + if (mState.equals(state) && mLastDispachedState.equals(state)) { return false; } mState.set(state); - mTmpState.set(state, true /* copySources */); + mLastDispachedState.set(state, true /* copySources */); applyLocalVisibilityOverride(); mViewRoot.notifyInsetsChanged(); - if (!mState.equals(mTmpState)) { + if (!mState.equals(mLastDispachedState)) { sendStateToWindowManager(); } return true; @@ -419,6 +428,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } + int[] showTypes = new int[1]; + int[] hideTypes = new int[1]; + // Ensure to update all existing source consumers for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); @@ -426,15 +438,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // control may be null, but we still need to update the control to null if it got // revoked. - consumer.setControl(control); + consumer.setControl(control, showTypes, hideTypes); } // Ensure to create source consumers if not available yet. for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { final InsetsSourceControl control = mTmpControlArray.valueAt(i); - getSourceConsumer(control.getType()).setControl(control); + InsetsSourceConsumer consumer = getSourceConsumer(control.getType()); + consumer.setControl(control, showTypes, hideTypes); + } mTmpControlArray.clear(); + if (showTypes[0] != 0) { + applyAnimation(showTypes[0], true /* show */, false /* fromIme */); + } + if (hideTypes[0] != 0) { + applyAnimation(hideTypes[0], false /* show */, false /* fromIme */); + } } @Override @@ -465,7 +485,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @InternalInsetsType int internalType = internalTypes.valueAt(i); @AnimationType int animationType = getAnimationType(internalType); InsetsSourceConsumer consumer = getSourceConsumer(internalType); - if (mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE + if (consumer.isRequestedVisible() && animationType == ANIMATION_TYPE_NONE || animationType == ANIMATION_TYPE_SHOW) { // no-op: already shown or animating in (because window visibility is // applied before starting animation). @@ -488,7 +508,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @InternalInsetsType int internalType = internalTypes.valueAt(i); @AnimationType int animationType = getAnimationType(internalType); InsetsSourceConsumer consumer = getSourceConsumer(internalType); - if (!mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE + if (!consumer.isRequestedVisible() && animationType == ANIMATION_TYPE_NONE || animationType == ANIMATION_TYPE_HIDE) { // no-op: already hidden or animating out. continue; @@ -535,7 +555,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final SparseArray<InsetsSourceControl> controls = new SparseArray<>(); Pair<Integer, Boolean> typesReadyPair = collectSourceControls( - fromIme, internalTypes, controls); + fromIme, internalTypes, controls, animationType); int typesReady = typesReadyPair.first; boolean imeReady = typesReadyPair.second; if (!imeReady) { @@ -562,17 +582,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * @return Pair of (types ready to animate, IME ready to animate). */ private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, - ArraySet<Integer> internalTypes, SparseArray<InsetsSourceControl> controls) { + ArraySet<Integer> internalTypes, SparseArray<InsetsSourceControl> controls, + @AnimationType int animationType) { int typesReady = 0; boolean imeReady = true; for (int i = internalTypes.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); - boolean setVisible = !consumer.isRequestedVisible(); - if (setVisible) { + boolean show = animationType == ANIMATION_TYPE_SHOW + || animationType == ANIMATION_TYPE_USER; + boolean canRun = false; + if (show) { // Show request switch(consumer.requestShow(fromIme)) { case ShowResult.SHOW_IMMEDIATELY: - typesReady |= InsetsState.toPublicType(consumer.getType()); + canRun = true; break; case ShowResult.IME_SHOW_DELAYED: imeReady = false; @@ -589,11 +612,22 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!fromIme) { consumer.notifyHidden(); } - typesReady |= InsetsState.toPublicType(consumer.getType()); + canRun = true; + } + if (!canRun) { + continue; } final InsetsSourceControl control = consumer.getControl(); if (control != null) { controls.put(consumer.getType(), control); + typesReady |= toPublicType(consumer.getType()); + } else if (animationType == ANIMATION_TYPE_SHOW) { + + // We don't have a control at the moment. However, we still want to update requested + // visibility state such that in case we get control, we can apply show animation. + consumer.show(fromIme); + } else if (animationType == ANIMATION_TYPE_HIDE) { + consumer.hide(); } } return new Pair<>(typesReady, imeReady); @@ -808,7 +842,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void showDirectly(@InsetsType int types) { final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { - getSourceConsumer(internalTypes.valueAt(i)).show(); + getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */); } } @@ -840,6 +874,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public boolean onPreDraw() { mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this); + if (controller.isCancelled()) { + return true; + } mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds); listener.onReady(controller, types); return true; diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 37034ee065ee..d4961eab7473 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -53,6 +53,9 @@ public class InsetsSource implements Parcelable { mType = other.mType; mFrame = new Rect(other.mFrame); mVisible = other.mVisible; + mVisibleFrame = other.mVisibleFrame != null + ? new Rect(other.mVisibleFrame) + : null; } public void setFrame(Rect frame) { @@ -165,6 +168,7 @@ public class InsetsSource implements Parcelable { public int hashCode() { int result = mType; result = 31 * result + mFrame.hashCode(); + result = 31 * result + (mVisibleFrame != null ? mVisibleFrame.hashCode() : 0); result = 31 * result + (mVisible ? 1 : 0); return result; } @@ -189,6 +193,15 @@ public class InsetsSource implements Parcelable { dest.writeBoolean(mVisible); } + @Override + public String toString() { + return "InsetsSource: {" + + "mType=" + InsetsState.typeToString(mType) + + ", mFrame=" + mFrame.toShortString() + + ", mVisible" + mVisible + + "}"; + } + public static final @android.annotation.NonNull Creator<InsetsSource> CREATOR = new Creator<InsetsSource>() { public InsetsSource createFromParcel(Parcel in) { diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 9901d053184c..e6497c00c8dd 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -17,11 +17,14 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; +import static android.view.InsetsState.toPublicType; import android.annotation.IntDef; import android.annotation.Nullable; +import android.util.MutableShort; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; +import android.view.WindowInsets.Type.InsetsType; import com.android.internal.annotations.VisibleForTesting; @@ -71,18 +74,48 @@ public class InsetsSourceConsumer { mRequestedVisible = InsetsState.getDefaultVisibility(type); } - public void setControl(@Nullable InsetsSourceControl control) { + /** + * Updates the control delivered from the server. + + * @param showTypes An integer array with a single entry that determines which types a show + * animation should be run after setting the control. + * @param hideTypes An integer array with a single entry that determines which types a hide + * animation should be run after setting the control. + */ + public void setControl(@Nullable InsetsSourceControl control, + @InsetsType int[] showTypes, @InsetsType int[] hideTypes) { if (mSourceControl == control) { return; } mSourceControl = control; - applyHiddenToControl(); - if (applyLocalVisibilityOverride()) { - mController.notifyVisibilityChanged(); - } + + // We are loosing control if (mSourceControl == null) { mController.notifyControlRevoked(this); + + // Restore server visibility. + mState.getSource(getType()).setVisible( + mController.getLastDispatchedState().getSource(getType()).isVisible()); + applyLocalVisibilityOverride(); + return; + } + + // We are gaining control, and need to run an animation since previous state didn't match + if (mRequestedVisible != mState.getSource(mType).isVisible()) { + if (mRequestedVisible) { + showTypes[0] |= toPublicType(getType()); + } else { + hideTypes[0] |= toPublicType(getType()); + } + return; + } + + // We are gaining control, but don't need to run an animation. However make sure that the + // leash visibility is still up to date. + if (applyLocalVisibilityOverride()) { + mController.notifyVisibilityChanged(); } + applyHiddenToControl(); } @VisibleForTesting @@ -95,7 +128,7 @@ public class InsetsSourceConsumer { } @VisibleForTesting - public void show() { + public void show(boolean fromIme) { setRequestedVisible(true); } @@ -172,17 +205,13 @@ public class InsetsSourceConsumer { * the moment. */ private void setRequestedVisible(boolean requestedVisible) { - if (mRequestedVisible == requestedVisible) { - return; - } mRequestedVisible = requestedVisible; - applyLocalVisibilityOverride(); - mController.notifyVisibilityChanged(); + if (applyLocalVisibilityOverride()) { + mController.notifyVisibilityChanged(); + } } private void applyHiddenToControl() { - - // TODO: Handle case properly when animation is running already (it shouldn't!) if (mSourceControl == null || mSourceControl.getLeash() == null) { return; } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index bd19799bb664..8648682aaa2e 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -21,7 +21,6 @@ import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; import static android.view.ViewRootImpl.sNewInsetsMode; -import static android.view.WindowInsets.Type.IME; import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; import static android.view.WindowInsets.Type.SIZE; import static android.view.WindowInsets.Type.SYSTEM_GESTURES; @@ -43,7 +42,6 @@ import android.util.ArraySet; import android.util.SparseIntArray; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; -import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import java.io.PrintWriter; @@ -366,7 +364,12 @@ public class InsetsState implements Parcelable { return result; } - static @Type.InsetsType int toPublicType(@InternalInsetsType int type) { + /** + * Converting a internal type to the public type. + * @param type internal insets type, {@code InternalInsetsType}. + * @return public insets type, {@code Type.InsetsType}. + */ + public static @Type.InsetsType int toPublicType(@InternalInsetsType int type) { switch (type) { case ITYPE_STATUS_BAR: return Type.STATUS_BARS; @@ -510,5 +513,13 @@ public class InsetsState implements Parcelable { mSources.put(source.getType(), source); } } + + @Override + public String toString() { + return "InsetsState: {" + + "mDisplayFrame=" + mDisplayFrame + + ", mSources=" + mSources + + "}"; + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d70bf22fcfbf..ee7f6fb11de6 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3654,8 +3654,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @deprecated For floating windows, use {@link LayoutParams#setFitInsetsTypes(int)} with * {@link Type#navigationBars()}. For non-floating windows that fill the screen, call - * {@link Window#setOnContentApplyWindowInsetsListener} with {@code null} or a listener that - * doesn't fit the navigation bar on the window content level. + * {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}. */ public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200; @@ -3683,8 +3682,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @deprecated For floating windows, use {@link LayoutParams#setFitInsetsTypes(int)} with * {@link Type#statusBars()} ()}. For non-floating windows that fill the screen, call - * {@link Window#setOnContentApplyWindowInsetsListener} with {@code null} or a listener that - * doesn't fit the status bar on the window content level. + * {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}. */ @Deprecated public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400; @@ -4673,7 +4671,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners; - private WindowInsetsAnimationCallback mWindowInsetsAnimationCallback; + WindowInsetsAnimationCallback mWindowInsetsAnimationCallback; /** * This lives here since it's only valid for interactive views. @@ -11537,7 +11535,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * @see #PFLAG4_OPTIONAL_FITS_SYSTEM_WINDOWS + * @see #PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS * @hide */ public void makeFrameworkOptionalFitsSystemWindows() { @@ -11545,6 +11543,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * @hide + */ + public boolean isFrameworkOptionalFitsSystemWindows() { + return (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0; + } + + /** * Returns the visibility status for this view. * * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 4f03ca152850..d416d420feda 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -7239,6 +7239,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public void dispatchWindowInsetsAnimationPrepare( @NonNull InsetsAnimation animation) { super.dispatchWindowInsetsAnimationPrepare(animation); + + // If we are root-level content view that fits insets, set dispatch mode to stop to imitate + // consume behavior. + boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0 + || isFrameworkOptionalFitsSystemWindows(); + if (isOptionalFitSystemWindows && mAttachInfo != null + && getListenerInfo().mWindowInsetsAnimationCallback == null + && mAttachInfo.mContentOnApplyWindowInsetsListener != null) { + mInsetsAnimationDispatchMode = DISPATCH_MODE_STOP; + return; + } + if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) { return; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 51ea30b41741..2b4b71f01aa5 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4887,8 +4887,14 @@ public final class ViewRootImpl implements ViewParent, break; case MSG_INSETS_CONTROL_CHANGED: { SomeArgs args = (SomeArgs) msg.obj; - mInsetsController.onControlsChanged((InsetsSourceControl[]) args.arg2); + + // Deliver state change before control change, such that: + // a) When gaining control, controller can compare with server state to evaluate + // whether it needs to run animation. + // b) When loosing control, controller can restore server state by taking last + // dispatched state as truth. mInsetsController.onStateChanged((InsetsState) args.arg1); + mInsetsController.onControlsChanged((InsetsSourceControl[]) args.arg2); break; } case MSG_SHOW_INSETS: { diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 0ef4e338f81c..4b284dbf9ed4 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -697,12 +697,10 @@ public abstract class Window { } /** - * Listener for applying window insets on the content of a window in a custom way. + * Listener for applying window insets on the content of a window. Used only by the framework to + * fit content according to legacy SystemUI flags. * - * <p>Apps may choose to implement this interface if they want to apply custom policy - * to the way that window insets are treated for fitting root-level content views. - * - * @see Window#setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener) + * @hide */ public interface OnContentApplyWindowInsetsListener { @@ -716,13 +714,15 @@ public abstract class Window { * * @param insets The root level insets that are about to be dispatched * @return A pair, with the first element containing the insets to apply as margin to the - * root-level content views, and the second element determining what should be - * dispatched to the content view. + * root-level content views, and the second element determining what should be + * dispatched to the content view. */ - @NonNull Pair<Insets, WindowInsets> onContentApplyWindowInsets( + @NonNull + Pair<Insets, WindowInsets> onContentApplyWindowInsets( @NonNull WindowInsets insets); } + public Window(Context context) { mContext = context; mFeatures = mLocalFeatures = getDefaultFeatures(context); @@ -1311,33 +1311,21 @@ public abstract class Window { } /** - * Sets the listener to be invoked when fitting root-level content views. + * Sets whether the decor view should fit root-level content views for {@link WindowInsets}. * <p> - * By default, a listener that inspects the now deprecated {@link View#SYSTEM_UI_LAYOUT_FLAGS} - * as well the {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag is installed and - * fits content according to these flags. + * If set to {@code true}, the framework will inspect the now deprecated + * {@link View#SYSTEM_UI_LAYOUT_FLAGS} as well the + * {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag and fits content according + * to these flags. * </p> - * @param contentOnApplyWindowInsetsListener The listener to use for fitting root-level content - * views, or {@code null} to disable any kind of - * content fitting on the window level and letting the - * {@link WindowInsets} pass through to the content - * view. - * @see OnContentApplyWindowInsetsListener - */ - public void setOnContentApplyWindowInsetsListener( - @Nullable OnContentApplyWindowInsetsListener contentOnApplyWindowInsetsListener) { - } - - /** - * Resets the listener set via {@link #setOnContentApplyWindowInsetsListener} to the default - * state. * <p> - * By default, a listener that inspects the now deprecated {@link View#SYSTEM_UI_LAYOUT_FLAGS} - * as well the {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag is installed and - * fits content according to these flags. + * If set to {@code false}, the framework will not fit the content view to the insets and will + * just pass through the {@link WindowInsets} to the content view. * </p> + * @param decorFitsSystemWindows Whether the decor view should fit root-level content views for + * insets. */ - public void resetOnContentApplyWindowInsetsListener() { + public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) { } /** diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java index c55caa3213d8..33f21f211a1a 100644 --- a/core/java/android/view/WindowContainerTransaction.java +++ b/core/java/android/view/WindowContainerTransaction.java @@ -86,6 +86,17 @@ public class WindowContainerTransaction implements Parcelable { return this; } + /** + * Set the smallestScreenWidth of a container. + */ + public WindowContainerTransaction setSmallestScreenWidthDp(IWindowContainer container, + int widthDp) { + Change cfg = getOrCreateChange(container.asBinder()); + cfg.mConfiguration.smallestScreenWidthDp = widthDp; + cfg.mConfigSetMask |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; + return this; + } + public Map<IBinder, Change> getChanges() { return mChanges; } diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java index 1e04d02fcb80..4c8463b37df4 100644 --- a/core/java/android/view/WindowInsetsAnimationCallback.java +++ b/core/java/android/view/WindowInsetsAnimationCallback.java @@ -86,9 +86,9 @@ public interface WindowInsetsAnimationCallback { * following: * <p> * <ul> - * <li>Application calls {@link WindowInsetsController#hideInputMethod()}, - * {@link WindowInsetsController#showInputMethod()}, - * {@link WindowInsetsController#controlInputMethodAnimation}</li> + * <li>Application calls {@link WindowInsetsController#hide(int)}, + * {@link WindowInsetsController#show(int)}, + * {@link WindowInsetsController#controlWindowInsetsAnimation}</li> * <li>onPrepare is called on the view hierarchy listeners</li> * <li>{@link View#onApplyWindowInsets} will be called with the end state of the * animation</li> @@ -106,12 +106,12 @@ public interface WindowInsetsAnimationCallback { * related methods. * <p> * Note: If the animation is application controlled by using - * {@link WindowInsetsController#controlInputMethodAnimation}, the end state of the animation + * {@link WindowInsetsController#controlWindowInsetsAnimation}, the end state of the animation * is undefined as the application may decide on the end state only by passing in the * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In this * situation, the system will dispatch the insets in the opposite visibility state before the * animation starts. Example: When controlling the input method with - * {@link WindowInsetsController#controlInputMethodAnimation} and the input method is currently + * {@link WindowInsetsController#controlWindowInsetsAnimation} and the input method is currently * showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets} instance for * which {@link WindowInsets#isVisible} will return {@code false} for {@link Type#ime}. * @@ -246,7 +246,7 @@ public interface WindowInsetsAnimationCallback { * be the same as the application passed into * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}, * interpolated with the interpolator passed into - * {@link WindowInsetsController#controlInputMethodAnimation}. + * {@link WindowInsetsController#controlWindowInsetsAnimation}. * </p> * <p> * Note: For system-initiated animations, this will always return a valid value between 0 diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java index f91254de33ff..701bd3158b1e 100644 --- a/core/java/android/view/WindowInsetsAnimationControlListener.java +++ b/core/java/android/view/WindowInsetsAnimationControlListener.java @@ -39,7 +39,7 @@ public interface WindowInsetsAnimationControlListener { * @param controller The controller to control the inset animation. * @param types The {@link InsetsType}s it was able to gain control over. Note that this may be * different than the types passed into - * {@link WindowInsetsController#controlInputMethodAnimation} in case the window + * {@link WindowInsetsController#controlWindowInsetsAnimation} in case the window * wasn't able to gain the controls because it wasn't the IME target or not * currently the window that's controlling the system bars. */ diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java index 2bf0d277268d..4a864be64f64 100644 --- a/core/java/android/view/WindowInsetsAnimationController.java +++ b/core/java/android/view/WindowInsetsAnimationController.java @@ -23,6 +23,7 @@ import android.annotation.SuppressLint; import android.graphics.Insets; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimationCallback.AnimationBounds; +import android.view.animation.Interpolator; /** * Controller for app-driven animation of system windows. @@ -32,7 +33,7 @@ import android.view.WindowInsetsAnimationCallback.AnimationBounds; * synchronized, such that changes the system windows and the app's current frame * are rendered at the same time. * <p> - * Control is obtained through {@link WindowInsetsController#controlInputMethodAnimation}. + * Control is obtained through {@link WindowInsetsController#controlWindowInsetsAnimation}. */ @SuppressLint("NotClosable") public interface WindowInsetsAnimationController { diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 02323cfb4f00..f501de91b3b7 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -167,62 +167,6 @@ public interface WindowInsetsController { @NonNull WindowInsetsAnimationControlListener listener); /** - * Lets the application control the animation for showing the IME in a frame-by-frame manner by - * modifying the position of the IME when it's causing insets. - * - * @param durationMillis Duration of the animation in - * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the - * animation doesn't have a predetermined duration. This value will be - * passed to {@link InsetsAnimation#getDurationMillis()} - * @param interpolator The interpolator used for this animation, or {@code null} if this - * animation doesn't follow an interpolation curve. This value will be - * passed to {@link InsetsAnimation#getInterpolator()} and used to calculate - * {@link InsetsAnimation#getInterpolatedFraction()}. - * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the - * IME are ready to be controlled, among other callbacks. - * - * @see InsetsAnimation#getFraction() - * @see InsetsAnimation#getInterpolatedFraction() - * @see InsetsAnimation#getInterpolator() - * @see InsetsAnimation#getDurationMillis() - */ - default void controlInputMethodAnimation(long durationMillis, - @Nullable Interpolator interpolator, - @NonNull WindowInsetsAnimationControlListener listener) { - controlWindowInsetsAnimation(ime(), durationMillis, interpolator, listener); - } - - /** - * Makes the IME appear on screen. - * <p> - * Note that if the window currently doesn't have control over the IME, because it doesn't have - * focus, it will apply the change as soon as the window gains control. The app can listen to - * the event by observing {@link View#onApplyWindowInsets} and checking visibility with - * {@link WindowInsets#isVisible}. - * - * @see #controlInputMethodAnimation - * @see #hideInputMethod() - */ - default void showInputMethod() { - show(ime()); - } - - /** - * Makes the IME disappear on screen. - * <p> - * Note that if the window currently doesn't have control over IME, because it doesn't have - * focus, it will apply the change as soon as the window gains control. The app can listen to - * the event by observing {@link View#onApplyWindowInsets} and checking visibility with - * {@link WindowInsets#isVisible}. - * - * @see #controlInputMethodAnimation - * @see #showInputMethod() - */ - default void hideInputMethod() { - hide(ime()); - } - - /** * Controls the appearance of system bars. * <p> * For example, the following statement adds {@link #APPEARANCE_LIGHT_STATUS_BARS}: diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index a6450a10561b..dad767142ba1 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -80,6 +80,7 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; import android.util.proto.ProtoOutputStream; +import android.view.View.OnApplyWindowInsetsListener; import android.view.WindowInsets.Side; import android.view.WindowInsets.Side.InsetsSide; import android.view.WindowInsets.Type; @@ -2193,8 +2194,9 @@ public interface WindowManager extends ViewManager { * value for {@link #softInputMode} will be ignored; the window will * not resize, but will stay fullscreen. * - * @deprecated Use {@link Window#setOnContentApplyWindowInsetsListener} instead with a - * listener that fits {@link Type#ime()} instead. + * @deprecated Call {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false} and + * install an {@link OnApplyWindowInsetsListener} on your root content view that fits insets + * of type {@link Type#ime()}. */ @Deprecated public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10; diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 051534cc3eb1..bbb751359bab 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -359,7 +359,7 @@ public class ResolverActivity extends Activity implements mMultiProfilePagerAdapter.getPersonalListAdapter()); mPersonalPackageMonitor.register( this, getMainLooper(), getPersonalProfileUserHandle(), false); - if (hasWorkProfile()) { + if (hasWorkProfile() && ENABLE_TABBED_VIEW) { mWorkPackageMonitor = createPackageMonitor( mMultiProfilePagerAdapter.getWorkListAdapter()); mWorkPackageMonitor.register(this, getMainLooper(), getWorkProfileUserHandle(), false); @@ -725,7 +725,7 @@ public class ResolverActivity extends Activity implements if (!mRegistered) { mPersonalPackageMonitor.register(this, getMainLooper(), getPersonalProfileUserHandle(), false); - if (hasWorkProfile()) { + if (hasWorkProfile() && ENABLE_TABBED_VIEW) { if (mWorkPackageMonitor == null) { mWorkPackageMonitor = createPackageMonitor( mMultiProfilePagerAdapter.getWorkListAdapter()); @@ -1155,7 +1155,9 @@ public class ResolverActivity extends Activity implements } private void safelyStartActivityInternal(TargetInfo cti) { - mPersonalPackageMonitor.unregister(); + if (mPersonalPackageMonitor != null) { + mPersonalPackageMonitor.unregister(); + } if (mWorkPackageMonitor != null) { mWorkPackageMonitor.unregister(); } diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java index e0eb9af8b228..5e886a611913 100644 --- a/core/java/com/android/internal/compat/ChangeReporter.java +++ b/core/java/com/android/internal/compat/ChangeReporter.java @@ -16,13 +16,16 @@ package com.android.internal.compat; +import android.annotation.IntDef; import android.util.Log; import android.util.Slog; -import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -42,7 +45,7 @@ public final class ChangeReporter { long mChangeId; int mState; - ChangeReport(long changeId, int state) { + ChangeReport(long changeId, @State int state) { mChangeId = changeId; mState = state; } @@ -69,7 +72,7 @@ public final class ChangeReporter { // When true will of every time to debug (logcat). private boolean mDebugLogAll; - public ChangeReporter(int source) { + public ChangeReporter(@Source int source) { mSource = source; mReportedChanges = new HashMap<>(); mDebugLogAll = false; @@ -85,8 +88,8 @@ public final class ChangeReporter { */ public void reportChange(int uid, long changeId, int state) { if (shouldWriteToStatsLog(uid, changeId, state)) { - StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId, - state, mSource); + FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, + changeId, state, mSource); } if (shouldWriteToDebug(uid, changeId, state)) { debugLog(uid, changeId, state); @@ -110,7 +113,7 @@ public final class ChangeReporter { /** - * Returns whether the next report should be logged to statsLog. + * Returns whether the next report should be logged to FrameworkStatsLog. * * @param uid affected by the change * @param changeId the reported change id @@ -174,7 +177,7 @@ public final class ChangeReporter { private void debugLog(int uid, long changeId, int state) { String message = String.format("Compat change id reported: %d; UID %d; state: %s", changeId, uid, stateToString(state)); - if (mSource == StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER) { + if (mSource == SOURCE_SYSTEM_SERVER) { Slog.d(TAG, message); } else { Log.d(TAG, message); @@ -183,21 +186,56 @@ public final class ChangeReporter { } /** - * Transforms StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE enum to a string. + * Transforms {@link #ChangeReporter.State} enum to a string. * * @param state to transform * @return a string representing the state */ - private static String stateToString(int state) { + private static String stateToString(@State int state) { switch (state) { - case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED: + case STATE_LOGGED: return "LOGGED"; - case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED: + case STATE_ENABLED: return "ENABLED"; - case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED: + case STATE_DISABLED: return "DISABLED"; default: return "UNKNOWN"; } } + + /** These values should be kept in sync with those in atoms.proto */ + public static final int STATE_UNKNOWN_STATE = + FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__UNKNOWN_STATE; + public static final int STATE_ENABLED = + FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED; + public static final int STATE_DISABLED = + FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED; + public static final int STATE_LOGGED = + FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED; + public static final int SOURCE_UNKNOWN_SOURCE = + FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__UNKNOWN_SOURCE; + public static final int SOURCE_APP_PROCESS = + FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS; + public static final int SOURCE_SYSTEM_SERVER = + FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "STATE_" }, value = { + STATE_UNKNOWN_STATE, + STATE_ENABLED, + STATE_DISABLED, + STATE_LOGGED + }) + public @interface State { + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "SOURCE_" }, value = { + SOURCE_UNKNOWN_SOURCE, + SOURCE_APP_PROCESS, + SOURCE_SYSTEM_SERVER + }) + public @interface Source { + } } diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index db009f68d28a..c6ed624d73ac 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -67,9 +67,10 @@ public class RuntimeInit { private static volatile boolean mCrashing = false; - /* + /** * Native heap allocations will now have a non-zero tag in the most significant byte. - * See {@linktourl https://source.android.com/devices/tech/debug/tagged-pointers}. + * See + * <a href="https://source.android.com/devices/tech/debug/tagged-pointers">https://source.android.com/devices/tech/debug/tagged-pointers</a>. */ @ChangeId @EnabledAfter(targetSdkVersion = VersionCodes.Q) diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index f13a638f7844..46d7f4ec4c85 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -320,7 +320,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { /** @see ViewRootImpl#mActivityConfigCallback */ private ActivityConfigCallback mActivityConfigCallback; - private OnContentApplyWindowInsetsListener mPendingOnContentApplyWindowInsetsListener; + private OnContentApplyWindowInsetsListener mPendingOnContentApplyWindowInsetsListener + = createDefaultContentWindowInsetsListener(); static class WindowManagerHolder { static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( @@ -2109,14 +2110,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { /** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */ void onViewRootImplSet(ViewRootImpl viewRoot) { viewRoot.setActivityConfigCallback(mActivityConfigCallback); - if (mPendingOnContentApplyWindowInsetsListener != null) { - viewRoot.setOnContentApplyWindowInsetsListener( - mPendingOnContentApplyWindowInsetsListener); - mPendingOnContentApplyWindowInsetsListener = null; - } else { - viewRoot.setOnContentApplyWindowInsetsListener( - createDefaultContentWindowInsetsListener()); - } + viewRoot.setOnContentApplyWindowInsetsListener( + mPendingOnContentApplyWindowInsetsListener); + mPendingOnContentApplyWindowInsetsListener = null; } private OnContentApplyWindowInsetsListener createDefaultContentWindowInsetsListener() { @@ -2125,8 +2121,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return new Pair<>(Insets.NONE, insets); } - boolean includeIme = (getAttributes().softInputMode & SOFT_INPUT_MASK_ADJUST) - == SOFT_INPUT_ADJUST_RESIZE; + boolean includeIme = + (getViewRootImpl().mWindowAttributes.softInputMode & SOFT_INPUT_MASK_ADJUST) + == SOFT_INPUT_ADJUST_RESIZE; Insets insetsToApply; if (ViewRootImpl.sNewInsetsMode == 0) { insetsToApply = insets.getSystemWindowInsets(); @@ -3902,17 +3899,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @Override - public void setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener listener) { + public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) { ViewRootImpl impl = getViewRootImpl(); + OnContentApplyWindowInsetsListener listener = decorFitsSystemWindows + ? createDefaultContentWindowInsetsListener() + : null; if (impl != null) { impl.setOnContentApplyWindowInsetsListener(listener); } else { mPendingOnContentApplyWindowInsetsListener = listener; } } - - @Override - public void resetOnContentApplyWindowInsetsListener() { - setOnContentApplyWindowInsetsListener(createDefaultContentWindowInsetsListener()); - } } diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index 408a7a8e139a..a87e080fa522 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -21,7 +21,9 @@ import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.text.TextUtils; +import java.util.Arrays; import java.util.Collection; +import java.util.Objects; /** * Simple static methods to be called at the start of your own methods to verify @@ -497,6 +499,64 @@ public class Preconditions { } /** + * Ensures that the given byte array is not {@code null}, and contains at least one element. + * + * @param value an array of elements. + * @param valueName the name of the argument to use if the check fails. + + * @return the validated array + * + * @throws NullPointerException if the {@code value} was {@code null} + * @throws IllegalArgumentException if the {@code value} was empty + */ + @NonNull + public static byte[] checkByteArrayNotEmpty(final byte[] value, final String valueName) { + if (value == null) { + throw new NullPointerException(valueName + " must not be null"); + } + if (value.length == 0) { + throw new IllegalArgumentException(valueName + " is empty"); + } + return value; + } + + /** + * Ensures that argument {@code value} is one of {@code supportedValues}. + * + * @param supportedValues an array of string values + * @param value a string value + * + * @return the validated value + * + * @throws NullPointerException if either {@code value} or {@code supportedValues} is null + * @throws IllegalArgumentException if the {@code value} is not in {@code supportedValues} + */ + @NonNull + public static String checkArgumentIsSupported(final String[] supportedValues, + final String value) { + checkNotNull(value); + checkNotNull(supportedValues); + + if (!contains(supportedValues, value)) { + throw new IllegalArgumentException(value + "is not supported " + + Arrays.toString(supportedValues)); + } + return value; + } + + private static boolean contains(String[] values, String value) { + if (values == null) { + return false; + } + for (int i = 0; i < values.length; ++i) { + if (Objects.equals(value, values[i])) { + return true; + } + } + return false; + } + + /** * Ensures that all elements in the argument floating point array are within the inclusive range * * <p>While this can be used to range check against +/- infinity, note that all NaN numbers diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 212630f13ece..cec68df3c949 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -137,6 +137,7 @@ cc_library_shared { "android_os_Parcel.cpp", "android_os_SELinux.cpp", "android_os_SharedMemory.cpp", + "android_os_storage_StorageManager.cpp", "android_os_Trace.cpp", "android_os_UEventObserver.cpp", "android_os_VintfObject.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 9783b655e057..481be241cc99 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -144,6 +144,7 @@ extern int register_android_os_Parcel(JNIEnv* env); extern int register_android_os_SELinux(JNIEnv* env); extern int register_android_os_VintfObject(JNIEnv *env); extern int register_android_os_VintfRuntimeInfo(JNIEnv *env); +extern int register_android_os_storage_StorageManager(JNIEnv* env); extern int register_android_os_SystemProperties(JNIEnv *env); extern int register_android_os_SystemClock(JNIEnv* env); extern int register_android_os_Trace(JNIEnv* env); @@ -622,6 +623,8 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p char jitprithreadweightOptBuf[sizeof("-Xjitprithreadweight:")-1 + PROPERTY_VALUE_MAX]; char jittransitionweightOptBuf[sizeof("-Xjittransitionweight:")-1 + PROPERTY_VALUE_MAX]; char hotstartupsamplesOptsBuf[sizeof("-Xps-hot-startup-method-samples:")-1 + PROPERTY_VALUE_MAX]; + char saveResolvedClassesDelayMsOptsBuf[ + sizeof("-Xps-save-resolved-classes-delay-ms:")-1 + PROPERTY_VALUE_MAX]; char madviseRandomOptsBuf[sizeof("-XX:MadviseRandomAccess:")-1 + PROPERTY_VALUE_MAX]; char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX]; char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX]; @@ -817,6 +820,9 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p parseRuntimeOption("dalvik.vm.hot-startup-method-samples", hotstartupsamplesOptsBuf, "-Xps-hot-startup-method-samples:"); + parseRuntimeOption("dalvik.vm.ps-resolved-classes-delay-ms", saveResolvedClassesDelayMsOptsBuf, + "-Xps-save-resolved-classes-delay-ms:"); + property_get("ro.config.low_ram", propBuf, ""); if (strcmp(propBuf, "true") == 0) { addOption("-XX:LowMemoryMode"); @@ -1463,6 +1469,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_HwParcel), REG_JNI(register_android_os_HwRemoteBinder), REG_JNI(register_android_os_NativeHandle), + REG_JNI(register_android_os_storage_StorageManager), REG_JNI(register_android_os_VintfObject), REG_JNI(register_android_os_VintfRuntimeInfo), REG_JNI(register_android_service_DataLoaderService), diff --git a/core/jni/android_os_storage_StorageManager.cpp b/core/jni/android_os_storage_StorageManager.cpp new file mode 100644 index 000000000000..aee6733ecf53 --- /dev/null +++ b/core/jni/android_os_storage_StorageManager.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "StorageManager" +#include <android-base/logging.h> +#include <android-base/unique_fd.h> +#include <fcntl.h> +#include <linux/fs.h> + +#include <nativehelper/JNIHelp.h> +#include "core_jni_helpers.h" + +namespace android { + +jboolean android_os_storage_StorageManager_setQuotaProjectId(JNIEnv* env, jobject self, + jstring path, jlong projectId) { + struct fsxattr fsx; + ScopedUtfChars utf_chars_path(env, path); + + if (projectId > UINT32_MAX) { + LOG(ERROR) << "Invalid project id: " << projectId; + return JNI_FALSE; + } + + android::base::unique_fd fd( + TEMP_FAILURE_RETRY(open(utf_chars_path.c_str(), O_RDONLY | O_CLOEXEC))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << utf_chars_path.c_str() << " to set project id."; + return JNI_FALSE; + } + + int ret = ioctl(fd, FS_IOC_FSGETXATTR, &fsx); + if (ret == -1) { + PLOG(ERROR) << "Failed to get extended attributes for " << utf_chars_path.c_str() + << " to get project id."; + return JNI_FALSE; + } + + fsx.fsx_projid = projectId; + ret = ioctl(fd, FS_IOC_FSSETXATTR, &fsx); + if (ret == -1) { + PLOG(ERROR) << "Failed to set extended attributes for " << utf_chars_path.c_str() + << " to set project id."; + return JNI_FALSE; + } + + return JNI_TRUE; +} + +// ---------------------------------------------------------------------------- + +static const JNINativeMethod gStorageManagerMethods[] = { + {"setQuotaProjectId", "(Ljava/lang/String;J)Z", + (void*)android_os_storage_StorageManager_setQuotaProjectId}, +}; + +const char* const kStorageManagerPathName = "android/os/storage/StorageManager"; + +int register_android_os_storage_StorageManager(JNIEnv* env) { + return RegisterMethodsOrDie(env, kStorageManagerPathName, gStorageManagerMethods, + NELEM(gStorageManagerMethods)); +} + +}; // namespace android diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 1fcc8acb5879..cfd3c0915002 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -51,6 +51,7 @@ static const char* kPathWhitelist[] = { "/dev/socket/webview_zygote", "/dev/socket/heapprofd", "/sys/kernel/debug/tracing/trace_marker", + "/sys/kernel/tracing/trace_marker", "/system/framework/framework-res.apk", "/dev/urandom", "/dev/ion", diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index c0743e58b5c5..7c8f62c14b1e 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -150,9 +150,9 @@ message DisplayContentProto { // Will be removed soon. optional PinnedStackControllerProto pinned_stack_controller = 5 [deprecated=true]; /* non app windows */ - repeated WindowTokenProto above_app_windows = 6; - repeated WindowTokenProto below_app_windows = 7; - repeated WindowTokenProto ime_windows = 8; + repeated WindowTokenProto above_app_windows = 6 [deprecated=true]; + repeated WindowTokenProto below_app_windows = 7 [deprecated=true]; + repeated WindowTokenProto ime_windows = 8 [deprecated=true]; optional int32 dpi = 9; optional .android.view.DisplayInfoProto display_info = 10; optional int32 rotation = 11; @@ -165,8 +165,33 @@ message DisplayContentProto { repeated IdentifierProto closing_apps = 18; repeated IdentifierProto changing_apps = 19; repeated WindowTokenProto overlay_windows = 20; + optional DisplayAreaProto root_display_area = 21; } +/* represents DisplayArea object */ +message DisplayAreaProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional WindowContainerProto window_container = 1; + optional string name = 2 [ (.android.privacy).dest = DEST_EXPLICIT ]; + repeated DisplayAreaChildProto children = 3; +} + +/* represents a generic child of a DisplayArea */ +message DisplayAreaChildProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + /* At most one of the following should be present: */ + + /* represents a DisplayArea child */ + optional DisplayAreaProto display_area = 1; + /* represents a WindowToken child */ + optional WindowTokenProto window = 2; + /* represents an unknown child - the class name is recorded */ + repeated string unknown = 3; +} + + /* represents DisplayFrames */ message DisplayFramesProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index cfed805d5d88..059bc443a609 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1781,8 +1781,11 @@ <attr name="crossProfile" format="boolean" /> </declare-styleable> - <!-- The <code>feature</code> tag declares a feature. A feature is a part of an app. E.g. - photo sharing app might include a direct messaging component. + <!-- The <code>feature</code> tag declares a feature. A feature is a logical part of an app. + E.g. photo sharing app might include a direct messaging component. To tag certain code as + belonging to a feature, use a context created via + {@link android.content.Context#createFeatureContext(String)} for any interaction with the + system. <p>This appears as a child tag of the root {@link #AndroidManifest manifest} tag. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 21128e33b6e5..21d1d3cf9c89 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1266,6 +1266,7 @@ <java-symbol type="array" name="vendor_disallowed_apps_managed_profile" /> <java-symbol type="array" name="vendor_disallowed_apps_managed_device" /> <java-symbol type="array" name="cross_profile_apps" /> + <java-symbol type="array" name="vendor_cross_profile_apps" /> <java-symbol type="drawable" name="default_wallpaper" /> <java-symbol type="drawable" name="default_lock_wallpaper" /> diff --git a/core/res/res/values/vendor_cross_profile_apps.xml b/core/res/res/values/vendor_cross_profile_apps.xml new file mode 100644 index 000000000000..32839cd17e1d --- /dev/null +++ b/core/res/res/values/vendor_cross_profile_apps.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources> + <!-- + A collection of apps that have been pre-approved for cross-profile communication. + These will not require admin consent, but will still require user consent during provisioning. + --> + <string-array translatable="false" name="vendor_cross_profile_apps"> + </string-array> +</resources> diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java index 09bbe37bf292..a052543c6446 100644 --- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java +++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java @@ -24,10 +24,10 @@ import org.junit.Test; public class ChangeReporterTest { @Test public void testStatsLogOnce() { - ChangeReporter reporter = new ChangeReporter(0); + ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); int myUid = 1022, otherUid = 1023; long myChangeId = 500L, otherChangeId = 600L; - int myState = 1, otherState = 2; + int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED; assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState)); reporter.reportChange(myUid, myChangeId, myState); @@ -42,10 +42,10 @@ public class ChangeReporterTest { @Test public void testStatsLogAfterReset() { - ChangeReporter reporter = new ChangeReporter(0); + ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); int myUid = 1022; long myChangeId = 500L; - int myState = 1; + int myState = ChangeReporter.STATE_ENABLED; assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState)); reporter.reportChange(myUid, myChangeId, myState); @@ -60,10 +60,10 @@ public class ChangeReporterTest { @Test public void testDebugLogOnce() { - ChangeReporter reporter = new ChangeReporter(0); + ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); int myUid = 1022, otherUid = 1023; long myChangeId = 500L, otherChangeId = 600L; - int myState = 1, otherState = 2; + int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED; assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState)); reporter.reportChange(myUid, myChangeId, myState); @@ -78,10 +78,10 @@ public class ChangeReporterTest { @Test public void testDebugLogAfterReset() { - ChangeReporter reporter = new ChangeReporter(0); + ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); int myUid = 1022; long myChangeId = 500L; - int myState = 1; + int myState = ChangeReporter.STATE_ENABLED; assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState)); reporter.reportChange(myUid, myChangeId, myState); @@ -96,10 +96,10 @@ public class ChangeReporterTest { @Test public void testDebugLogWithLogAll() { - ChangeReporter reporter = new ChangeReporter(0); + ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); int myUid = 1022; long myChangeId = 500L; - int myState = 1; + int myState = ChangeReporter.STATE_ENABLED; assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState)); reporter.reportChange(myUid, myChangeId, myState); diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java index ba29a97b55ab..d17b63597a21 100644 --- a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java +++ b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java @@ -27,17 +27,17 @@ import android.os.TimestampedValue; import org.junit.Test; public class PhoneTimeSuggestionTest { - private static final int PHONE_ID = 99999; + private static final int SLOT_INDEX = 99999; @Test public void testEquals() { - PhoneTimeSuggestion.Builder builder1 = new PhoneTimeSuggestion.Builder(PHONE_ID); + PhoneTimeSuggestion.Builder builder1 = new PhoneTimeSuggestion.Builder(SLOT_INDEX); { PhoneTimeSuggestion one = builder1.build(); assertEquals(one, one); } - PhoneTimeSuggestion.Builder builder2 = new PhoneTimeSuggestion.Builder(PHONE_ID); + PhoneTimeSuggestion.Builder builder2 = new PhoneTimeSuggestion.Builder(SLOT_INDEX); { PhoneTimeSuggestion one = builder1.build(); PhoneTimeSuggestion two = builder2.build(); @@ -59,7 +59,7 @@ public class PhoneTimeSuggestionTest { assertEquals(two, one); } - PhoneTimeSuggestion.Builder builder3 = new PhoneTimeSuggestion.Builder(PHONE_ID + 1); + PhoneTimeSuggestion.Builder builder3 = new PhoneTimeSuggestion.Builder(SLOT_INDEX + 1); builder3.setUtcTime(new TimestampedValue<>(1111L, 2222L)); { PhoneTimeSuggestion one = builder1.build(); @@ -80,7 +80,7 @@ public class PhoneTimeSuggestionTest { @Test public void testParcelable() { - PhoneTimeSuggestion.Builder builder = new PhoneTimeSuggestion.Builder(PHONE_ID); + PhoneTimeSuggestion.Builder builder = new PhoneTimeSuggestion.Builder(SLOT_INDEX); assertRoundTripParcelable(builder.build()); builder.setUtcTime(new TimestampedValue<>(1111L, 2222L)); diff --git a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java index 0108a0bbab47..384dbf9fab07 100644 --- a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java +++ b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java @@ -26,17 +26,17 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; public class PhoneTimeZoneSuggestionTest { - private static final int PHONE_ID = 99999; + private static final int SLOT_INDEX = 99999; @Test public void testEquals() { - PhoneTimeZoneSuggestion.Builder builder1 = new PhoneTimeZoneSuggestion.Builder(PHONE_ID); + PhoneTimeZoneSuggestion.Builder builder1 = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); { PhoneTimeZoneSuggestion one = builder1.build(); assertEquals(one, one); } - PhoneTimeZoneSuggestion.Builder builder2 = new PhoneTimeZoneSuggestion.Builder(PHONE_ID); + PhoneTimeZoneSuggestion.Builder builder2 = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); { PhoneTimeZoneSuggestion one = builder1.build(); PhoneTimeZoneSuggestion two = builder2.build(); @@ -45,7 +45,7 @@ public class PhoneTimeZoneSuggestionTest { } PhoneTimeZoneSuggestion.Builder builder3 = - new PhoneTimeZoneSuggestion.Builder(PHONE_ID + 1); + new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX + 1); { PhoneTimeZoneSuggestion one = builder1.build(); PhoneTimeZoneSuggestion three = builder3.build(); @@ -120,7 +120,7 @@ public class PhoneTimeZoneSuggestionTest { @Test(expected = RuntimeException.class) public void testBuilderValidates_emptyZone_badMatchType() { - PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID); + PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); // No zone ID, so match type should be left unset. builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET); builder.build(); @@ -128,7 +128,7 @@ public class PhoneTimeZoneSuggestionTest { @Test(expected = RuntimeException.class) public void testBuilderValidates_zoneSet_badMatchType() { - PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID); + PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); builder.setZoneId("Europe/London"); builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); builder.build(); @@ -136,7 +136,7 @@ public class PhoneTimeZoneSuggestionTest { @Test public void testParcelable() { - PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID); + PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); assertRoundTripParcelable(builder.build()); builder.setZoneId("Europe/London"); diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java index 5cb7852f7acc..ea0a0fd49f9f 100644 --- a/core/tests/coretests/src/android/os/PowerManagerTest.java +++ b/core/tests/coretests/src/android/os/PowerManagerTest.java @@ -214,6 +214,9 @@ public class PowerManagerTest extends AndroidTestCase { } // Add listener2 on main thread. mPm.addThermalStatusListener(mListener2); + verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC) + .times(1)).onThermalStatusChanged(status); + reset(mListener2); status = PowerManager.THERMAL_STATUS_MODERATE; mUiDevice.executeShellCommand("cmd thermalservice override-status " + Integer.toString(status)); diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 169716f99dea..bdb802195d8b 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -109,13 +109,14 @@ public class InsetsAnimationControlImplTest { InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mInsetsState, () -> mMockTransaction, mMockController); topConsumer.setControl( - new InsetsSourceControl(ITYPE_STATUS_BAR, mTopLeash, new Point(0, 0))); + new InsetsSourceControl(ITYPE_STATUS_BAR, mTopLeash, new Point(0, 0)), + new int[1], new int[1]); InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ITYPE_NAVIGATION_BAR, mInsetsState, () -> mMockTransaction, mMockController); - navConsumer.hide(); navConsumer.setControl(new InsetsSourceControl(ITYPE_NAVIGATION_BAR, mNavLeash, - new Point(400, 0))); + new Point(400, 0)), new int[1], new int[1]); + navConsumer.hide(); SparseArray<InsetsSourceControl> controls = new SparseArray<>(); controls.put(ITYPE_STATUS_BAR, topConsumer.getControl()); diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index e68c4b8d2ab3..2c9dba1d59fd 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -121,9 +121,20 @@ public class InsetsControllerTest { if (type == ITYPE_IME) { return new InsetsSourceConsumer(type, controller.getState(), Transaction::new, controller) { + + private boolean mImeRequestedShow; + + @Override + public void show(boolean fromIme) { + super.show(fromIme); + if (fromIme) { + mImeRequestedShow = true; + } + } + @Override public int requestShow(boolean fromController) { - if (fromController) { + if (fromController || mImeRequestedShow) { return SHOW_IMMEDIATELY; } else { return IME_SHOW_DELAYED; @@ -399,6 +410,84 @@ public class InsetsControllerTest { } @Test + public void testRestoreStartsAnimation() { + InsetsSourceControl control = + new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); + mController.onControlsChanged(new InsetsSourceControl[]{control}); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mController.hide(Type.statusBars()); + mController.cancelExistingAnimation(); + assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible()); + assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible()); + + // Loosing control + InsetsState state = new InsetsState(mController.getState()); + state.setSourceVisible(ITYPE_STATUS_BAR, true); + mController.onStateChanged(state); + mController.onControlsChanged(new InsetsSourceControl[0]); + assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible()); + assertTrue(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible()); + + // Gaining control + mController.onControlsChanged(new InsetsSourceControl[]{control}); + assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); + mController.cancelExistingAnimation(); + assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible()); + assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible()); + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + @Test + public void testStartImeAnimationAfterGettingControl() { + InsetsSourceControl control = + new InsetsSourceControl(ITYPE_IME, mLeash, new Point()); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + + mController.show(ime()); + assertFalse(mController.getState().getSource(ITYPE_IME).isVisible()); + + // Pretend IME is calling + mController.show(ime(), true /* fromIme */); + + // Gaining control shortly after + mController.onControlsChanged(new InsetsSourceControl[]{control}); + + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME)); + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible()); + assertTrue(mController.getState().getSource(ITYPE_IME).isVisible()); + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + @Test + public void testStartImeAnimationAfterGettingControl_imeLater() { + InsetsSourceControl control = + new InsetsSourceControl(ITYPE_IME, mLeash, new Point()); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + + mController.show(ime()); + assertFalse(mController.getState().getSource(ITYPE_IME).isVisible()); + + // Gaining control shortly after + mController.onControlsChanged(new InsetsSourceControl[]{control}); + + // Pretend IME is calling + mController.show(ime(), true /* fromIme */); + + assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME)); + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible()); + assertTrue(mController.getState().getSource(ITYPE_IME).isVisible()); + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + @Test public void testAnimationEndState_controller() throws Exception { InsetsSourceControl control = new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); @@ -432,7 +521,7 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { WindowInsetsAnimationControlListener listener = mock(WindowInsetsAnimationControlListener.class); - mController.controlInputMethodAnimation(0, new LinearInterpolator(), listener); + mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener); // Ready gets deferred until next predraw mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); @@ -456,7 +545,7 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { WindowInsetsAnimationControlListener listener = mock(WindowInsetsAnimationControlListener.class); - mController.controlInputMethodAnimation(0, new LinearInterpolator(), listener); + mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener); // Ready gets deferred until next predraw mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); @@ -476,7 +565,7 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { WindowInsetsAnimationControlListener listener = mock(WindowInsetsAnimationControlListener.class); - mController.controlInputMethodAnimation(0, new LinearInterpolator(), listener); + mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener); // Ready gets deferred until next predraw mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 492c03653990..5e9e2f0065ed 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -18,6 +18,8 @@ package android.view; import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.WindowInsets.Type.statusBars; +import static junit.framework.Assert.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; @@ -90,34 +92,44 @@ public class InsetsSourceConsumerTest { }); instrumentation.waitForIdleSync(); - mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point())); + mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()), + new int[1], new int[1]); } @Test public void testHide() { - mConsumer.hide(); - assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible()); - verify(mSpyInsetsSource).setVisible(eq(false)); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mConsumer.hide(); + assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible()); + verify(mSpyInsetsSource).setVisible(eq(false)); + }); + } @Test public void testShow() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // Insets source starts out visible + mConsumer.hide(); + mConsumer.show(false /* fromIme */); + assertTrue("Consumer should be visible", mConsumer.isRequestedVisible()); + verify(mSpyInsetsSource).setVisible(eq(false)); + verify(mSpyInsetsSource).setVisible(eq(true)); + }); - // Insets source starts out visible - mConsumer.hide(); - mConsumer.show(); - assertTrue("Consumer should be visible", mConsumer.isRequestedVisible()); - verify(mSpyInsetsSource).setVisible(eq(false)); - verify(mSpyInsetsSource).setVisible(eq(true)); } @Test public void testRestore() { - mConsumer.setControl(null); - reset(mMockTransaction); - mConsumer.hide(); - verifyZeroInteractions(mMockTransaction); - mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point())); - verify(mMockTransaction).hide(eq(mLeash)); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mConsumer.setControl(null, new int[1], new int[1]); + reset(mMockTransaction); + mConsumer.hide(); + verifyZeroInteractions(mMockTransaction); + int[] hideTypes = new int[1]; + mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()), + new int[1], hideTypes); + assertEquals(statusBars(), hideTypes[0]); + }); } } diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 20be8c22ccfc..79ac2b77dda5 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -213,6 +213,7 @@ public class InsetsStateTest { @Test public void testParcelUnparcel() { mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_IME).setVisibleFrame(new Rect(0, 0, 50, 10)); mState.getSource(ITYPE_IME).setVisible(true); mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); Parcel p = Parcel.obtain(); @@ -224,6 +225,16 @@ public class InsetsStateTest { } @Test + public void testCopy() { + mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_IME).setVisibleFrame(new Rect(0, 0, 50, 10)); + mState.getSource(ITYPE_IME).setVisible(true); + mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState2.set(mState, true); + assertEquals(mState, mState2); + } + + @Test public void testGetDefaultVisibility() { assertTrue(InsetsState.getDefaultVisibility(ITYPE_STATUS_BAR)); assertTrue(InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR)); diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 91c5fbec888a..9da185b95279 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1183,12 +1183,6 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, - "292555239": { - "message": "ScreenRotation sill animating: mDisplayAnimator: %s\nmEnterBlackFrameAnimator: %s\nmRotateScreenAnimator: %s\nmScreenshotRotationAnimator: %s", - "level": "VERBOSE", - "group": "WM_DEBUG_ORIENTATION", - "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java" - }, "292904800": { "message": "Deferring rotation, animation in progress.", "level": "VERBOSE", @@ -1573,12 +1567,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "1004585481": { - "message": "%s forcing orientation to %d for display id=%d", - "level": "VERBOSE", - "group": "WM_DEBUG_ORIENTATION", - "at": "com\/android\/server\/wm\/DisplayContent.java" - }, "1051545910": { "message": "Exit animation finished in %s: remove=%b", "level": "VERBOSE", @@ -1699,6 +1687,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "1346895820": { + "message": "ScreenRotation still animating: type: %d\nmDisplayAnimator: %s\nmEnterBlackFrameAnimator: %s\nmRotateScreenAnimator: %s\nmScreenshotRotationAnimator: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java" + }, "1358462645": { "message": "Looking for focus: %s, flags=%d, canReceive=%b", "level": "VERBOSE", @@ -1717,6 +1711,12 @@ "group": "WM_DEBUG_IME", "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java" }, + "1389009035": { + "message": "NonAppWindowContainer cannot set orientation: %s", + "level": "WARN", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "1401700824": { "message": "Window drawn win=%s", "level": "DEBUG", @@ -1891,6 +1891,12 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1674747211": { + "message": "%s forcing orientation to %d for display id=%d", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayArea.java" + }, "1677260366": { "message": "Finish starting %s: first real window is shown, no animation", "level": "VERBOSE", diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp index 551bdc63121d..234f42d79cb7 100644 --- a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp @@ -39,14 +39,13 @@ namespace skiapipeline { * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking. * Only GPU Texture memory is tracked separately and everything else is grouped as one - * "GPU Memory" category. + * "Misc Memory" category. */ static std::unordered_map<const char*, const char*> sResourceMap = { {"malloc", "HWUI CPU Memory"}, // taken from setMemoryBacking(backingType) {"gl_texture", "HWUI Texture Memory"}, // taken from setMemoryBacking(backingType) - {"Texture", - "HWUI Texture Memory"}, // taken from dumpStringValue(value, valueName="type") - // Uncomment categories below to split "GPU Memory" into more brackets for debugging. + {"Texture", "HWUI Texture Memory"}, // taken from dumpStringValue(value, valueName="type") + // Uncomment categories below to split "Misc Memory" into more brackets for debugging. /*{"vk_buffer", "vk_buffer"}, {"gl_renderbuffer", "gl_renderbuffer"}, {"gl_buffer", "gl_buffer"}, @@ -169,8 +168,8 @@ void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) { mLastDumpValue = 0; mLastPurgeableDumpValue = INVALID_MEMORY_SIZE; mLastDumpName = dumpName; - // Categories not listed in sResourceMap are reported as "GPU memory" - mCategory = "HWUI GPU Memory"; + // Categories not listed in sResourceMap are reported as "Misc Memory" + mCategory = "HWUI Misc Memory"; } } /* namespace skiapipeline */ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 5993e176f0b8..699b96a685c9 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -143,10 +143,11 @@ void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) { ATRACE_CALL(); if (surface) { - mNativeSurface = new ReliableSurface{std::move(surface)}; + mNativeSurface = std::make_unique<ReliableSurface>(std::move(surface)); + mNativeSurface->init(); if (enableTimeout) { // TODO: Fix error handling & re-shorten timeout - ANativeWindow_setDequeueTimeout(mNativeSurface.get(), 4000_ms); + ANativeWindow_setDequeueTimeout(mNativeSurface->getNativeWindow(), 4000_ms); } } else { mNativeSurface = nullptr; @@ -161,8 +162,9 @@ void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) { } ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::SRGB; - bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode, - mRenderAheadCapacity); + bool hasSurface = mRenderPipeline->setSurface( + mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior, colorMode, + mRenderAheadCapacity); mFrameNumber = -1; @@ -203,7 +205,8 @@ void CanvasContext::setStopped(bool stopped) { void CanvasContext::allocateBuffers() { if (mNativeSurface) { - mNativeSurface->allocateBuffers(); + ANativeWindow* anw = mNativeSurface->getNativeWindow(); + ANativeWindow_allocateBuffers(anw); } } @@ -428,7 +431,7 @@ void CanvasContext::setPresentTime() { presentTime = mCurrentFrameInfo->get(FrameInfoIndex::Vsync) + (frameIntervalNanos * (renderAhead + 1)); } - native_window_set_buffers_timestamp(mNativeSurface.get(), presentTime); + native_window_set_buffers_timestamp(mNativeSurface->getNativeWindow(), presentTime); } void CanvasContext::draw() { @@ -489,16 +492,18 @@ void CanvasContext::draw() { swap.swapCompletedTime = systemTime(SYSTEM_TIME_MONOTONIC); swap.vsyncTime = mRenderThread.timeLord().latestVsync(); if (didDraw) { - nsecs_t dequeueStart = ANativeWindow_getLastDequeueStartTime(mNativeSurface.get()); + nsecs_t dequeueStart = + ANativeWindow_getLastDequeueStartTime(mNativeSurface->getNativeWindow()); if (dequeueStart < mCurrentFrameInfo->get(FrameInfoIndex::SyncStart)) { // Ignoring dequeue duration as it happened prior to frame render start // and thus is not part of the frame. swap.dequeueDuration = 0; } else { swap.dequeueDuration = - ANativeWindow_getLastDequeueDuration(mNativeSurface.get()); + ANativeWindow_getLastDequeueDuration(mNativeSurface->getNativeWindow()); } - swap.queueDuration = ANativeWindow_getLastQueueDuration(mNativeSurface.get()); + swap.queueDuration = + ANativeWindow_getLastQueueDuration(mNativeSurface->getNativeWindow()); } else { swap.dequeueDuration = 0; swap.queueDuration = 0; @@ -567,14 +572,16 @@ void CanvasContext::doFrame() { } SkISize CanvasContext::getNextFrameSize() const { - ReliableSurface* surface = mNativeSurface.get(); - if (surface) { - SkISize size; - size.fWidth = ANativeWindow_getWidth(surface); - size.fHeight = ANativeWindow_getHeight(surface); - return size; + static constexpr SkISize defaultFrameSize = {INT32_MAX, INT32_MAX}; + if (mNativeSurface == nullptr) { + return defaultFrameSize; } - return {INT32_MAX, INT32_MAX}; + ANativeWindow* anw = mNativeSurface->getNativeWindow(); + + SkISize size; + size.fWidth = ANativeWindow_getWidth(anw); + size.fHeight = ANativeWindow_getHeight(anw); + return size; } void CanvasContext::prepareAndDraw(RenderNode* node) { @@ -702,11 +709,9 @@ bool CanvasContext::surfaceRequiresRedraw() { if (!mNativeSurface) return false; if (mHaveNewSurface) return true; - int width = -1; - int height = -1; - ReliableSurface* surface = mNativeSurface.get(); - surface->query(NATIVE_WINDOW_WIDTH, &width); - surface->query(NATIVE_WINDOW_HEIGHT, &height); + ANativeWindow* anw = mNativeSurface->getNativeWindow(); + const int width = ANativeWindow_getWidth(anw); + const int height = ANativeWindow_getHeight(anw); return width != mLastFrameWidth || height != mLastFrameHeight; } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 4490f80eb8af..0967b20e44ee 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -220,7 +220,7 @@ private: int32_t mLastFrameHeight = 0; RenderThread& mRenderThread; - sp<ReliableSurface> mNativeSurface; + std::unique_ptr<ReliableSurface> mNativeSurface; // stopped indicates the CanvasContext will reject actual redraw operations, // and defer repaint until it is un-stopped bool mStopped = false; diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp index 864780fb6ae8..e92500f5be51 100644 --- a/libs/hwui/renderthread/ReliableSurface.cpp +++ b/libs/hwui/renderthread/ReliableSurface.cpp @@ -26,64 +26,38 @@ namespace android::uirenderer::renderthread { // to propagate this error back to the caller constexpr bool DISABLE_BUFFER_PREFETCH = true; -// TODO: Make surface less protected -// This exists because perform is a varargs, and ANativeWindow has no va_list perform. -// So wrapping/chaining that is hard. Telling the compiler to ignore protected is easy, so we do -// that instead -struct SurfaceExposer : Surface { - // Make warnings happy - SurfaceExposer() = delete; - - using Surface::cancelBuffer; - using Surface::dequeueBuffer; - using Surface::lockBuffer_DEPRECATED; - using Surface::perform; - using Surface::queueBuffer; - using Surface::setBufferCount; - using Surface::setSwapInterval; -}; - -#define callProtected(surface, func, ...) ((*surface).*&SurfaceExposer::func)(__VA_ARGS__) - ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) { LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr"); - - ANativeWindow::setSwapInterval = hook_setSwapInterval; - ANativeWindow::dequeueBuffer = hook_dequeueBuffer; - ANativeWindow::cancelBuffer = hook_cancelBuffer; - ANativeWindow::queueBuffer = hook_queueBuffer; - ANativeWindow::query = hook_query; - ANativeWindow::perform = hook_perform; - - ANativeWindow::dequeueBuffer_DEPRECATED = hook_dequeueBuffer_DEPRECATED; - ANativeWindow::cancelBuffer_DEPRECATED = hook_cancelBuffer_DEPRECATED; - ANativeWindow::lockBuffer_DEPRECATED = hook_lockBuffer_DEPRECATED; - ANativeWindow::queueBuffer_DEPRECATED = hook_queueBuffer_DEPRECATED; } ReliableSurface::~ReliableSurface() { clearReservedBuffer(); + // Clear out the interceptors for proper hygiene. + // As a concrete example, if the underlying ANativeWindow is associated with + // an EGLSurface that is still in use, then if we don't clear out the + // interceptors then we walk into undefined behavior. + ANativeWindow_setCancelBufferInterceptor(mSurface.get(), nullptr, nullptr); + ANativeWindow_setDequeueBufferInterceptor(mSurface.get(), nullptr, nullptr); + ANativeWindow_setQueueBufferInterceptor(mSurface.get(), nullptr, nullptr); + ANativeWindow_setPerformInterceptor(mSurface.get(), nullptr, nullptr); } -void ReliableSurface::perform(int operation, va_list args) { - std::lock_guard _lock{mMutex}; +void ReliableSurface::init() { + int result = ANativeWindow_setCancelBufferInterceptor(mSurface.get(), hook_cancelBuffer, this); + LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set cancelBuffer interceptor: error = %d", + result); - switch (operation) { - case NATIVE_WINDOW_SET_USAGE: - mUsage = va_arg(args, uint32_t); - break; - case NATIVE_WINDOW_SET_USAGE64: - mUsage = va_arg(args, uint64_t); - break; - case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY: - /* width */ va_arg(args, uint32_t); - /* height */ va_arg(args, uint32_t); - mFormat = va_arg(args, PixelFormat); - break; - case NATIVE_WINDOW_SET_BUFFERS_FORMAT: - mFormat = va_arg(args, PixelFormat); - break; - } + result = ANativeWindow_setDequeueBufferInterceptor(mSurface.get(), hook_dequeueBuffer, this); + LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set dequeueBuffer interceptor: error = %d", + result); + + result = ANativeWindow_setQueueBufferInterceptor(mSurface.get(), hook_queueBuffer, this); + LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set queueBuffer interceptor: error = %d", + result); + + result = ANativeWindow_setPerformInterceptor(mSurface.get(), hook_perform, this); + LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set perform interceptor: error = %d", + result); } int ReliableSurface::reserveNext() { @@ -111,7 +85,9 @@ int ReliableSurface::reserveNext() { int fenceFd = -1; ANativeWindowBuffer* buffer = nullptr; - int result = callProtected(mSurface, dequeueBuffer, &buffer, &fenceFd); + + // Note that this calls back into our own hooked method. + int result = ANativeWindow_dequeueBuffer(mSurface.get(), &buffer, &fenceFd); { std::lock_guard _lock{mMutex}; @@ -138,59 +114,11 @@ void ReliableSurface::clearReservedBuffer() { mHasDequeuedBuffer = false; } if (buffer) { - callProtected(mSurface, cancelBuffer, buffer, releaseFd); - } -} - -int ReliableSurface::cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd) { - clearReservedBuffer(); - if (isFallbackBuffer(buffer)) { - if (fenceFd > 0) { - close(fenceFd); - } - return OK; - } - int result = callProtected(mSurface, cancelBuffer, buffer, fenceFd); - return result; -} - -int ReliableSurface::dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd) { - { - std::lock_guard _lock{mMutex}; - if (mReservedBuffer) { - *buffer = mReservedBuffer; - *fenceFd = mReservedFenceFd.release(); - mReservedBuffer = nullptr; - return OK; - } - } - - - int result = callProtected(mSurface, dequeueBuffer, buffer, fenceFd); - if (result != OK) { - ALOGW("dequeueBuffer failed, error = %d; switching to fallback", result); - *buffer = acquireFallbackBuffer(result); - *fenceFd = -1; - return *buffer ? OK : INVALID_OPERATION; - } else { - std::lock_guard _lock{mMutex}; - mHasDequeuedBuffer = true; - } - return OK; -} - -int ReliableSurface::queueBuffer(ANativeWindowBuffer* buffer, int fenceFd) { - clearReservedBuffer(); - - if (isFallbackBuffer(buffer)) { - if (fenceFd > 0) { - close(fenceFd); - } - return OK; + // Note that clearReservedBuffer may be reentrant here, so + // mReservedBuffer must be cleared once we reach here to avoid recursing + // forever. + ANativeWindow_cancelBuffer(mSurface.get(), buffer, releaseFd); } - - int result = callProtected(mSurface, queueBuffer, buffer, fenceFd); - return result; } bool ReliableSurface::isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const { @@ -229,82 +157,95 @@ ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer(int error) { return AHardwareBuffer_to_ANativeWindowBuffer(newBuffer); } -Surface* ReliableSurface::getWrapped(const ANativeWindow* window) { - return getSelf(window)->mSurface.get(); -} - -int ReliableSurface::hook_setSwapInterval(ANativeWindow* window, int interval) { - return callProtected(getWrapped(window), setSwapInterval, interval); -} - -int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, - int* fenceFd) { - return getSelf(window)->dequeueBuffer(buffer, fenceFd); -} - -int ReliableSurface::hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, - int fenceFd) { - return getSelf(window)->cancelBuffer(buffer, fenceFd); -} - -int ReliableSurface::hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, - int fenceFd) { - return getSelf(window)->queueBuffer(buffer, fenceFd); -} +int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window, + ANativeWindow_dequeueBufferFn dequeueBuffer, void* data, + ANativeWindowBuffer** buffer, int* fenceFd) { + ReliableSurface* rs = reinterpret_cast<ReliableSurface*>(data); + { + std::lock_guard _lock{rs->mMutex}; + if (rs->mReservedBuffer) { + *buffer = rs->mReservedBuffer; + *fenceFd = rs->mReservedFenceFd.release(); + rs->mReservedBuffer = nullptr; + return OK; + } + } -int ReliableSurface::hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer** buffer) { - ANativeWindowBuffer* buf; - int fenceFd = -1; - int result = window->dequeueBuffer(window, &buf, &fenceFd); + int result = dequeueBuffer(window, buffer, fenceFd); if (result != OK) { - return result; - } - sp<Fence> fence(new Fence(fenceFd)); - int waitResult = fence->waitForever("dequeueBuffer_DEPRECATED"); - if (waitResult != OK) { - ALOGE("dequeueBuffer_DEPRECATED: Fence::wait returned an error: %d", waitResult); - window->cancelBuffer(window, buf, -1); - return waitResult; + ALOGW("dequeueBuffer failed, error = %d; switching to fallback", result); + *buffer = rs->acquireFallbackBuffer(result); + *fenceFd = -1; + return *buffer ? OK : INVALID_OPERATION; + } else { + std::lock_guard _lock{rs->mMutex}; + rs->mHasDequeuedBuffer = true; } - *buffer = buf; - return result; + return OK; } -int ReliableSurface::hook_cancelBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer* buffer) { - return window->cancelBuffer(window, buffer, -1); +int ReliableSurface::hook_cancelBuffer(ANativeWindow* window, + ANativeWindow_cancelBufferFn cancelBuffer, void* data, + ANativeWindowBuffer* buffer, int fenceFd) { + ReliableSurface* rs = reinterpret_cast<ReliableSurface*>(data); + rs->clearReservedBuffer(); + if (rs->isFallbackBuffer(buffer)) { + if (fenceFd > 0) { + close(fenceFd); + } + return OK; + } + return cancelBuffer(window, buffer, fenceFd); } -int ReliableSurface::hook_lockBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer* buffer) { - // This method is a no-op in Surface as well - return OK; -} +int ReliableSurface::hook_queueBuffer(ANativeWindow* window, + ANativeWindow_queueBufferFn queueBuffer, void* data, + ANativeWindowBuffer* buffer, int fenceFd) { + ReliableSurface* rs = reinterpret_cast<ReliableSurface*>(data); + rs->clearReservedBuffer(); -int ReliableSurface::hook_queueBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer* buffer) { - return window->queueBuffer(window, buffer, -1); -} + if (rs->isFallbackBuffer(buffer)) { + if (fenceFd > 0) { + close(fenceFd); + } + return OK; + } -int ReliableSurface::hook_query(const ANativeWindow* window, int what, int* value) { - return getWrapped(window)->query(what, value); + return queueBuffer(window, buffer, fenceFd); } -int ReliableSurface::hook_perform(ANativeWindow* window, int operation, ...) { +int ReliableSurface::hook_perform(ANativeWindow* window, ANativeWindow_performFn perform, + void* data, int operation, va_list args) { // Drop the reserved buffer if there is one since this (probably) mutated buffer dimensions // TODO: Filter to things that only affect the reserved buffer // TODO: Can we mutate the reserved buffer in some cases? - getSelf(window)->clearReservedBuffer(); - va_list args; - va_start(args, operation); - int result = callProtected(getWrapped(window), perform, operation, args); - va_end(args); + ReliableSurface* rs = reinterpret_cast<ReliableSurface*>(data); + rs->clearReservedBuffer(); - va_start(args, operation); - getSelf(window)->perform(operation, args); - va_end(args); + va_list argsCopy; + va_copy(argsCopy, args); + int result = perform(window, operation, argsCopy); + { + std::lock_guard _lock{rs->mMutex}; + + switch (operation) { + case ANATIVEWINDOW_PERFORM_SET_USAGE: + rs->mUsage = va_arg(args, uint32_t); + break; + case ANATIVEWINDOW_PERFORM_SET_USAGE64: + rs->mUsage = va_arg(args, uint64_t); + break; + case ANATIVEWINDOW_PERFORM_SET_BUFFERS_GEOMETRY: + /* width */ va_arg(args, uint32_t); + /* height */ va_arg(args, uint32_t); + rs->mFormat = va_arg(args, PixelFormat); + break; + case ANATIVEWINDOW_PERFORM_SET_BUFFERS_FORMAT: + rs->mFormat = va_arg(args, PixelFormat); + break; + } + } return result; } diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h index f768df37ba7d..32472539f616 100644 --- a/libs/hwui/renderthread/ReliableSurface.h +++ b/libs/hwui/renderthread/ReliableSurface.h @@ -16,6 +16,7 @@ #pragma once +#include <apex/window.h> #include <gui/Surface.h> #include <utils/Macros.h> #include <utils/StrongPointer.h> @@ -24,16 +25,21 @@ namespace android::uirenderer::renderthread { -class ReliableSurface : public ANativeObjectBase<ANativeWindow, ReliableSurface, RefBase> { +class ReliableSurface { PREVENT_COPY_AND_ASSIGN(ReliableSurface); public: ReliableSurface(sp<Surface>&& surface); ~ReliableSurface(); - int reserveNext(); + // Performs initialization that is not safe to do in the constructor. + // For instance, registering ANativeWindow interceptors with ReliableSurface + // passed as the data pointer is not safe. + void init(); + + ANativeWindow* getNativeWindow() { return mSurface.get(); } - void allocateBuffers() { mSurface->allocateBuffers(); } + int reserveNext(); int query(int what, int* value) const { return mSurface->query(what, value); } @@ -61,7 +67,7 @@ public: } private: - const sp<Surface> mSurface; + sp<Surface> mSurface; mutable std::mutex mMutex; @@ -78,27 +84,20 @@ private: ANativeWindowBuffer* acquireFallbackBuffer(int error); void clearReservedBuffer(); - void perform(int operation, va_list args); - int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd); - int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd); - int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd); - - static Surface* getWrapped(const ANativeWindow*); - - // ANativeWindow hooks - static int hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); - static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, - int* fenceFd); - static int hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); - - static int hook_perform(ANativeWindow* window, int operation, ...); - static int hook_query(const ANativeWindow* window, int what, int* value); - static int hook_setSwapInterval(ANativeWindow* window, int interval); - - static int hook_cancelBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); - static int hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer** buffer); - static int hook_lockBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); - static int hook_queueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); + // ANativeWindow hooks. When an ANativeWindow_* method is called on the + // underlying ANativeWindow, these methods will intercept the original call. + // For example, an EGL driver would call into these hooks instead of the + // original methods. + static int hook_cancelBuffer(ANativeWindow* window, ANativeWindow_cancelBufferFn cancelBuffer, + void* data, ANativeWindowBuffer* buffer, int fenceFd); + static int hook_dequeueBuffer(ANativeWindow* window, + ANativeWindow_dequeueBufferFn dequeueBuffer, void* data, + ANativeWindowBuffer** buffer, int* fenceFd); + static int hook_queueBuffer(ANativeWindow* window, ANativeWindow_queueBufferFn queueBuffer, + void* data, ANativeWindowBuffer* buffer, int fenceFd); + + static int hook_perform(ANativeWindow* window, ANativeWindow_performFn perform, void* data, + int operation, va_list args); }; }; // namespace android::uirenderer::renderthread diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index 7a12cee899d8..eb76c29301c6 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -16,7 +16,6 @@ package android.location; -import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; @@ -1214,23 +1213,6 @@ public class Location implements Parcelable { } /** - * Attaches an extra {@link Location} to this Location. This is useful for location providers - * to set the {@link #EXTRA_NO_GPS_LOCATION} extra to provide coarse locations for clients. - * - * @param key the key associated with the Location extra - * @param value the Location to attach - * @hide - */ - @TestApi - @SystemApi - public void setExtraLocation(@Nullable String key, @Nullable Location value) { - if (mExtras == null) { - mExtras = new Bundle(); - } - mExtras.putParcelable(key, value); - } - - /** * Returns true if the Location came from a mock provider. * * @return true if this Location came from a mock provider, false otherwise diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index dd01243629c3..0e88c75e6d25 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -21,6 +21,7 @@ import static android.media.MediaRouter2Utils.toUniqueId; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; @@ -197,6 +198,7 @@ public final class MediaRoute2Info implements Parcelable { final List<String> mFeatures; @DeviceType final int mDeviceType; + final boolean mIsSystem; final Uri mIconUri; final CharSequence mDescription; @ConnectionState @@ -213,6 +215,7 @@ public final class MediaRoute2Info implements Parcelable { mName = builder.mName; mFeatures = builder.mFeatures; mDeviceType = builder.mDeviceType; + mIsSystem = builder.mIsSystem; mIconUri = builder.mIconUri; mDescription = builder.mDescription; mConnectionState = builder.mConnectionState; @@ -229,6 +232,7 @@ public final class MediaRoute2Info implements Parcelable { mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mFeatures = in.createStringArrayList(); mDeviceType = in.readInt(); + mIsSystem = in.readBoolean(); mIconUri = in.readParcelable(null); mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mConnectionState = in.readInt(); @@ -287,6 +291,17 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Returns whether the route is a system route or not. + * <p> + * System routes are media routes directly controlled by the system + * such as phone speaker, wired headset, and Bluetooth devices. + * </p> + */ + public boolean isSystemRoute() { + return mIsSystem; + } + + /** * Gets the URI of the icon representing this route. * <p> * This icon will be used in picker UIs if available. @@ -360,6 +375,7 @@ public final class MediaRoute2Info implements Parcelable { * @hide */ @NonNull + @TestApi public String getOriginalId() { return mId; } @@ -423,6 +439,7 @@ public final class MediaRoute2Info implements Parcelable { && Objects.equals(mName, other.mName) && Objects.equals(mFeatures, other.mFeatures) && (mDeviceType == other.mDeviceType) + && (mIsSystem == other.mIsSystem) && Objects.equals(mIconUri, other.mIconUri) && Objects.equals(mDescription, other.mDescription) && (mConnectionState == other.mConnectionState) @@ -436,7 +453,7 @@ public final class MediaRoute2Info implements Parcelable { @Override public int hashCode() { // Note: mExtras is not included. - return Objects.hash(mId, mName, mFeatures, mDeviceType, mIconUri, mDescription, + return Objects.hash(mId, mName, mFeatures, mDeviceType, mIsSystem, mIconUri, mDescription, mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume, mProviderId); } @@ -473,6 +490,7 @@ public final class MediaRoute2Info implements Parcelable { TextUtils.writeToParcel(mName, dest, flags); dest.writeStringList(mFeatures); dest.writeInt(mDeviceType); + dest.writeBoolean(mIsSystem); dest.writeParcelable(mIconUri, flags); TextUtils.writeToParcel(mDescription, dest, flags); dest.writeInt(mConnectionState); @@ -494,6 +512,7 @@ public final class MediaRoute2Info implements Parcelable { @DeviceType int mDeviceType = DEVICE_TYPE_UNKNOWN; + boolean mIsSystem; Uri mIconUri; CharSequence mDescription; @ConnectionState @@ -540,6 +559,7 @@ public final class MediaRoute2Info implements Parcelable { mName = routeInfo.mName; mFeatures = new ArrayList<>(routeInfo.mFeatures); mDeviceType = routeInfo.mDeviceType; + mIsSystem = routeInfo.mIsSystem; mIconUri = routeInfo.mIconUri; mDescription = routeInfo.mDescription; mConnectionState = routeInfo.mConnectionState; @@ -608,6 +628,16 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Sets whether the route is a system route or not. + * @hide + */ + @NonNull + public Builder setSystemRoute(boolean isSystem) { + mIsSystem = isSystem; + return this; + } + + /** * Sets the URI of the icon representing this route. * <p> * This icon will be used in picker UIs if available. diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java index c9a2ec7ed095..afe002edfbcd 100644 --- a/media/java/android/media/MediaRoute2ProviderInfo.java +++ b/media/java/android/media/MediaRoute2ProviderInfo.java @@ -180,6 +180,23 @@ public final class MediaRoute2ProviderInfo implements Parcelable { } /** + * Sets whether the provider provides system routes or not + */ + @NonNull + public Builder setSystemRouteProvider(boolean isSystem) { + int count = mRoutes.size(); + for (int i = 0; i < count; i++) { + MediaRoute2Info route = mRoutes.valueAt(i); + if (route.isSystemRoute() != isSystem) { + mRoutes.setValueAt(i, new MediaRoute2Info.Builder(route) + .setSystemRoute(isSystem) + .build()); + } + } + return this; + } + + /** * Adds a route to the provider */ @NonNull diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 7b9a44f71473..f751a22c4900 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -230,7 +230,11 @@ public class MediaRouter2 { /** * Gets the unmodifiable list of {@link MediaRoute2Info routes} currently * known to the media router. + * <p> + * {@link MediaRoute2Info#isSystemRoute() System routes} such as phone speaker, + * Bluetooth devices are always included in the list. * Please note that the list can be changed before callbacks are invoked. + * </p> * * @return the list of routes that contains at least one of the route features in discovery * preferences registered by the application @@ -243,7 +247,8 @@ public class MediaRouter2 { List<MediaRoute2Info> filteredRoutes = new ArrayList<>(); for (MediaRoute2Info route : mRoutes.values()) { - if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { + if (route.isSystemRoute() + || route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { filteredRoutes.add(route); } } @@ -307,12 +312,18 @@ public class MediaRouter2 { * with the given route. * * @param route the route you want to create a controller with. + * @throws IllegalArgumentException if the given route is + * {@link MediaRoute2Info#isSystemRoute() system route} * * @see RoutingControllerCallback#onControllerCreated * @see RoutingControllerCallback#onControllerCreationFailed */ public void requestCreateController(@NonNull MediaRoute2Info route) { Objects.requireNonNull(route, "route must not be null"); + if (route.isSystemRoute()) { + throw new IllegalArgumentException("Can't create a route controller with " + + "a system route. Use getSystemController()."); + } // TODO: Check the given route exists final int requestId; diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 61e2f77b7d2e..662eeb1418f1 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -161,14 +161,15 @@ public class MediaRouter2Manager { public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) { Objects.requireNonNull(packageName, "packageName must not be null"); + List<MediaRoute2Info> routes = new ArrayList<>(); + List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName); if (preferredFeatures == null) { - return Collections.emptyList(); + preferredFeatures = Collections.emptyList(); } - List<MediaRoute2Info> routes = new ArrayList<>(); synchronized (mRoutesLock) { for (MediaRoute2Info route : mRoutes.values()) { - if (route.hasAnyFeatures(preferredFeatures)) { + if (route.isSystemRoute() || route.hasAnyFeatures(preferredFeatures)) { routes.add(route); } } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index 4a2044af0431..16259ab45792 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -196,7 +196,14 @@ public class MediaRouterManagerTest { public void testRouteFeatures() throws Exception { Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL); - assertEquals(1, routes.size()); + int routeCount = 0; + for (MediaRoute2Info route : routes.values()) { + if (!route.isSystemRoute()) { + routeCount++; + } + } + + assertEquals(1, routeCount); assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE)); } diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 97b861b390ad..a8f1d2c7bbba 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -23,6 +23,8 @@ LIBANDROID { AChoreographer_postFrameCallbackDelayed; # introduced=24 AChoreographer_postFrameCallback64; # introduced=29 AChoreographer_postFrameCallbackDelayed64; # introduced=29 + AChoreographer_registerRefreshRateCallback; # introduced=30 + AChoreographer_unregisterRefreshRateCallback; # introduced=30 AConfiguration_copy; AConfiguration_delete; AConfiguration_diff; diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index e2297e44fdfe..d2f514c6c0ca 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -29,6 +29,9 @@ <bool name="config_enableRightNavigationBar">false</bool> <bool name="config_enableBottomNavigationBar">true</bool> + <!-- Disable normal notification rendering; we handle that ourselves --> + <bool name="config_renderNotifications">false</bool> + <!-- Whether heads-up notifications should be shown when shade is open. --> <bool name="config_enableHeadsUpNotificationWhenNotificationShadeOpen">true</bool> diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index cf4ee7d97409..585acfec410a 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -22,7 +22,6 @@ import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME; import android.content.Context; import com.android.systemui.car.CarDeviceProvisionedControllerImpl; -import com.android.systemui.car.CarNotificationEntryManager; import com.android.systemui.car.CarNotificationInterruptionStateProvider; import com.android.systemui.dagger.SystemUIRootComponent; import com.android.systemui.dock.DockManager; @@ -73,13 +72,6 @@ abstract class CarSystemUIModule { return false; } - /** - * Use {@link CarNotificationEntryManager}, which does nothing when adding a notification. - */ - @Binds - abstract NotificationEntryManager bindNotificationEntryManager( - CarNotificationEntryManager notificationEntryManager); - @Singleton @Provides static HeadsUpManagerPhone provideHeadsUpManagerPhone(Context context, diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java deleted file mode 100644 index cfe1c702663e..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.car; - -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; - -import com.android.systemui.statusbar.FeatureFlags; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; -import com.android.systemui.statusbar.notification.logging.NotifLog; -import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.util.leak.LeakDetector; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import dagger.Lazy; - -/** - * Car specific notification entry manager that does nothing when adding a notification. - * - * <p> This is because system UI notifications are disabled and we have a different implementation. - * Please see {@link com.android.car.notification}. - */ -@Singleton -public class CarNotificationEntryManager extends NotificationEntryManager { - - @Inject - public CarNotificationEntryManager( - NotifLog notifLog, - NotificationGroupManager groupManager, - NotificationRankingManager rankingManager, - KeyguardEnvironment keyguardEnvironment, - FeatureFlags featureFlags, - Lazy<NotificationRowBinder> notificationRowBinderLazy, - Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy, - LeakDetector leakDetector) { - super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags, - notificationRowBinderLazy, notificationRemoteInputManagerLazy, leakDetector); - } - - @Override - public void addNotification( - StatusBarNotification notification, NotificationListenerService.RankingMap ranking) { - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 76e9ec64e2f2..210dd321933a 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -87,10 +87,8 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -103,12 +101,10 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; @@ -124,7 +120,6 @@ import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LightsOutNotifController; import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ScrimController; @@ -141,7 +136,6 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.volume.VolumeComponent; @@ -257,7 +251,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt public CarStatusBar( Context context, - FeatureFlags featureFlags, + NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -269,13 +263,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NotifPipelineInitializer> notifPipelineInitializer, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationEntryManager notificationEntryManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -296,12 +288,10 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt VibratorHelper vibratorHelper, BubbleController bubbleController, NotificationGroupManager groupManager, - NotificationGroupAlertTransferHelper groupAlertTransferHelper, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, Lazy<AssistManager> assistManagerLazy, - NotificationListener notificationListener, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, LockscreenLockIconController lockscreenLockIconController, @@ -318,7 +308,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt Optional<Recents> recents, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, - RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, SuperStatusBarViewFactory superStatusBarViewFactory, LightsOutNotifController lightsOutNotifController, @@ -334,7 +323,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry, /* Car Settings injected components. */ CarServiceProvider carServiceProvider, @@ -345,7 +333,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt FlingAnimationUtils.Builder flingAnimationUtilsBuilder) { super( context, - featureFlags, + notificationsController, lightBarController, autoHideController, keyguardUpdateMonitor, @@ -357,13 +345,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - notifPipelineInitializer, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, notificationGutsManager, notificationLogger, - notificationEntryManager, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, @@ -384,12 +370,10 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt vibratorHelper, bubbleController, groupManager, - groupAlertTransferHelper, visualStabilityManager, deviceProvisionedController, navigationBarController, assistManagerLazy, - notificationListener, configurationController, notificationShadeWindowController, lockscreenLockIconController, @@ -407,7 +391,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt recents, statusBarComponentBuilder, pluginManager, - remoteInputUriController, dividerOptional, lightsOutNotifController, statusBarNotificationActivityStarterBuilder, @@ -422,7 +405,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt keyguardDismissUtil, extensionController, userInfoControllerImpl, - notificationRowBinder, dismissCallbackRegistry); mUserSwitcherController = userSwitcherController; mScrimController = scrimController; diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index 45da8223943b..498bd8780f29 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -47,10 +47,8 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -63,12 +61,10 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; @@ -83,7 +79,6 @@ import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LightsOutNotifController; import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ScrimController; @@ -99,7 +94,6 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.volume.VolumeComponent; @@ -127,7 +121,7 @@ public class CarStatusBarModule { @Singleton static CarStatusBar provideStatusBar( Context context, - FeatureFlags featureFlags, + NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -139,13 +133,11 @@ public class CarStatusBarModule { HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NotifPipelineInitializer> notifPipelineInitializer, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationEntryManager notificationEntryManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -166,12 +158,10 @@ public class CarStatusBarModule { VibratorHelper vibratorHelper, BubbleController bubbleController, NotificationGroupManager groupManager, - NotificationGroupAlertTransferHelper groupAlertTransferHelper, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, Lazy<AssistManager> assistManagerLazy, - NotificationListener notificationListener, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, LockscreenLockIconController lockscreenLockIconController, @@ -188,7 +178,6 @@ public class CarStatusBarModule { Optional<Recents> recentsOptional, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, - RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, SuperStatusBarViewFactory superStatusBarViewFactory, LightsOutNotifController lightsOutNotifController, @@ -204,7 +193,6 @@ public class CarStatusBarModule { KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry, CarServiceProvider carServiceProvider, Lazy<PowerManagerHelper> powerManagerHelperLazy, @@ -214,7 +202,7 @@ public class CarStatusBarModule { FlingAnimationUtils.Builder flingAnimationUtilsBuilder) { return new CarStatusBar( context, - featureFlags, + notificationsController, lightBarController, autoHideController, keyguardUpdateMonitor, @@ -226,13 +214,11 @@ public class CarStatusBarModule { headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - notifPipelineInitializer, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, notificationGutsManager, notificationLogger, - notificationEntryManager, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, @@ -253,12 +239,10 @@ public class CarStatusBarModule { vibratorHelper, bubbleController, groupManager, - groupAlertTransferHelper, visualStabilityManager, deviceProvisionedController, navigationBarController, assistManagerLazy, - notificationListener, configurationController, notificationShadeWindowController, lockscreenLockIconController, @@ -275,7 +259,6 @@ public class CarStatusBarModule { recentsOptional, statusBarComponentBuilder, pluginManager, - remoteInputUriController, dividerOptional, superStatusBarViewFactory, lightsOutNotifController, @@ -290,7 +273,6 @@ public class CarStatusBarModule { keyguardDismissUtil, extensionController, userInfoControllerImpl, - notificationRowBinder, dismissCallbackRegistry, carServiceProvider, powerManagerHelperLazy, diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index ec445d4dcbee..38cf5ab9afe6 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -318,6 +318,16 @@ public class ExternalStorageProvider extends FileSystemProvider { return false; } + // Allow all directories on USB, including the root. + try { + RootInfo rootInfo = getRootFromDocId(docId); + if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { + return false; + } + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to determine rootInfo for docId"); + } + final String path = getPathFromDocId(docId); // Block the root of the storage diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 2b143e425589..b31841d82d0e 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Gekoppel via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Beskikbaar via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te meld"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Gekoppel, geen internet nie"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Daar kan nie by private DNS-bediener ingegaan word nie"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet nie"</string> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 2a93e01765bb..4955ad8fda29 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"በ <xliff:g id="NAME">%1$s</xliff:g> በኩል ተገናኝተዋል"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"በ%1$s በኩል የሚገኝ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ለመመዝገብ መታ ያድርጉ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ተገናኝቷል፣ ምንም በይነመረብ የለም"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"የግል ዲኤንኤስ አገልጋይ ሊደረስበት አይችልም"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"የተገደበ ግንኙነት"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ምንም በይነመረብ የለም"</string> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 5103345401f4..7b26be4f4c14 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"تم الاتصال عبر <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="available_via_passpoint" msgid="1716000261192603682">"متوفرة عبر %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"انقر للاشتراك."</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"متصلة ولكن بلا إنترنت"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"لا يمكن الوصول إلى خادم أسماء نظام نطاقات خاص"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"لا يتوفر اتصال إنترنت."</string> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index fa26b9b93ef6..dcebe5bd9b6c 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>ৰ জৰিয়তে সংযুক্ত"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$sৰ মাধ্যমেৰে উপলব্ধ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ছাইন আপ কৰিবলৈ টিপক"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"সংযোজিত, ইণ্টাৰনেট নাই"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত DNS ছাৰ্ভাৰ এক্সেছ কৰিব নোৱাৰি"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ইণ্টাৰনেট সংযোগ সীমিত"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইণ্টাৰনেট সংযোগ নাই"</string> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 2e80fdcb8972..7f3db37f2a25 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ilə qoşulub"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s vasitəsilə əlçatandır"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Qeydiyyatdan keçmək üçün klikləyin"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Qoşuludur, internet yoxdur"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Özəl DNS serverinə giriş mümkün deyil"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Məhdud bağlantı"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yoxdur"</string> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index ae5c93670644..b789cb0dbe5a 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupna je preko pristupne tačke %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrovali"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Veza je uspostavljena, nema interneta"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Pristup privatnom DNS serveru nije uspeo"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index ad201d081fce..ce191eb59676 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Падключана праз праграму \"<xliff:g id="NAME">%1$s</xliff:g>\""</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Даступна праз %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Націсніце, каб зарэгістравацца"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Падключана, без доступу да інтэрнэту"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Не ўдалося атрымаць доступ да прыватнага DNS-сервера"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Абмежаваныя магчымасці падключэння"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Не падключана да інтэрнэту"</string> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 94f78ad30a67..3e6f77db29c1 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Установена е връзка през <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Мрежата е достъпна през „%1$s“"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Докоснете, за да се регистрирате"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Установена е връзка – няма достъп до интернет"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се осъществи достъп до частния DNS сървър"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена връзка"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Няма връзка с интернет"</string> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 517191601d2f..7f6938ac8de2 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-এর মাধ্যমে কানেক্ট করা আছে"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s এর মাধ্যমে উপলব্ধ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"সাইন-আপ করতে ট্যাপ করুন"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"কানেক্ট, ইন্টারনেট নেই"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত ডিএনএস সার্ভার অ্যাক্সেস করা যাবে না"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"সীমিত কানেকশন"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইন্টারনেট কানেকশন নেই"</string> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index f74bcee6fd08..0563abd66311 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupan preko %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite za prijavu"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Povezano, nema interneta"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS serveru"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema internetske veze"</string> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 1b23ecfd904b..71b2c5d85f84 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connectat mitjançant <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible mitjançant %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca per registrar-te"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connectada, sense Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"No es pot accedir al servidor DNS privat"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexió limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sense connexió a Internet"</string> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index 22603ccdd804..a381772970aa 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Připojeno přes <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupné prostřednictvím %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Klepnutím se zaregistrujete"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Připojeno, není k dispozici internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Nelze získat přístup k soukromému serveru DNS"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Omezené připojení"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nejste připojeni k internetu"</string> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index bef1855570a1..f6e8576117d9 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Forbundet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgængelig via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryk for at registrere dig"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tilsluttet – intet internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Der er ikke adgang til den private DNS-server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrænset forbindelse"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Intet internet"</string> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index c8c97bdf3d42..99a091093463 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Verbunden über <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Verfügbar über %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Zum Anmelden tippen"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Verbunden, kein Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Auf den privaten DNS-Server kann nicht zugegriffen werden"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Eingeschränkte Verbindung"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Kein Internet"</string> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 371075c7acc0..d37ea4409124 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Συνδέθηκε μέσω <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Διαθέσιμο μέσω %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Πατήστε για εγγραφή"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Συνδέθηκε, χωρίς σύνδεση στο διαδίκτυο"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Δεν είναι δυνατή η πρόσβαση στον ιδιωτικό διακομιστή DNS."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Περιορισμένη σύνδεση"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Δεν υπάρχει σύνδεση στο διαδίκτυο"</string> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index b314d1782585..429cd3edd60b 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index b314d1782585..429cd3edd60b 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index b314d1782585..429cd3edd60b 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index b314d1782585..429cd3edd60b 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml index 95944dc9eae1..1aa6cdba32fb 100644 --- a/packages/SettingsLib/res/values-en-rXC/strings.xml +++ b/packages/SettingsLib/res/values-en-rXC/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No internet"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index c6dfdd3e3ac2..7d28a3255a7b 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conexión a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Presiona para registrarte"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectado pero sin conexión a Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"No se puede acceder al servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index ba4a9ff9b245..b35696f54cdc 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para registrarte"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conexión sin Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"No se ha podido acceder al servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index df3b792b0e05..b6c112b94da7 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ühendatud võrgu <xliff:g id="NAME">%1$s</xliff:g> kaudu"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Saadaval üksuse %1$s kaudu"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Puudutage registreerumiseks"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ühendatud, Interneti-ühendus puudub"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Privaatsele DNS-serverile ei pääse juurde"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Piiratud ühendus"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Interneti-ühendus puudub"</string> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index fcb320ffe61f..4fd9add70470 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> bidez konektatuta"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s bidez erabilgarri"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Sakatu erregistratzeko"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Konektatuta; ezin da atzitu Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Ezin da atzitu DNS zerbitzari pribatua"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Konexio mugatua"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ez dago Interneteko konexiorik"</string> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 261a438a4774..32a98be394cb 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"متصل شده ازطریق <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"در دسترس از طریق %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"برای ثبتنام ضربه بزنید"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"متصل، بدون اینترنت"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"سرور DNS خصوصی قابل دسترسی نیست"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"عدم دسترسی به اینترنت"</string> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 4ccf430131af..84c53723d6c6 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Yhdistetty (<xliff:g id="NAME">%1$s</xliff:g>)"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Käytettävissä seuraavan kautta: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Rekisteröidy napauttamalla"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Yhdistetty, ei internetyhteyttä"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Ei pääsyä yksityiselle DNS-palvelimelle"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Rajallinen yhteys"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ei internetyhteyttä"</string> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 84a97973d7c4..995eab641719 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connecté sur le réseau <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Accessible par %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toucher pour vous connecter"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connecté, aucun accès à Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucune connexion Internet"</string> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 030a7f9b3b48..2afacab43f8d 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connecté via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Appuyez ici pour vous connecter"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connecté, aucun accès à Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucun accès à Internet"</string> @@ -57,7 +58,7 @@ <string name="osu_sign_up_complete" msgid="7640183358878916847">"Inscription terminée. Connexion…"</string> <string name="speed_label_very_slow" msgid="8526005255731597666">"Très lente"</string> <string name="speed_label_slow" msgid="6069917670665664161">"Lente"</string> - <string name="speed_label_okay" msgid="1253594383880810424">"Correct"</string> + <string name="speed_label_okay" msgid="1253594383880810424">"Correcte"</string> <string name="speed_label_medium" msgid="9078405312828606976">"Moyenne"</string> <string name="speed_label_fast" msgid="2677719134596044051">"Élevée"</string> <string name="speed_label_very_fast" msgid="8215718029533182439">"Très rapide"</string> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index af033cf43328..a106e60203e3 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Wifi conectada a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dispoñible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para rexistrarte"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conexión sen Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Non se puido acceder ao servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Pouca conexión"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Non hai conexión a Internet"</string> @@ -316,7 +317,7 @@ <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Desactivar encamiñamento audio USB"</string> <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Desactiva o encamiñamento automático a periféricos de audio USB"</string> <string name="debug_layout" msgid="1659216803043339741">"Mostrar límites de deseño"</string> - <string name="debug_layout_summary" msgid="8825829038287321978">"Mostra os límites dos clips, as marxes, etc."</string> + <string name="debug_layout_summary" msgid="8825829038287321978">"Mostra os límites dos clips, as marxes etc."</string> <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"Forzar dirección do deseño RTL"</string> <string name="force_rtl_layout_all_locales_summary" msgid="6663016859517239880">"Forza a dirección de pantalla a RTL (dereita a esquerda) para todas as configuración rexionais"</string> <string name="force_msaa" msgid="4081288296137775550">"Forzar MSAA 4x"</string> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 87fd8767b326..5f2d5cde3198 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> દ્વારા કનેક્ટ થયેલ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s દ્વારા ઉપલબ્ધ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"સાઇન અપ કરવા માટે ટૅપ કરો"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"કનેક્ટ કર્યું, કોઈ ઇન્ટરનેટ નથી"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"ખાનગી DNS સર્વર ઍક્સેસ કરી શકાતા નથી"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"મર્યાદિત કનેક્શન"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ઇન્ટરનેટ ઍક્સેસ નથી"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 02b5c9601ab4..d60eeadac7a6 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> के ज़रिए कनेक्ट किया गया"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s के द्वारा उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करने के लिए टैप करें"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"कनेक्ट हो गया है, लेकिन इंटरनेट नहीं है"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"निजी डीएनएस सर्वर को ऐक्सेस नहीं किया जा सकता"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित कनेक्शन"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट कनेक्शन नहीं है"</string> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 598cfe2aadb2..28e34601444c 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezan putem mreže <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupno putem %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrirali"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Povezano, bez interneta"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS poslužitelju"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index d970c737c3d6..22e03a779545 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Kapcsolódva a következőn keresztül: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Elérhető a következőn keresztül: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Koppintson a regisztrációhoz"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Csatlakozva, nincs internet-hozzáférés"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"A privát DNS-kiszolgálóhoz nem lehet hozzáférni"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Korlátozott kapcsolat"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nincs internetkapcsolat"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 908693439de0..ecb615a40cc2 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Միացված է <xliff:g id="NAME">%1$s</xliff:g>-ի միջոցով"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Հասանելի է %1$s-ի միջոցով"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Հպեք՝ գրանցվելու համար"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Միացված է, սակայն ինտերնետ կապ չկա"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Մասնավոր DNS սերվերն անհասանելի է"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Սահմանափակ կապ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ինտերնետ կապ չկա"</string> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 3c1504c7f14a..cfbda04f176d 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tersambung melalui <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketuk untuk mendaftar"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tersambung, tidak ada internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Server DNS pribadi tidak dapat diakses"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Koneksi terbatas"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tidak ada internet"</string> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index 438e900be4ba..ba4c00976ccb 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tenging í gegnum <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Í boði í gegnum %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ýttu til að skrá þig"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tengt, enginn netaðgangur"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Ekki næst í DNS-einkaþjón"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Takmörkuð tenging"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Engin nettenging"</string> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 114b33b49206..50e5777ed9d9 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connesso tramite <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibile tramite %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tocca per registrarti"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connesso, senza Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Non è possibile accedere al server DNS privato"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connessione limitata"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nessuna connessione a Internet"</string> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 62085a85e243..9f1e457383e1 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"מחוברת באמצעות <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"זמינה דרך %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"יש להקיש כדי להירשם"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"מחובר. אין אינטרנט"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"לא ניתן לגשת לשרת DNS הפרטי"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"חיבור מוגבל"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"אין אינטרנט"</string> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index 28b98ee7c387..47020ed45794 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> で接続しました"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s経由で使用可能"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"タップして登録してください"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"接続済み、インターネット接続なし"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"プライベート DNS サーバーにアクセスできません"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"接続が制限されています"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"インターネット未接続"</string> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index 6f5d0b39fc1f..116488d1c151 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"დაკავშირებულია <xliff:g id="NAME">%1$s</xliff:g>-ით"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"ხელმისაწვდომია %1$s-ით"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"შეეხეთ რეგისტრაციისთვის"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"დაკავშირებულია, ინტერნეტის გარეშე"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"პირად DNS სერვერზე წვდომა შეუძლებელია"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"შეზღუდული კავშირი"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ინტერნეტ-კავშირი არ არის"</string> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index 3fe426e6d027..3c4fdb739ef5 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> арқылы жалғанған"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s арқылы қолжетімді"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Тіркелу үшін түртіңіз."</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Қосылған, интернет жоқ"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS серверіне кіру мүмкін емес."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Шектеулі байланыс"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернетпен байланыс жоқ"</string> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index 24bfa35d2368..87a4f19f3d67 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"ភ្ជាប់តាម <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"មានតាមរយៈ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ចុចដើម្បីចុះឈ្មោះ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"បានភ្ជាប់ ប៉ុន្តែគ្មានអ៊ីនធឺណិតទេ"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"មិនអាចចូលប្រើម៉ាស៊ីនមេ DNS ឯកជនបានទេ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ការតភ្ជាប់មានកម្រិត"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"គ្មានអ៊ីនធឺណិតទេ"</string> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index 0699bbcd007a..144dddb1f7ff 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ಆ್ಯಪ್ ಮೂಲಕ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ಮೂಲಕ ಲಭ್ಯವಿದೆ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ಸೈನ್ ಅಪ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ, ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"ಖಾಸಗಿ DNS ಸರ್ವರ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ಸೀಮಿತ ಸಂಪರ್ಕ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 25e9cfe8fa1a..df9f21d800ef 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>을(를) 통해 연결됨"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s을(를) 통해 사용 가능"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"탭하여 가입"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"연결됨, 인터넷 사용 불가"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"비공개 DNS 서버에 액세스할 수 없습니다."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"제한된 연결"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"인터넷 연결 없음"</string> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 3dfce1eb2a8a..13c414404da2 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> аркылуу туташты"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s аркылуу жеткиликтүү"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Катталуу үчүн таптап коюңуз"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Туташып турат, Интернет жок"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS сервери жеткиликсиз"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Байланыш чектелген"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернет жок"</string> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index 48e50933c0ce..48224f4c2f81 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"ເຊື່ອມຕໍ່ຜ່ານ <xliff:g id="NAME">%1$s</xliff:g> ແລ້ວ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"ມີໃຫ້ຜ່ານ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ແຕະເພື່ອສະໝັກ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ເຊື່ອມຕໍ່ແລ້ວ, ບໍ່ມີອິນເຕີເນັດ"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີ DNS ສ່ວນຕົວໄດ້"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ການເຊື່ອມຕໍ່ຈຳກັດ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ບໍ່ມີອິນເຕີເນັດ"</string> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index 8b3fbadbfb39..d080334062ad 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Prisijungta naudojant programą „<xliff:g id="NAME">%1$s</xliff:g>“"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Pasiekiama naudojant „%1$s“"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Palieskite, kad prisiregistruotumėte"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Prisijungta, nėra interneto"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Privataus DNS serverio negalima pasiekti"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ribotas ryšys"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nėra interneto ryšio"</string> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index 4fc5b223c384..e13a50d990a7 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Savienojums ar <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Pieejams, izmantojot %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Pieskarieties, lai reģistrētos"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Savienojums izveidots, nav piekļuves internetam"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Nevar piekļūt privātam DNS serverim."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ierobežots savienojums"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nav piekļuves internetam"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index 288e526be4c9..d027ffe4f44f 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Поврзано преку <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Достапно преку %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Допрете за да се регистрирате"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Поврзана, нема интернет"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се пристапи до приватниот DNS-сервер"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена врска"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернет"</string> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index e775297982fd..e2a3d9fbd4ba 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> മുഖേന കണക്റ്റ് ചെയ്തു"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s വഴി ലഭ്യം"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"സൈൻ അപ്പ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"കണക്റ്റ് ചെയ്തു, ഇന്റർനെറ്റ് ഇല്ല"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"സ്വകാര്യ DNS സെർവർ ആക്സസ് ചെയ്യാനാവില്ല"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"പരിമിത കണക്ഷൻ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ഇന്റർനെറ്റ് ഇല്ല"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index ba69f9b654ab..dd08c9e9fdfc 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-р холбогдсон"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s-р боломжтой"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Бүртгүүлэхийн тулд товшино уу"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Холбогдсон хэдий ч интернет алга"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Хувийн DNS серверт хандах боломжгүй байна"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Хязгаарлагдмал холболт"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернэт алга"</string> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 1930cdf75e38..fb7cd1f3cf31 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे कनेक्ट केले"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s द्वारे उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करण्यासाठी टॅप करा"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"कनेक्ट केले, इंटरनेट नाही"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"खाजगी DNS सर्व्हर ॲक्सेस करू शकत नाही"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"मर्यादित कनेक्शन"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट नाही"</string> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index e4931883bacd..72b15ff2a172 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Disambungkan melalui <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketik untuk daftar"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Disambungkan, tiada Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Pelayan DNS peribadi tidak boleh diakses"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Sambungan terhad"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tiada Internet"</string> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 2c4b32c04107..24017ecd8196 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s မှတစ်ဆင့်ရနိုင်သည်"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"အကောင့်ဖွင့်ရန် တို့ပါ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ချိတ်ဆက်ထားသည်၊ အင်တာနက်မရှိ"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"သီးသန့် ဒီအန်အက်စ် (DNS) ဆာဗာကို သုံး၍မရပါ။"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ချိတ်ဆက်မှု ကန့်သတ်ထားသည်"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"အင်တာနက် မရှိပါ"</string> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index 093c06f20132..aca13a78c453 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tilkoblet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgjengelig via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Trykk for å registrere deg"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tilkoblet – ingen Internett-tilgang"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Den private DNS-tjeneren kan ikke nås"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrenset tilkobling"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ingen internettilkobling"</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index fb8b7377e35d..b2c7518eb8d6 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> मार्फत जडान गरिएको"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s मार्फत उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप गर्न ट्याप गर्नुहोस्"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"जडान गरियो तर इन्टरनेट छैन"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"निजी DNS सर्भरमाथि पहुँच प्राप्त गर्न सकिँदैन"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित जडान"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इन्टरनेटमाथिको पहुँच छैन"</string> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index 267dab4cdee8..c74683bdf238 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Verbonden via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Beschikbaar via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te melden"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Verbonden, geen internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Geen toegang tot privé-DNS-server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet"</string> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index d8ae3bfb9a72..704fc42e7ddf 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ଦ୍ବାରା ସଂଯୋଗ କରାଯାଇଛି"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ମାଧ୍ୟମରେ ଉପଲବ୍ଧ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ସାଇନ୍ ଅପ୍ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ସଂଯୁକ୍ତ, ଇଣ୍ଟର୍ନେଟ୍ ନାହିଁ"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"ବ୍ୟକ୍ତିଗତ DNS ସର୍ଭର୍ ଆକ୍ସେସ୍ କରିହେବ ନାହିଁ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ସୀମିତ ସଂଯୋଗ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"କୌଣସି ଇଣ୍ଟରନେଟ୍ ନାହିଁ"</string> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index 6a784865b12c..bf5a32cd33b5 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ਰਾਹੀਂ ਉਪਲਬਧ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ਸਾਈਨ-ਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ਕਨੈਕਟ ਕੀਤਾ, ਕੋਈ ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"ਨਿੱਜੀ ਡੋਮੇਨ ਨਾਮ ਪ੍ਰਣਾਲੀ (DNS) ਸਰਵਰ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ਸੀਮਤ ਕਨੈਕਸ਼ਨ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index 8c5547c2791d..d907760c5567 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Połączenie przez: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostępne przez %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Kliknij, by się zarejestrować"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Połączono, brak internetu"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Brak dostępu do prywatnego serwera DNS"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograniczone połączenie"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brak internetu"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 8c036164e140..e61c84e01179 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectada, sem Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 6aeff1cb271c..8ceeb6333210 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ligado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível através de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ligado, sem Internet."</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível aceder ao servidor DNS."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ligação limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 8c036164e140..e61c84e01179 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectada, sem Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 387441f32b56..5ad8dfd136de 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectat prin <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibilă prin %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Atingeți pentru a vă înscrie"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectată, fără internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Serverul DNS privat nu poate fi accesat"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexiune limitată"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Fără conexiune la internet"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 361e29ff452e..104efdc57b45 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Подключено через приложение \"<xliff:g id="NAME">%1$s</xliff:g>\"."</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступно через %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Нажмите, чтобы зарегистрироваться"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Подключено, без доступа к Интернету"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Доступа к частному DNS-серверу нет."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Подключение к сети ограничено."</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нет подключения к Интернету"</string> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index faa848fec830..f5dad8748b24 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> හරහා සම්බන්ධයි"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s හරහා ලබා ගැනීමට හැකිය"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ලියාපදිංචි වීමට තට්ටු කරන්න"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"සම්බන්ධයි, අන්තර්ජාලය නැත"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"පුද්ගලික DNS සේවාදායකයට ප්රවේශ වීමට නොහැකිය"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"සීමිත සම්බන්ධතාව"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"අන්තර්ජාලය නැත"</string> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 2035d88c75f0..527dafb95a2e 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Pripojené prostredníctvom siete <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"K dispozícii prostredníctvom %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Prihláste sa klepnutím"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Pripojené, žiadny internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"K súkromnému serveru DNS sa nepodarilo získať prístup"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Obmedzené pripojenie"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Žiadny internet"</string> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 288961947269..481695046625 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezava vzpostavljena prek omrežja <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Na voljo prek: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dotaknite se, če se želite registrirati"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Vzpostavljena povezava, brez interneta"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Do zasebnega strežnika DNS ni mogoče dostopati"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Omejena povezava"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brez internetne povezave"</string> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index ccd4e3066d9f..12511dd3582c 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Lidhur përmes <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"E mundshme përmes %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Trokit për t\'u regjistruar"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"U lidh, por nuk ka internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Serveri privat DNS nuk mund të qaset"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Lidhje e kufizuar"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nuk ka internet"</string> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 08e2bc85db4e..4724be52f87c 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Повезано преко: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступна је преко приступне тачке %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Додирните да бисте се регистровали"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Веза је успостављена, нема интернета"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Приступ приватном DNS серверу није успео"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена веза"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернета"</string> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index c0cdbc913aea..9d1bc42b613a 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Anslutet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tillgängligt via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryck för att logga in"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ansluten, inget internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Det går inte att komma åt den privata DNS-servern."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begränsad anslutning"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Inget internet"</string> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index f00dea3822cf..1e2489f89ff6 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Imeunganishwa kupitia <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Inapatikana kupitia %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Gusa ili ujisajili"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Imeunganishwa, hakuna intaneti"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Seva ya faragha ya DNS haiwezi kufikiwa"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Muunganisho hafifu"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Hakuna intaneti"</string> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 52e0363147da..fcb801b112a0 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> மூலம் இணைக்கப்பட்டது"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s வழியாகக் கிடைக்கிறது"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"பதிவு செய்யத் தட்டவும்"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"இணைக்கப்பட்டுள்ளது, ஆனால் இண்டர்நெட் இல்லை"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"தனிப்பட்ட DNS சேவையகத்தை அணுக இயலாது"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"வரம்பிற்கு உட்பட்ட இணைப்பு"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"இணைய இணைப்பு இல்லை"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index a39c4e14cddd..7a8045111baa 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ద్వారా కనెక్ట్ చేయబడింది"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ద్వారా అందుబాటులో ఉంది"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"సైన్ అప్ చేయడానికి నొక్కండి"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"కనెక్ట్ చేయబడింది, ఇంటర్నెట్ లేదు"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"ప్రైవేట్ DNS సర్వర్ను యాక్సెస్ చేయడం సాధ్యపడదు"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"పరిమిత కనెక్షన్"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ఇంటర్నెట్ లేదు"</string> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 635d77a77a35..130e1c4318a1 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"เชื่อมต่อแล้วผ่าน <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"พร้อมใช้งานผ่านทาง %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"แตะเพื่อลงชื่อสมัครใช้"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"เชื่อมต่อแล้ว ไม่พบอินเทอร์เน็ต"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"เข้าถึงเซิร์ฟเวอร์ DNS ไม่ได้"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"การเชื่อมต่อที่จำกัด"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ไม่มีอินเทอร์เน็ต"</string> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index 3f7f0ff56f8a..bc1a40560df0 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Nakakonekta sa pamamagitan ng <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available sa pamamagitan ng %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"I-tap para mag-sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Nakakonekta, walang internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Hindi ma-access ang pribadong DNS server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limitadong koneksyon"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Walang internet"</string> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 7ad6fcd24ea1..4e263ef86cd5 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ile bağlandı"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s üzerinden kullanılabilir"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Kaydolmak için dokunun"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Bağlı, internet yok"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Gizli DNS sunucusuna erişilemiyor"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Sınırlı bağlantı"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yok"</string> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index b5dd61898470..ad9f600ae74d 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Підключено через додаток <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступ через %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Торкніться, щоб увійти"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Під’єднано, але немає доступу до Інтернету"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Немає доступу до приватного DNS-сервера"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Обмежене з’єднання"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Немає Інтернету"</string> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index ef9b2a1b294e..b995da1ad37b 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> کے ذریعے منسلک"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"دستیاب بذریعہ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"سائن اپ کے لیے تھپتھپائیں"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"منسلک، انٹرنیٹ نہیں ہے"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"نجی DNS سرور تک رسائی حاصل نہیں کی جا سکی"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"محدود کنکشن"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"انٹرنیٹ نہیں ہے"</string> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index b202d6444965..f02b63940115 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> orqali ulandi"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s orqali ishlaydi"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Yozilish uchun bosing"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ulangan, lekin internet aloqasi yo‘q"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Xususiy DNS server ishlamayapti"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Cheklangan aloqa"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Internet yo‘q"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 5bcff5ad27b2..8fcf1f2ef21c 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Đã kết nối qua <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Có sẵn qua %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Nhấn để đăng ký"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Đã kết nối, không có Internet"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Không thể truy cập máy chủ DNS riêng tư"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Kết nối giới hạn"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Không có Internet"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 418370b916e6..755cbeee3499 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"已通过<xliff:g id="NAME">%1$s</xliff:g>连接到网络"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可通过%1$s连接"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"点按即可注册"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"已连接,但无法访问互联网"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"无法访问私人 DNS 服务器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"网络连接受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"无法访问互联网"</string> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index 79b55796bda2..e50acf5004fa 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"已透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 連線"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"輕按即可登入"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"已連線,但沒有互聯網"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"連線受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有互聯網連線"</string> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 47ab7641b7ad..7df6fed4d8fd 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 使用"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"輕觸即可註冊"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"已連線,沒有網際網路"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"連線能力受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有網際網路連線"</string> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 87f45de36c58..46dffbe3aa33 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -42,7 +42,8 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ixhumeke nge-<xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Iyatholakala nge-%1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Thepha ukuze ubhalisele"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Kuxhunyiwe, ayikho i-inthanethi"</string> + <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> + <skip /> <string name="private_dns_broken" msgid="1984159464346556931">"Iseva eyimfihlo ye-DNS ayikwazi ukufinyelelwa"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Iqoqo elikhawulelwe"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ayikho i-inthanethi"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index a7df6db3d31f..dd21e5c07b13 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -129,7 +129,7 @@ <string name="certinstaller_package" translatable="false">com.android.certinstaller</string> <!-- Summary for Connected wifi network without internet --> - <string name="wifi_connected_no_internet">Connected, no internet</string> + <string name="wifi_connected_no_internet">No internet</string> <!-- Summary for connected network without internet due to private dns validation failed [CHAR LIMIT=NONE] --> <string name="private_dns_broken">Private DNS server cannot be accessed</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index e008cd038317..a4be46cf1f63 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -40,15 +40,16 @@ public class InfoMediaManager extends MediaManager { final Executor mExecutor = Executors.newSingleThreadExecutor(); @VisibleForTesting MediaRouter2Manager mRouterManager; + @VisibleForTesting + String mPackageName; - private String mPackageName; private MediaDevice mCurrentConnectedDevice; public InfoMediaManager(Context context, String packageName, Notification notification) { super(context, notification); mRouterManager = MediaRouter2Manager.getInstance(context); - if (packageName != null) { + if (!TextUtils.isEmpty(packageName)) { mPackageName = packageName; } } @@ -57,6 +58,7 @@ public class InfoMediaManager extends MediaManager { public void startScan() { mMediaDevices.clear(); mRouterManager.registerCallback(mExecutor, mMediaRouterCallback); + refreshDevices(); } @VisibleForTesting @@ -79,21 +81,37 @@ public class InfoMediaManager extends MediaManager { return mCurrentConnectedDevice; } - class RouterManagerCallback extends MediaRouter2Manager.Callback { + private void refreshDevices() { + mMediaDevices.clear(); + mCurrentConnectedDevice = null; + if (TextUtils.isEmpty(mPackageName)) { + buildAllRoutes(); + } else { + buildAvailableRoutes(); + } + dispatchDeviceListAdded(); + } - private void refreshDevices() { - mMediaDevices.clear(); - mCurrentConnectedDevice = null; - for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) { - final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route, - mPackageName); - if (TextUtils.equals(route.getClientPackageName(), mPackageName)) { - mCurrentConnectedDevice = device; - } - mMediaDevices.add(device); + private void buildAllRoutes() { + for (MediaRoute2Info route : mRouterManager.getAllRoutes()) { + final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route, + mPackageName); + mMediaDevices.add(device); + } + } + + private void buildAvailableRoutes() { + for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) { + final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route, + mPackageName); + if (TextUtils.equals(route.getClientPackageName(), mPackageName)) { + mCurrentConnectedDevice = device; } - dispatchDeviceListAdded(); + mMediaDevices.add(device); } + } + + class RouterManagerCallback extends MediaRouter2Manager.Callback { @Override public void onRoutesAdded(List<MediaRoute2Info> routes) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index e85a102294d8..50196d2b2994 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -18,7 +18,6 @@ package com.android.settingslib.media; import android.app.Notification; import android.bluetooth.BluetoothProfile; import android.content.Context; -import android.text.TextUtils; import android.util.Log; import androidx.annotation.IntDef; @@ -162,10 +161,8 @@ public class LocalMediaManager implements BluetoothCallback { mMediaDevices.clear(); mBluetoothMediaManager.registerCallback(mMediaDeviceCallback); mBluetoothMediaManager.startScan(); - if (!TextUtils.isEmpty(mPackageName)) { - mInfoMediaManager.registerCallback(mMediaDeviceCallback); - mInfoMediaManager.startScan(); - } + mInfoMediaManager.registerCallback(mMediaDeviceCallback); + mInfoMediaManager.startScan(); } private void addPhoneDeviceIfNecessary() { @@ -208,10 +205,8 @@ public class LocalMediaManager implements BluetoothCallback { public void stopScan() { mBluetoothMediaManager.unregisterCallback(mMediaDeviceCallback); mBluetoothMediaManager.stopScan(); - if (!TextUtils.isEmpty(mPackageName)) { - mInfoMediaManager.unregisterCallback(mMediaDeviceCallback); - mInfoMediaManager.stopScan(); - } + mInfoMediaManager.unregisterCallback(mMediaDeviceCallback); + mInfoMediaManager.stopScan(); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 3b1a0c327c9f..44e70f436963 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -1170,8 +1170,8 @@ public class AccessPoint implements Comparable<AccessPoint> { } else { // In range, not disabled. if (mConfig != null) { // Is saved network // Last attempt to connect to this failed. Show reason why - switch (mConfig.recentFailure.getAssociationStatus()) { - case WifiConfiguration.RecentFailure.STATUS_AP_UNABLE_TO_HANDLE_NEW_STA: + switch (mConfig.getRecentFailureReason()) { + case WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA: summary.append(mContext.getString( R.string.wifi_ap_unable_to_handle_new_sta)); break; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 67f6dd903841..3726fb24479a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -74,7 +74,7 @@ public class InfoMediaManagerTest { } @Test - public void onRouteAdded_shouldAddMediaDevice() { + public void onRouteAdded_getAvailableRoutes_shouldAddMediaDevice() { final MediaRoute2Info info = mock(MediaRoute2Info.class); when(info.getId()).thenReturn(TEST_ID); when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); @@ -95,6 +95,27 @@ public class InfoMediaManagerTest { } @Test + public void onRouteAdded_buildAllRoutes_shouldAddMediaDevice() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + final List<MediaRoute2Info> routes = new ArrayList<>(); + routes.add(info); + when(mRouterManager.getAllRoutes()).thenReturn(routes); + + final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); + assertThat(mediaDevice).isNull(); + + mInfoMediaManager.mPackageName = ""; + mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes); + + final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); + assertThat(infoDevice.getId()).isEqualTo(TEST_ID); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); + } + + @Test public void onControlCategoriesChanged_samePackageName_shouldAddMediaDevice() { final MediaRoute2Info info = mock(MediaRoute2Info.class); when(info.getId()).thenReturn(TEST_ID); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index cdf97285a16f..3c52f543c81b 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -45,6 +45,7 @@ import android.provider.settings.validators.Validator; import android.util.ArrayMap; import android.util.ArraySet; import android.util.BackupUtils; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Slog; import android.view.Display; @@ -280,6 +281,16 @@ public class SettingsBackupAgent extends BackupAgentHelper { Settings.Secure.getMovedToGlobalSettings(movedToGlobal); Set<String> movedToSecure = getMovedToSecureSettings(); + Set<String> preservedGlobalSettings = getSettingsToPreserveInRestore( + Settings.Global.CONTENT_URI); + Set<String> preservedSecureSettings = getSettingsToPreserveInRestore( + Settings.Secure.CONTENT_URI); + Set<String> preservedSystemSettings = getSettingsToPreserveInRestore( + Settings.System.CONTENT_URI); + Set<String> preservedSettings = new HashSet<>(preservedGlobalSettings); + preservedSettings.addAll(preservedSecureSettings); + preservedSettings.addAll(preservedSystemSettings); + byte[] restoredWifiSupplicantData = null; byte[] restoredWifiIpConfigData = null; @@ -300,7 +311,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { case KEY_SYSTEM : restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal, movedToSecure, R.array.restore_blocked_system_settings, - dynamicBlockList); + dynamicBlockList, + preservedSystemSettings); mSettingsHelper.applyAudioSettings(); break; @@ -311,7 +323,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToGlobal, null, R.array.restore_blocked_secure_settings, - dynamicBlockList); + dynamicBlockList, + preservedSecureSettings); break; case KEY_GLOBAL : @@ -321,7 +334,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { null, movedToSecure, R.array.restore_blocked_global_settings, - dynamicBlockList); + dynamicBlockList, + preservedGlobalSettings); break; case KEY_WIFI_SUPPLICANT : @@ -368,7 +382,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { restoreDeviceSpecificConfig( restoredDeviceSpecificConfig, R.array.restore_blocked_device_specific_settings, - dynamicBlockList); + dynamicBlockList, + preservedSettings); break; default : @@ -418,7 +433,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { in.readFully(buffer, 0, nBytes); restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal, movedToSecure, R.array.restore_blocked_system_settings, - Collections.emptySet()); + Collections.emptySet(), Collections.emptySet()); // secure settings nBytes = in.readInt(); @@ -432,7 +447,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToGlobal, null, R.array.restore_blocked_secure_settings, - Collections.emptySet()); + Collections.emptySet(), Collections.emptySet()); // Global only if sufficiently new if (version >= FULL_BACKUP_ADDED_GLOBAL) { @@ -443,7 +458,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToGlobal.clear(); // no redirection; this *is* the global namespace restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal, movedToSecure, R.array.restore_blocked_global_settings, - Collections.emptySet()); + Collections.emptySet(), Collections.emptySet()); } // locale @@ -608,6 +623,40 @@ public class SettingsBackupAgent extends BackupAgentHelper { } /** + * Get names of the settings for which the current value should be preserved during restore. + */ + private Set<String> getSettingsToPreserveInRestore(Uri settingsUri) { + if (!FeatureFlagUtils.isEnabled(getApplicationContext(), + FeatureFlagUtils.SETTINGS_DO_NOT_RESTORE_PRESERVED)) { + return Collections.emptySet(); + } + + Cursor cursor = getContentResolver().query(settingsUri, new String[] { + Settings.NameValueTable.NAME, Settings.NameValueTable.IS_PRESERVED_IN_RESTORE }, + /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null); + + if (!cursor.moveToFirst()) { + Slog.i(TAG, "No settings to be preserved in restore"); + return Collections.emptySet(); + } + + int nameIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME); + int isPreservedIndex = cursor.getColumnIndex( + Settings.NameValueTable.IS_PRESERVED_IN_RESTORE); + + Set<String> preservedSettings = new HashSet<>(); + while (!cursor.isAfterLast()) { + if (Boolean.parseBoolean(cursor.getString(isPreservedIndex))) { + preservedSettings.add(getQualifiedKeyForSetting(cursor.getString(nameIndex), + settingsUri)); + } + cursor.moveToNext(); + } + + return preservedSettings; + } + + /** * Serialize the owner info and other lock settings */ private byte[] getLockSettings(@UserIdInt int userId) { @@ -650,7 +699,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { HashSet<String> movedToGlobal, Set<String> movedToSecure, int blockedSettingsArrayId, - Set<String> dynamicBlockList) { + Set<String> dynamicBlockList, + Set<String> settingsToPreserve) { byte[] settings = new byte[data.getDataSize()]; try { data.readEntityData(settings, 0, settings.length); @@ -665,7 +715,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToGlobal, movedToSecure, blockedSettingsArrayId, - dynamicBlockList); + dynamicBlockList, + settingsToPreserve); } private void restoreSettings( @@ -675,7 +726,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { HashSet<String> movedToGlobal, Set<String> movedToSecure, int blockedSettingsArrayId, - Set<String> dynamicBlockList) { + Set<String> dynamicBlockList, + Set<String> settingsToPreserve) { restoreSettings( settings, 0, @@ -684,10 +736,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToGlobal, movedToSecure, blockedSettingsArrayId, - dynamicBlockList); + dynamicBlockList, + settingsToPreserve); } - private void restoreSettings( + @VisibleForTesting + void restoreSettings( byte[] settings, int pos, int bytes, @@ -695,31 +749,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { HashSet<String> movedToGlobal, Set<String> movedToSecure, int blockedSettingsArrayId, - Set<String> dynamicBlockList) { + Set<String> dynamicBlockList, + Set<String> settingsToPreserve) { if (DEBUG) { Log.i(TAG, "restoreSettings: " + contentUri); } - // Figure out the white list and redirects to the global table. We restore anything - // in either the backup whitelist or the legacy-restore whitelist for this table. - final String[] whitelist; - Map<String, Validator> validators = null; - if (contentUri.equals(Settings.Secure.CONTENT_URI)) { - whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP, - Settings.Secure.LEGACY_RESTORE_SETTINGS, - DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP); - validators = SecureSettingsValidators.VALIDATORS; - } else if (contentUri.equals(Settings.System.CONTENT_URI)) { - whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP, - Settings.System.LEGACY_RESTORE_SETTINGS); - validators = SystemSettingsValidators.VALIDATORS; - } else if (contentUri.equals(Settings.Global.CONTENT_URI)) { - whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP, - Settings.Global.LEGACY_RESTORE_SETTINGS); - validators = GlobalSettingsValidators.VALIDATORS; - } else { - throw new IllegalArgumentException("Unknown URI: " + contentUri); - } + SettingsBackupWhitelist whitelist = getBackupWhitelist(contentUri); // Restore only the white list data. final ArrayMap<String, String> cachedEntries = new ArrayMap<>(); @@ -729,7 +765,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { Set<String> blockedSettings = getBlockedSettings(blockedSettingsArrayId); - for (String key : whitelist) { + for (String key : whitelist.mSettingsWhitelist) { boolean isBlockedBySystem = blockedSettings != null && blockedSettings.contains(key); if (isBlockedBySystem || isBlockedByDynamicList(dynamicBlockList, contentUri, key)) { Log.i( @@ -742,6 +778,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { continue; } + if (settingsToPreserve.contains(getQualifiedKeyForSetting(key, contentUri))) { + Log.i(TAG, "Skipping restore for setting " + key + " as it is marked as " + + "preserved"); + continue; + } + String value = null; boolean hasValueToRestore = false; if (cachedEntries.indexOfKey(key) >= 0) { @@ -775,7 +817,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { } // only restore the settings that have valid values - if (!isValidSettingValue(key, value, validators)) { + if (!isValidSettingValue(key, value, whitelist.mSettingsValidators)) { Log.w(TAG, "Attempted restore of " + key + " setting, but its value didn't pass" + " validation, value: " + value); continue; @@ -798,11 +840,42 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } + @VisibleForTesting + SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) { + // Figure out the white list and redirects to the global table. We restore anything + // in either the backup whitelist or the legacy-restore whitelist for this table. + String[] whitelist; + Map<String, Validator> validators = null; + if (contentUri.equals(Settings.Secure.CONTENT_URI)) { + whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP, + Settings.Secure.LEGACY_RESTORE_SETTINGS, + DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP); + validators = SecureSettingsValidators.VALIDATORS; + } else if (contentUri.equals(Settings.System.CONTENT_URI)) { + whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP, + Settings.System.LEGACY_RESTORE_SETTINGS); + validators = SystemSettingsValidators.VALIDATORS; + } else if (contentUri.equals(Settings.Global.CONTENT_URI)) { + whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP, + Settings.Global.LEGACY_RESTORE_SETTINGS); + validators = GlobalSettingsValidators.VALIDATORS; + } else { + throw new IllegalArgumentException("Unknown URI: " + contentUri); + } + + return new SettingsBackupWhitelist(whitelist, validators); + } + private boolean isBlockedByDynamicList(Set<String> dynamicBlockList, Uri areaUri, String key) { String contentKey = Uri.withAppendedPath(areaUri, key).toString(); return dynamicBlockList.contains(contentKey); } + @VisibleForTesting + static String getQualifiedKeyForSetting(String settingName, Uri settingUri) { + return Uri.withAppendedPath(settingUri, settingName).toString(); + } + // There may be other sources of blocked settings, so I'm separating out this // code to make it easy to modify in the future. @VisibleForTesting @@ -1089,7 +1162,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { */ @VisibleForTesting boolean restoreDeviceSpecificConfig(byte[] data, int blockedSettingsArrayId, - Set<String> dynamicBlocklist) { + Set<String> dynamicBlocklist, Set<String> preservedSettings) { // We're using an AtomicInteger to wrap the position int and allow called methods to // modify it. AtomicInteger pos = new AtomicInteger(0); @@ -1108,7 +1181,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { null, null, blockedSettingsArrayId, - dynamicBlocklist); + dynamicBlocklist, + preservedSettings); updateWindowManagerIfNeeded(originalDensity); @@ -1240,4 +1314,20 @@ public class SettingsBackupAgent extends BackupAgentHelper { | ((in[pos + 3] & 0xFF) << 0); return result; } + + /** + * Store the whitelist of settings to be backed up and validators for them. + */ + @VisibleForTesting + static class SettingsBackupWhitelist { + final String[] mSettingsWhitelist; + final Map<String, Validator> mSettingsValidators; + + + SettingsBackupWhitelist(String[] settingsWhitelist, + Map<String, Validator> settingsValidators) { + mSettingsWhitelist = settingsWhitelist; + mSettingsValidators = settingsValidators; + } + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 874e29940202..c969bfd193b5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -180,10 +180,17 @@ public class SettingsProvider extends ContentProvider { private static final int MUTATION_OPERATION_UPDATE = 3; private static final int MUTATION_OPERATION_RESET = 4; + private static final String[] LEGACY_SQL_COLUMNS = new String[] { + Settings.NameValueTable._ID, + Settings.NameValueTable.NAME, + Settings.NameValueTable.VALUE, + }; + private static final String[] ALL_COLUMNS = new String[] { Settings.NameValueTable._ID, Settings.NameValueTable.NAME, - Settings.NameValueTable.VALUE + Settings.NameValueTable.VALUE, + Settings.NameValueTable.IS_PRESERVED_IN_RESTORE, }; public static final int SETTINGS_TYPE_GLOBAL = SettingsState.SETTINGS_TYPE_GLOBAL; @@ -2353,6 +2360,10 @@ public class SettingsProvider extends ContentProvider { case Settings.NameValueTable.VALUE: { values[i] = setting.getValue(); } break; + + case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE: { + values[i] = String.valueOf(setting.isValuePreservedInRestore()); + } break; } } @@ -3097,7 +3108,7 @@ public class SettingsProvider extends ContentProvider { SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); queryBuilder.setTables(table); - Cursor cursor = queryBuilder.query(database, ALL_COLUMNS, + Cursor cursor = queryBuilder.query(database, LEGACY_SQL_COLUMNS, null, null, null, null, null); if (cursor == null) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index db18213a3599..9934e59d8d56 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -41,13 +41,13 @@ import android.util.AtomicFile; import android.util.Base64; import android.util.Slog; import android.util.SparseIntArray; -import android.util.StatsLog; import android.util.TimeUtils; import android.util.Xml; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FrameworkStatsLog; import libcore.io.IoUtils; @@ -429,8 +429,9 @@ final class SettingsState { mSettings.put(name, newState); } - StatsLog.write(StatsLog.SETTING_CHANGED, name, value, newState.value, oldValue, tag, - makeDefault, getUserIdFromKey(mKey), StatsLog.SETTING_CHANGED__REASON__UPDATED); + FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, value, newState.value, + oldValue, tag, makeDefault, getUserIdFromKey(mKey), + FrameworkStatsLog.SETTING_CHANGED__REASON__UPDATED); addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState); @@ -489,9 +490,9 @@ final class SettingsState { if (key.startsWith(prefix) && !keyValues.containsKey(key)) { Setting oldState = mSettings.remove(key); - StatsLog.write(StatsLog.SETTING_CHANGED, key, /* value= */ "", /* newValue= */ "", - oldState.value, /* tag */ "", false, getUserIdFromKey(mKey), - StatsLog.SETTING_CHANGED__REASON__DELETED); + FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key, + /* value= */ "", /* newValue= */ "", oldState.value, /* tag */ "", false, + getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED); addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState); changedKeys.add(key); // key was removed } @@ -516,9 +517,9 @@ final class SettingsState { continue; } - StatsLog.write(StatsLog.SETTING_CHANGED, key, value, state.value, oldValue, - /* tag */ null, /* make default */ false, - getUserIdFromKey(mKey), StatsLog.SETTING_CHANGED__REASON__UPDATED); + FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key, value, state.value, + oldValue, /* tag */ null, /* make default */ false, + getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__UPDATED); addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, state); } @@ -544,9 +545,9 @@ final class SettingsState { Setting oldState = mSettings.remove(name); - StatsLog.write(StatsLog.SETTING_CHANGED, name, /* value= */ "", /* newValue= */ "", - oldState.value, /* tag */ "", false, getUserIdFromKey(mKey), - StatsLog.SETTING_CHANGED__REASON__DELETED); + FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "", + /* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey), + FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED); updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, null, oldState.defaultValue, null); diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java index e6508823c7e3..f5334fb7e6ea 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java @@ -32,6 +32,8 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.Settings; +import android.provider.settings.validators.SettingsValidators; +import android.provider.settings.validators.Validator; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; @@ -43,6 +45,7 @@ import org.junit.runner.RunWith; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -55,12 +58,24 @@ import java.util.concurrent.atomic.AtomicInteger; /** Tests for the SettingsHelperTest */ @RunWith(AndroidJUnit4.class) public class SettingsBackupAgentTest extends BaseSettingsProviderTest { - + private static final Uri TEST_URI = Uri.EMPTY; private static final String TEST_DISPLAY_DENSITY_FORCED = "123"; + private static final String OVERRIDDEN_TEST_SETTING = "overridden_setting"; + private static final String PRESERVED_TEST_SETTING = "preserved_setting"; + private static final Map<String, String> DEVICE_SPECIFIC_TEST_VALUES = new HashMap<>(); private static final Map<String, String> TEST_VALUES = new HashMap<>(); + private static final Map<String, Validator> TEST_VALUES_VALIDATORS = new HashMap<>(); static { - TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED, TEST_DISPLAY_DENSITY_FORCED); + DEVICE_SPECIFIC_TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED, + TEST_DISPLAY_DENSITY_FORCED); + + TEST_VALUES.put(OVERRIDDEN_TEST_SETTING, "123"); + TEST_VALUES.put(PRESERVED_TEST_SETTING, "124"); + + TEST_VALUES_VALIDATORS.put(OVERRIDDEN_TEST_SETTING, + SettingsValidators.ANY_STRING_VALIDATOR); + TEST_VALUES_VALIDATORS.put(PRESERVED_TEST_SETTING, SettingsValidators.ANY_STRING_VALIDATOR); } private TestFriendlySettingsBackupAgent mAgentUnderTest; @@ -83,14 +98,15 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration(); - assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries); + assertEquals("Not all values backed up.", DEVICE_SPECIFIC_TEST_VALUES.keySet(), helper.mReadEntries); mAgentUnderTest.restoreDeviceSpecificConfig( settingsBackup, R.array.restore_blocked_device_specific_settings, + Collections.emptySet(), Collections.emptySet()); - assertEquals("Not all values were restored.", TEST_VALUES, helper.mWrittenValues); + assertEquals("Not all values were restored.", DEVICE_SPECIFIC_TEST_VALUES, helper.mWrittenValues); } @Test @@ -100,12 +116,13 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration(); - assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries); - mAgentUnderTest.setBlockedSettings(TEST_VALUES.keySet().toArray(new String[0])); + assertEquals("Not all values backed up.", DEVICE_SPECIFIC_TEST_VALUES.keySet(), helper.mReadEntries); + mAgentUnderTest.setBlockedSettings(DEVICE_SPECIFIC_TEST_VALUES.keySet().toArray(new String[0])); mAgentUnderTest.restoreDeviceSpecificConfig( settingsBackup, R.array.restore_blocked_device_specific_settings, + Collections.emptySet(), Collections.emptySet()); assertTrue("Not all values were blocked.", helper.mWrittenValues.isEmpty()); @@ -172,9 +189,50 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { mAgentUnderTest.restoreDeviceSpecificConfig( data, R.array.restore_blocked_device_specific_settings, + Collections.emptySet(), Collections.emptySet())); } + @Test + public void testOnRestore_preservedSettingsAreNotRestored() { + SettingsBackupAgent.SettingsBackupWhitelist whitelist = + new SettingsBackupAgent.SettingsBackupWhitelist( + new String[] { OVERRIDDEN_TEST_SETTING, PRESERVED_TEST_SETTING }, + TEST_VALUES_VALIDATORS); + mAgentUnderTest.setSettingsWhitelist(whitelist); + mAgentUnderTest.setBlockedSettings(); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest.restoreSettings(backupData, /* pos */ 0, backupData.length, TEST_URI, new HashSet<>(), + Collections.emptySet(), /* blockedSettingsArrayId */ 0, Collections.emptySet(), + new HashSet<>(Collections.singletonList(SettingsBackupAgent.getQualifiedKeyForSetting(PRESERVED_TEST_SETTING, TEST_URI)))); + + assertTrue(settingsHelper.mWrittenValues.containsKey(OVERRIDDEN_TEST_SETTING)); + assertFalse(settingsHelper.mWrittenValues.containsKey(PRESERVED_TEST_SETTING)); + } + + private byte[] generateBackupData(Map<String, String> keyValueData) { + int totalBytes = 0; + for (String key : keyValueData.keySet()) { + totalBytes += 2 * Integer.BYTES + key.getBytes().length + + keyValueData.get(key).getBytes().length; + } + + ByteBuffer buffer = ByteBuffer.allocate(totalBytes); + for (String key : keyValueData.keySet()) { + byte[] keyBytes = key.getBytes(); + byte[] valueBytes = keyValueData.get(key).getBytes(); + buffer.putInt(keyBytes.length); + buffer.put(keyBytes); + buffer.putInt(valueBytes.length); + buffer.put(valueBytes); + } + + return buffer.array(); + } + private byte[] generateUncorruptedHeader() throws IOException { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { mAgentUnderTest.writeHeader(os); @@ -219,6 +277,7 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { private static class TestFriendlySettingsBackupAgent extends SettingsBackupAgent { private Boolean mForcedDeviceInfoRestoreAcceptability = null; private String[] mBlockedSettings = null; + private SettingsBackupWhitelist mSettingsWhitelist = null; void setForcedDeviceInfoRestoreAcceptability(boolean value) { mForcedDeviceInfoRestoreAcceptability = value; @@ -228,6 +287,10 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { mBlockedSettings = blockedSettings; } + void setSettingsWhitelist(SettingsBackupWhitelist settingsWhitelist) { + mSettingsWhitelist = settingsWhitelist; + } + @Override protected Set<String> getBlockedSettings(int blockedSettingsArrayId) { return mBlockedSettings == null @@ -241,6 +304,15 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { ? super.isSourceAcceptable(data, pos) : mForcedDeviceInfoRestoreAcceptability; } + + @Override + SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) { + if (mSettingsWhitelist == null) { + return super.getBackupWhitelist(contentUri); + } + + return mSettingsWhitelist; + } } /** The TestSettingsHelper tracks which values have been backed up and/or restored. */ @@ -257,7 +329,7 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { @Override public String onBackupValue(String key, String value) { mReadEntries.add(key); - String readValue = TEST_VALUES.get(key); + String readValue = DEVICE_SPECIFIC_TEST_VALUES.get(key); assert readValue != null; return readValue; } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 26fa1cf46974..149eaf47b97d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -646,6 +646,7 @@ android:label="Controls Providers" android:theme="@style/Theme.SystemUI" android:exported="true" + android:showForAllUsers="true" android:excludeFromRecents="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> @@ -655,6 +656,7 @@ android:parentActivityName=".controls.management.ControlsProviderSelectorActivity" android:theme="@style/Theme.SystemUI" android:excludeFromRecents="true" + android:showForAllUsers="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 2aa6d9512898..d72ce5eea78d 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -328,6 +328,10 @@ <item type="id" name="action_toggle_overview"/> + <!-- Whether or not to show notifications to the user. If disabled, SystemUI will still be + registered as a notification listener, but will ignore all notification events. --> + <bool name="config_renderNotifications">true</bool> + <!-- Whether or not the gear icon on notifications should be shown. The gear is shown when the the notification is not swiped enough to dismiss it. --> <bool name="config_showNotificationGear">true</bool> diff --git a/packages/SystemUI/src/com/android/systemui/DejankUtils.java b/packages/SystemUI/src/com/android/systemui/DejankUtils.java index 97578e19ccd1..3fce55f6e515 100644 --- a/packages/SystemUI/src/com/android/systemui/DejankUtils.java +++ b/packages/SystemUI/src/com/android/systemui/DejankUtils.java @@ -61,19 +61,21 @@ public class DejankUtils { || !isMainThread() || sTemporarilyIgnoreStrictMode) { return null; } + } - try { - String description = binder.getInterfaceDescriptor(); + try { + String description = binder.getInterfaceDescriptor(); + synchronized (sLock) { if (sWhitelistedFrameworkClasses.contains(description)) { return null; } - } catch (RemoteException e) { - e.printStackTrace(); } - - StrictMode.noteSlowCall("IPC detected on critical path: " + sBlockingIpcs.peek()); - return null; + } catch (RemoteException e) { + e.printStackTrace(); } + + StrictMode.noteSlowCall("IPC detected on critical path: " + sBlockingIpcs.peek()); + return null; } @Override @@ -126,9 +128,11 @@ public class DejankUtils { if (STRICT_MODE_ENABLED && sBlockingIpcs.empty()) { synchronized (sLock) { sBlockingIpcs.push("detectBlockingIpcs"); - try { - runnable.run(); - } finally { + } + try { + runnable.run(); + } finally { + synchronized (sLock) { sBlockingIpcs.pop(); } } @@ -177,9 +181,11 @@ public class DejankUtils { if (STRICT_MODE_ENABLED && !sTemporarilyIgnoreStrictMode) { synchronized (sLock) { sTemporarilyIgnoreStrictMode = true; - try { - runnable.run(); - } finally { + } + try { + runnable.run(); + } finally { + synchronized (sLock) { sTemporarilyIgnoreStrictMode = false; } } @@ -196,14 +202,16 @@ public class DejankUtils { if (STRICT_MODE_ENABLED && !sTemporarilyIgnoreStrictMode) { synchronized (sLock) { sTemporarilyIgnoreStrictMode = true; - final T val; - try { - val = supplier.get(); - } finally { + } + final T val; + try { + val = supplier.get(); + } finally { + synchronized (sLock) { sTemporarilyIgnoreStrictMode = false; } - return val; } + return val; } else { return supplier.get(); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt index e6cdf50580d8..53841e2f144b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt @@ -18,4 +18,8 @@ package com.android.systemui.controls import android.service.controls.Control -data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
\ No newline at end of file +data class ControlStatus( + val control: Control, + val favorite: Boolean, + val removed: Boolean = false +)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt index 265ddd8043b6..588ef5c4e68f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt @@ -22,7 +22,7 @@ import com.android.settingslib.applications.DefaultAppInfo class ControlsServiceInfo( context: Context, - serviceInfo: ServiceInfo + val serviceInfo: ServiceInfo ) : DefaultAppInfo( context, context.packageManager, diff --git a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt new file mode 100644 index 000000000000..4f39f2255a75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls + +import android.os.UserHandle + +interface UserAwareController { + + fun changeUser(newUser: UserHandle) {} + val currentUserId: Int +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt index 6b7fc4b7e827..12c3ce9c69ee 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt @@ -19,8 +19,9 @@ package com.android.systemui.controls.controller import android.content.ComponentName import android.service.controls.Control import android.service.controls.actions.ControlAction +import com.android.systemui.controls.UserAwareController -interface ControlsBindingController { +interface ControlsBindingController : UserAwareController { fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) fun bindServices(components: List<ComponentName>) fun subscribe(controls: List<ControlInfo>) diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt index 2db2cf1af191..48d2fa68d198 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -19,6 +19,7 @@ package com.android.systemui.controls.controller import android.content.ComponentName import android.content.Context import android.os.IBinder +import android.os.UserHandle import android.service.controls.Control import android.service.controls.IControlsActionCallback import android.service.controls.IControlsLoadCallback @@ -50,12 +51,17 @@ open class ControlsBindingControllerImpl @Inject constructor( private val refreshing = AtomicBoolean(false) + private var currentUser = context.user + + override val currentUserId: Int + get() = currentUser.identifier + @GuardedBy("componentMap") private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> = ArrayMap<IBinder, ControlsProviderLifecycleManager>() @GuardedBy("componentMap") - private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> = - ArrayMap<ComponentName, ControlsProviderLifecycleManager>() + private val componentMap: MutableMap<Key, ControlsProviderLifecycleManager> = + ArrayMap<Key, ControlsProviderLifecycleManager>() private val loadCallbackService = object : IControlsLoadCallback.Stub() { override fun accept(token: IBinder, controls: MutableList<Control>) { @@ -103,6 +109,7 @@ open class ControlsBindingControllerImpl @Inject constructor( loadCallbackService, actionCallbackService, subscriberService, + currentUser, component ) } @@ -110,7 +117,7 @@ open class ControlsBindingControllerImpl @Inject constructor( private fun retrieveLifecycleManager(component: ComponentName): ControlsProviderLifecycleManager { synchronized(componentMap) { - val provider = componentMap.getOrPut(component) { + val provider = componentMap.getOrPut(Key(component, currentUser)) { createProviderManager(component) } tokenMap.putIfAbsent(provider.token, provider) @@ -137,7 +144,7 @@ open class ControlsBindingControllerImpl @Inject constructor( val providersWithFavorites = controlsByComponentName.keys synchronized(componentMap) { componentMap.forEach { - if (it.key !in providersWithFavorites) { + if (it.key.component !in providersWithFavorites) { backgroundExecutor.execute { it.value.unbindService() } } } @@ -167,6 +174,36 @@ open class ControlsBindingControllerImpl @Inject constructor( } } + override fun changeUser(newUser: UserHandle) { + if (newUser == currentUser) return + synchronized(componentMap) { + unbindAllProvidersLocked() // unbind all providers from the old user + } + refreshing.set(false) + currentUser = newUser + } + + private fun unbindAllProvidersLocked() { + componentMap.values.forEach { + if (it.user == currentUser) { + it.unbindService() + } + } + } + + override fun toString(): String { + return StringBuilder(" ControlsBindingController:\n").apply { + append(" refreshing=${refreshing.get()}\n") + append(" currentUser=$currentUser\n") + append(" Providers:\n") + synchronized(componentMap) { + componentMap.values.forEach { + append(" $it\n") + } + } + }.toString() + } + private abstract inner class CallbackRunnable(val token: IBinder) : Runnable { protected val provider: ControlsProviderLifecycleManager? = synchronized(componentMap) { @@ -183,6 +220,10 @@ open class ControlsBindingControllerImpl @Inject constructor( Log.e(TAG, "No provider found for token:$token") return } + if (provider.user != currentUser) { + Log.e(TAG, "User ${provider.user} is not current user") + return + } synchronized(componentMap) { if (token !in tokenMap.keys) { Log.e(TAG, "Provider for token:$token does not exist anymore") @@ -204,6 +245,10 @@ open class ControlsBindingControllerImpl @Inject constructor( if (!refreshing.get()) { Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}") } + if (provider?.user != currentUser) { + Log.e(TAG, "User ${provider?.user} is not current user") + return + } provider?.let { lazyController.get().refreshStatus(it.componentName, control) } @@ -229,7 +274,7 @@ open class ControlsBindingControllerImpl @Inject constructor( ) : CallbackRunnable(token) { override fun run() { provider?.let { - Log.i(TAG, "onComplete receive from '${provider?.componentName}'") + Log.i(TAG, "onComplete receive from '${provider.componentName}'") } } } @@ -240,7 +285,7 @@ open class ControlsBindingControllerImpl @Inject constructor( ) : CallbackRunnable(token) { override fun run() { provider?.let { - Log.e(TAG, "onError receive from '${provider?.componentName}': $error") + Log.e(TAG, "onError receive from '${provider.componentName}': $error") } } } @@ -251,9 +296,15 @@ open class ControlsBindingControllerImpl @Inject constructor( @ControlAction.ResponseResult val response: Int ) : CallbackRunnable(token) { override fun run() { + if (provider?.user != currentUser) { + Log.e(TAG, "User ${provider?.user} is not current user") + return + } provider?.let { lazyController.get().onActionResponse(it.componentName, controlId, response) } } } } + +private data class Key(val component: ComponentName, val user: UserHandle)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index e098faa00d03..b02de4500043 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -20,8 +20,9 @@ import android.content.ComponentName import android.service.controls.Control import android.service.controls.actions.ControlAction import com.android.systemui.controls.ControlStatus +import com.android.systemui.controls.UserAwareController -interface ControlsController { +interface ControlsController : UserAwareController { val available: Boolean fun getFavoriteControls(): List<ControlInfo> diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index d5b5b5f0442e..6ff1cf8474d3 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -17,10 +17,13 @@ package com.android.systemui.controls.controller import android.app.PendingIntent +import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.os.Environment +import android.os.UserHandle import android.provider.Settings import android.service.controls.Control import android.service.controls.actions.ControlAction @@ -29,14 +32,17 @@ import android.util.Log import com.android.internal.annotations.GuardedBy import com.android.systemui.DumpController import com.android.systemui.Dumpable +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.management.ControlsFavoritingActivity +import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.concurrency.DelayableExecutor import java.io.FileDescriptor import java.io.PrintWriter import java.util.Optional +import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -46,35 +52,101 @@ class ControlsControllerImpl @Inject constructor ( @Background private val executor: DelayableExecutor, private val uiController: ControlsUiController, private val bindingController: ControlsBindingController, - private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, + private val listingController: ControlsListingController, + broadcastDispatcher: BroadcastDispatcher, + optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, dumpController: DumpController ) : Dumpable, ControlsController { companion object { private const val TAG = "ControlsControllerImpl" const val CONTROLS_AVAILABLE = "systemui.controls_available" + const val USER_CHANGE_RETRY_DELAY = 500L // ms } - override val available = Settings.Secure.getInt( + // Map of map: ComponentName -> (String -> ControlInfo). + // Only for current user + @GuardedBy("currentFavorites") + private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() + + private var userChanging = true + override var available = Settings.Secure.getInt( context.contentResolver, CONTROLS_AVAILABLE, 0) != 0 - val persistenceWrapper = optionalWrapper.orElseGet { + private set + + private var currentUser = context.user + override val currentUserId + get() = currentUser.identifier + + private val persistenceWrapper = optionalWrapper.orElseGet { ControlsFavoritePersistenceWrapper( Environment.buildPath( - context.filesDir, - ControlsFavoritePersistenceWrapper.FILE_NAME), + context.filesDir, + ControlsFavoritePersistenceWrapper.FILE_NAME + ), executor ) } - // Map of map: ComponentName -> (String -> ControlInfo) - @GuardedBy("currentFavorites") - private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() + private fun setValuesForUser(newUser: UserHandle) { + Log.d(TAG, "Changing to user: $newUser") + currentUser = newUser + val userContext = context.createContextAsUser(currentUser, 0) + val fileName = Environment.buildPath( + userContext.filesDir, ControlsFavoritePersistenceWrapper.FILE_NAME) + persistenceWrapper.changeFile(fileName) + available = Settings.Secure.getIntForUser( + context.contentResolver, CONTROLS_AVAILABLE, 0) != 0 + synchronized(currentFavorites) { + currentFavorites.clear() + } + if (available) { + loadFavorites() + } + bindingController.changeUser(newUser) + listingController.changeUser(newUser) + userChanging = false + } + + private val userSwitchReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_USER_SWITCHED) { + userChanging = true + val newUser = + UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId)) + if (currentUser == newUser) { + userChanging = false + return + } + setValuesForUser(newUser) + } + } + } init { + dumpController.registerDumpable(this) if (available) { - dumpController.registerDumpable(this) loadFavorites() } + userChanging = false + broadcastDispatcher.registerReceiver( + userSwitchReceiver, + IntentFilter(Intent.ACTION_USER_SWITCHED), + executor, + UserHandle.ALL + ) + } + + private fun confirmAvailability(): Boolean { + if (userChanging) { + Log.w(TAG, "Controls not available while user is changing") + return false + } + if (!available) { + Log.d(TAG, "Controls not available") + return false + } + return true } private fun loadFavorites() { @@ -91,8 +163,18 @@ class ControlsControllerImpl @Inject constructor ( componentName: ComponentName, callback: (List<ControlStatus>) -> Unit ) { - if (!available) { - Log.d(TAG, "Controls not available") + if (!confirmAvailability()) { + if (userChanging) { + // Try again later, userChanging should not last forever. If so, we have bigger + // problems + executor.executeDelayed( + { loadForComponent(componentName, callback) }, + USER_CHANGE_RETRY_DELAY, + TimeUnit.MILLISECONDS + ) + } else { + callback(emptyList()) + } return } bindingController.bindAndLoad(componentName) { @@ -158,10 +240,7 @@ class ControlsControllerImpl @Inject constructor ( } override fun subscribeToFavorites() { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return // Make a copy of the favorites list val favorites = synchronized(currentFavorites) { currentFavorites.flatMap { it.value.values.toList() } @@ -170,18 +249,12 @@ class ControlsControllerImpl @Inject constructor ( } override fun unsubscribe() { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return bindingController.unsubscribe() } override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return var changed = false val listOfControls = synchronized(currentFavorites) { if (state) { @@ -211,7 +284,7 @@ class ControlsControllerImpl @Inject constructor ( } override fun refreshStatus(componentName: ComponentName, control: Control) { - if (!available) { + if (!confirmAvailability()) { Log.d(TAG, "Controls not available") return } @@ -227,28 +300,24 @@ class ControlsControllerImpl @Inject constructor ( } override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return uiController.onActionResponse(componentName, controlId, response) } override fun getFavoriteControls(): List<ControlInfo> { - if (!available) { - Log.d(TAG, "Controls not available") - return emptyList() - } + if (!confirmAvailability()) return emptyList() synchronized(currentFavorites) { return favoritesAsListLocked() } } override fun action(controlInfo: ControlInfo, action: ControlAction) { + if (!confirmAvailability()) return bindingController.action(controlInfo, action) } override fun clearFavorites() { + if (!confirmAvailability()) return val changed = synchronized(currentFavorites) { currentFavorites.isNotEmpty().also { currentFavorites.clear() @@ -261,6 +330,9 @@ class ControlsControllerImpl @Inject constructor ( override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("ControlsController state:") + pw.println(" Available: $available") + pw.println(" Changing users: $userChanging") + pw.println(" Current user: ${currentUser.identifier}") pw.println(" Favorites:") synchronized(currentFavorites) { currentFavorites.forEach { @@ -269,5 +341,6 @@ class ControlsControllerImpl @Inject constructor ( } } } + pw.println(bindingController.toString()) } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt index 6f2d71fd0f59..7d1df14bbf1b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt @@ -16,7 +16,6 @@ package com.android.systemui.controls.controller -import android.app.ActivityManager import android.content.ComponentName import android.util.AtomicFile import android.util.Log @@ -32,8 +31,8 @@ import java.io.FileNotFoundException import java.io.IOException class ControlsFavoritePersistenceWrapper( - val file: File, - val executor: DelayableExecutor + private var file: File, + private var executor: DelayableExecutor ) { companion object { @@ -47,11 +46,13 @@ class ControlsFavoritePersistenceWrapper( private const val TAG_TYPE = "type" } - val currentUser: Int - get() = ActivityManager.getCurrentUser() + fun changeFile(fileName: File) { + file = fileName + } fun storeFavorites(list: List<ControlInfo>) { executor.execute { + Log.d(TAG, "Saving data to file: $file") val atomicFile = AtomicFile(file) val writer = try { atomicFile.startWrite() @@ -98,6 +99,7 @@ class ControlsFavoritePersistenceWrapper( return emptyList() } try { + Log.d(TAG, "Reading data from file: $file") val parser = Xml.newPullParser() parser.setInput(reader, null) return parseXml(parser) @@ -111,7 +113,7 @@ class ControlsFavoritePersistenceWrapper( } private fun parseXml(parser: XmlPullParser): List<ControlInfo> { - var type: Int = 0 + var type = 0 val infos = mutableListOf<ControlInfo>() while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -123,9 +125,9 @@ class ControlsFavoritePersistenceWrapper( parser.getAttributeValue(null, TAG_COMPONENT)) val id = parser.getAttributeValue(null, TAG_ID) val title = parser.getAttributeValue(null, TAG_TITLE) - val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt() - if (component != null && id != null && title != null && type != null) { - infos.add(ControlInfo(component, id, title, type)) + val deviceType = parser.getAttributeValue(null, TAG_TYPE)?.toInt() + if (component != null && id != null && title != null && deviceType != null) { + infos.add(ControlInfo(component, id, title, deviceType)) } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt index 99aa3601ba30..739ca7ec0c29 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -24,6 +24,7 @@ import android.os.Binder import android.os.Bundle import android.os.IBinder import android.os.RemoteException +import android.os.UserHandle import android.service.controls.Control import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE import android.service.controls.ControlsProviderService.CALLBACK_TOKEN @@ -46,6 +47,7 @@ class ControlsProviderLifecycleManager( private val loadCallbackService: IControlsLoadCallback.Stub, private val actionCallbackService: IControlsActionCallback.Stub, private val subscriberService: IControlsSubscriber.Stub, + val user: UserHandle, val componentName: ComponentName ) : IBinder.DeathRecipient { @@ -96,7 +98,7 @@ class ControlsProviderLifecycleManager( } bindTryCount++ try { - isBound = context.bindService(intent, serviceConnection, BIND_FLAGS) + isBound = context.bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user) } catch (e: SecurityException) { Log.e(TAG, "Failed to bind to service", e) isBound = false @@ -152,7 +154,9 @@ class ControlsProviderLifecycleManager( load() } queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run { - subscribe(this) + if (this.isNotEmpty()) { + subscribe(this) + } } queue.filter { it is Message.Action }.forEach { val msg = it as Message.Action @@ -286,6 +290,15 @@ class ControlsProviderLifecycleManager( maybeUnbindAndRemoveCallback() } + override fun toString(): String { + return StringBuilder("ControlsProviderLifecycleManager(").apply { + append("component=$componentName") + append(", user=$user") + append(", bound=$isBound") + append(")") + }.toString() + } + sealed class Message { abstract val type: Int object Load : Message() { @@ -301,4 +314,4 @@ class ControlsProviderLifecycleManager( override val type = MSG_ACTION } } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt index d62bb4def3aa..22c69086cf8c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView import com.android.settingslib.widget.CandidateInfo import com.android.systemui.R diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 01c4fef67fd4..7ee4fd5b059e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -22,15 +22,18 @@ import android.os.Bundle import android.view.LayoutInflater import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.settings.CurrentUserTracker import java.util.concurrent.Executor import javax.inject.Inject class ControlsFavoritingActivity @Inject constructor( @Main private val executor: Executor, - private val controller: ControlsControllerImpl + private val controller: ControlsControllerImpl, + broadcastDispatcher: BroadcastDispatcher ) : Activity() { companion object { @@ -42,11 +45,24 @@ class ControlsFavoritingActivity @Inject constructor( private lateinit var recyclerView: RecyclerView private lateinit var adapter: ControlAdapter + private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { + private val startingUser = controller.currentUserId + + override fun onUserSwitched(newUserId: Int) { + if (newUserId != startingUser) { + stopTracking() + finish() + } + } + } + + private var component: ComponentName? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val app = intent.getCharSequenceExtra(EXTRA_APP) - val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT) + component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT) // If we have no component name, there's not much we can do. val callback = component?.let { @@ -68,6 +84,11 @@ class ControlsFavoritingActivity @Inject constructor( } setContentView(recyclerView) + currentUserTracker.startTracking() + } + + override fun onResume() { + super.onResume() component?.let { controller.loadForComponent(it) { executor.execute { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt index 09e0ce9fea8d..34db684022fb 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt @@ -18,14 +18,17 @@ package com.android.systemui.controls.management import android.content.ComponentName import com.android.settingslib.widget.CandidateInfo +import com.android.systemui.controls.UserAwareController import com.android.systemui.statusbar.policy.CallbackController interface ControlsListingController : - CallbackController<ControlsListingController.ControlsListingCallback> { + CallbackController<ControlsListingController.ControlsListingCallback>, + UserAwareController { fun getCurrentServices(): List<CandidateInfo> fun getAppLabel(name: ComponentName): CharSequence? = "" + @FunctionalInterface interface ControlsListingCallback { fun onServicesUpdated(list: List<CandidateInfo>) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt index 3949c5929a85..882382cc4ade 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -19,6 +19,7 @@ package com.android.systemui.controls.management import android.content.ComponentName import android.content.Context import android.content.pm.ServiceInfo +import android.os.UserHandle import android.service.controls.ControlsProviderService import android.util.Log import com.android.internal.annotations.VisibleForTesting @@ -31,6 +32,16 @@ import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton +private fun createServiceListing(context: Context): ServiceListing { + return ServiceListing.Builder(context).apply { + setIntentAction(ControlsProviderService.SERVICE_CONTROLS) + setPermission("android.permission.BIND_CONTROLS") + setNoun("Controls Provider") + setSetting("controls_providers") + setTag("controls_providers") + }.build() +} + /** * Provides a listing of components to be used as ControlsServiceProvider. * @@ -43,41 +54,55 @@ import javax.inject.Singleton class ControlsListingControllerImpl @VisibleForTesting constructor( private val context: Context, @Background private val backgroundExecutor: Executor, - private val serviceListing: ServiceListing + private val serviceListingBuilder: (Context) -> ServiceListing ) : ControlsListingController { @Inject constructor(context: Context, executor: Executor): this( context, executor, - ServiceListing.Builder(context) - .setIntentAction(ControlsProviderService.SERVICE_CONTROLS) - .setPermission("android.permission.BIND_CONTROLS") - .setNoun("Controls Provider") - .setSetting("controls_providers") - .setTag("controls_providers") - .build() + ::createServiceListing ) + private var serviceListing = serviceListingBuilder(context) + companion object { private const val TAG = "ControlsListingControllerImpl" } private var availableServices = emptyList<ServiceInfo>() - init { - serviceListing.addCallback { - Log.d(TAG, "ServiceConfig reloaded") - availableServices = it.toList() - - backgroundExecutor.execute { - callbacks.forEach { - it.onServicesUpdated(getCurrentServices()) - } + override var currentUserId = context.userId + private set + + private val serviceListingCallback = ServiceListing.Callback { + Log.d(TAG, "ServiceConfig reloaded") + availableServices = it.toList() + + backgroundExecutor.execute { + callbacks.forEach { + it.onServicesUpdated(getCurrentServices()) } } } + init { + serviceListing.addCallback(serviceListingCallback) + } + + override fun changeUser(newUser: UserHandle) { + backgroundExecutor.execute { + callbacks.clear() + availableServices = emptyList() + serviceListing.setListening(false) + serviceListing.removeCallback(serviceListingCallback) + currentUserId = newUser.identifier + val contextForUser = context.createContextAsUser(newUser, 0) + serviceListing = serviceListingBuilder(contextForUser) + serviceListing.addCallback(serviceListingCallback) + } + } + // All operations in background thread private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>() @@ -91,6 +116,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( */ override fun addCallback(listener: ControlsListingController.ControlsListingCallback) { backgroundExecutor.execute { + Log.d(TAG, "Subscribing callback") callbacks.add(listener) if (callbacks.size == 1) { serviceListing.setListening(true) @@ -108,6 +134,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( */ override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) { backgroundExecutor.execute { + Log.d(TAG, "Unsubscribing callback") callbacks.remove(listener) if (callbacks.size == 0) { serviceListing.setListening(false) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 69af516b4ac9..5ff949c98806 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -22,7 +22,10 @@ import android.os.Bundle import android.view.LayoutInflater import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.settings.CurrentUserTracker import com.android.systemui.util.LifecycleActivity import java.util.concurrent.Executor import javax.inject.Inject @@ -32,7 +35,9 @@ import javax.inject.Inject */ class ControlsProviderSelectorActivity @Inject constructor( @Main private val executor: Executor, - private val listingController: ControlsListingController + @Background private val backExecutor: Executor, + private val listingController: ControlsListingController, + broadcastDispatcher: BroadcastDispatcher ) : LifecycleActivity() { companion object { @@ -40,6 +45,16 @@ class ControlsProviderSelectorActivity @Inject constructor( } private lateinit var recyclerView: RecyclerView + private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { + private val startingUser = listingController.currentUserId + + override fun onUserSwitched(newUserId: Int) { + if (newUserId != startingUser) { + stopTracking() + finish() + } + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -50,6 +65,7 @@ class ControlsProviderSelectorActivity @Inject constructor( recyclerView.layoutManager = LinearLayoutManager(applicationContext) setContentView(recyclerView) + currentUserTracker.startTracking() } /** @@ -57,13 +73,17 @@ class ControlsProviderSelectorActivity @Inject constructor( * @param component a component name for a [ControlsProviderService] */ fun launchFavoritingActivity(component: ComponentName?) { - component?.let { - val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply { - putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it)) - putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + backExecutor.execute { + component?.let { + val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java) + .apply { + putExtra(ControlsFavoritingActivity.EXTRA_APP, + listingController.getAppLabel(it)) + putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + } + startActivity(intent) } - startActivity(intent) } } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index f793b3df92a4..7b541991088c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -38,6 +38,7 @@ import com.android.systemui.statusbar.StatusBarWindowBlurController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.people.PeopleHubModule; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.phone.KeyguardLiftController; @@ -64,6 +65,7 @@ import dagger.Provides; AssistModule.class, ConcurrencyModule.class, LogModule.class, + NotificationsModule.class, PeopleHubModule.class, }, subcomponents = {StatusBarComponent.class, NotificationRowComponent.class}) diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 43db85bd91ec..6f655bb0b209 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -118,6 +118,7 @@ public class DozeFactory { new DozeWallpaperState(mWallpaperManager, mBiometricUnlockController, mDozeParameters), new DozeDockHandler(config, machine, mDockManager), + new DozeSuppressedHandler(dozeService, config, machine), new DozeAuthRemover(dozeService) }); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 8afdf1aeb023..96ae6c9ec4a2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -266,6 +266,14 @@ public class DozeLog implements Dumpable { mLogger.logSensorTriggered(reason); } + /** + * Appends doze suppressed event to the logs + * @param suppressedState The {@link DozeMachine.State} that was suppressed + */ + public void traceDozeSuppressed(DozeMachine.State suppressedState) { + mLogger.logDozeSuppressed(suppressedState); + } + private class SummaryStats { private int mCount; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index 42decd592071..732745a1158b 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -194,6 +194,14 @@ class DozeLogger @Inject constructor( "Sensor triggered, type=${reasonToString(int1)}" }) } + + fun logDozeSuppressed(state: DozeMachine.State) { + buffer.log(TAG, INFO, { + str1 = state.name + }, { + "Doze state suppressed, state=$str1" + }) + } } private const val TAG = "DozeLog" diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 03c25eee4f6f..6e81d3a11098 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -102,6 +102,10 @@ public class DozeMachine { } } + boolean isAlwaysOn() { + return this == DOZE_AOD || this == DOZE_AOD_DOCKED; + } + int screenState(DozeParameters parameters) { switch (this) { case UNINITIALIZED: @@ -324,6 +328,11 @@ public class DozeMachine { if (mState == State.FINISH) { return State.FINISH; } + if (mConfig.dozeSuppressed(UserHandle.USER_CURRENT) && requestedState.isAlwaysOn()) { + Log.i(TAG, "Doze is suppressed. Suppressing state: " + requestedState); + mDozeLog.traceDozeSuppressed(requestedState); + return State.DOZE; + } if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING || mState == State.DOZE_AOD || mState == State.DOZE) && requestedState == State.DOZE_PULSE_DONE) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 3abeea91cdee..e50f1fb2a3ee 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -18,7 +18,6 @@ package com.android.systemui.doze; import static com.android.systemui.doze.DozeMachine.State.DOZE; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; -import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING; import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE; @@ -90,10 +89,10 @@ public class DozeScreenState implements DozeMachine.Part { } final boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState); - final boolean pulseEnding = oldState == DOZE_PULSE_DONE && isAlwaysOnState(newState); + final boolean pulseEnding = oldState == DOZE_PULSE_DONE && newState.isAlwaysOn(); final boolean turningOn = (oldState == DOZE_AOD_PAUSED || oldState == DOZE) - && isAlwaysOnState(newState); - final boolean turningOff = (isAlwaysOnState(oldState) && newState == DOZE) + && newState.isAlwaysOn(); + final boolean turningOff = (newState.isAlwaysOn() && newState == DOZE) || (oldState == DOZE_AOD_PAUSING && newState == DOZE_AOD_PAUSED); final boolean justInitialized = oldState == DozeMachine.State.INITIALIZED; if (messagePending || justInitialized || pulseEnding || turningOn) { @@ -132,10 +131,6 @@ public class DozeScreenState implements DozeMachine.Part { } } - private boolean isAlwaysOnState(DozeMachine.State state) { - return state == DOZE_AOD || state == DOZE_AOD_DOCKED; - } - private void applyPendingScreenState() { applyScreenState(mPendingScreenState); mPendingScreenState = Display.STATE_UNKNOWN; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java new file mode 100644 index 000000000000..3a5c1a0890f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.doze; + +import static java.util.Objects.requireNonNull; + +import android.app.ActivityManager; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.hardware.display.AmbientDisplayConfiguration; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +/** Handles updating the doze state when doze is suppressed. */ +public final class DozeSuppressedHandler implements DozeMachine.Part { + + private static final String TAG = DozeSuppressedHandler.class.getSimpleName(); + private static final boolean DEBUG = DozeService.DEBUG; + + private final ContentResolver mResolver; + private final AmbientDisplayConfiguration mConfig; + private final DozeMachine mMachine; + private final DozeSuppressedSettingObserver mSettingObserver; + private final Handler mHandler = new Handler(); + + public DozeSuppressedHandler(Context context, AmbientDisplayConfiguration config, + DozeMachine machine) { + this(context, config, machine, null); + } + + @VisibleForTesting + DozeSuppressedHandler(Context context, AmbientDisplayConfiguration config, DozeMachine machine, + DozeSuppressedSettingObserver observer) { + mResolver = context.getContentResolver(); + mConfig = requireNonNull(config); + mMachine = requireNonNull(machine); + if (observer == null) { + mSettingObserver = new DozeSuppressedSettingObserver(mHandler); + } else { + mSettingObserver = observer; + } + } + + @Override + public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { + switch (newState) { + case INITIALIZED: + mSettingObserver.register(); + break; + case FINISH: + mSettingObserver.unregister(); + break; + default: + // no-op + } + } + + /** + * Listens to changes to the DOZE_SUPPRESSED secure setting and updates the doze state + * accordingly. + */ + final class DozeSuppressedSettingObserver extends ContentObserver { + private boolean mRegistered; + + private DozeSuppressedSettingObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri, int userId) { + if (userId != ActivityManager.getCurrentUser()) { + return; + } + final DozeMachine.State nextState; + if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) + && !mConfig.dozeSuppressed(UserHandle.USER_CURRENT)) { + nextState = DozeMachine.State.DOZE_AOD; + } else { + nextState = DozeMachine.State.DOZE; + } + mMachine.requestState(nextState); + } + + void register() { + if (mRegistered) { + return; + } + mResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.SUPPRESS_DOZE), + false, this, UserHandle.USER_CURRENT); + Log.d(TAG, "Register"); + mRegistered = true; + } + + void unregister() { + if (!mRegistered) { + return; + } + mResolver.unregisterContentObserver(this); + Log.d(TAG, "Unregister"); + mRegistered = false; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index b9c056df89a1..305a4c870d91 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -127,6 +127,11 @@ public class DozeTriggers implements DozeMachine.Part { mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled"); return; } + if (mConfig.dozeSuppressed(UserHandle.USER_CURRENT)) { + runIfNotNull(onPulseSuppressedListener); + mDozeLog.tracePulseDropped("dozeSuppressed"); + return; + } requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */, onPulseSuppressedListener); mDozeLog.traceNotificationPulse(); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index dd1856a93d2e..c9c38d31e865 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -30,17 +30,17 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.android.internal.R; -import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.drawable.ScrimDrawable; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; import com.android.systemui.Dependency; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -59,6 +59,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, private final KeyguardStateController mKeyguardStateController; private final DeviceProvisionedController mDeviceProvisionedController; private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension; + private final BlurUtils mBlurUtils; private GlobalActionsPanelPlugin mPlugin; private final CommandQueue mCommandQueue; private GlobalActionsDialog mGlobalActionsDialog; @@ -68,13 +69,14 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, @Inject public GlobalActionsImpl(Context context, CommandQueue commandQueue, - Lazy<GlobalActionsDialog> globalActionsDialogLazy) { + Lazy<GlobalActionsDialog> globalActionsDialogLazy, BlurUtils blurUtils) { mContext = context; mGlobalActionsDialogLazy = globalActionsDialogLazy; mKeyguardStateController = Dependency.get(KeyguardStateController.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); mPluginManager = Dependency.get(PluginManager.class); mCommandQueue = commandQueue; + mBlurUtils = blurUtils; mCommandQueue.addCallback(this); mPanelExtension = Dependency.get(ExtensionController.class) .newExtension(GlobalActionsPanelPlugin.class) @@ -110,7 +112,6 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, @Override public void showShutdownUi(boolean isReboot, String reason) { ScrimDrawable background = new ScrimDrawable(); - background.setAlpha((int) (SHUTDOWN_SCRIM_ALPHA * 255)); Dialog d = new Dialog(mContext, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); @@ -160,8 +161,13 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, reasonView.setText(rebootReasonMessage); } - GradientColors colors = Dependency.get(SysuiColorExtractor.class).getNeutralColors(); - background.setColor(colors.getMainColor(), false); + if (mBlurUtils.supportsBlursOnWindows()) { + background.setAlpha((int) (ScrimController.GRADIENT_SCRIM_ALPHA_BUSY * 255)); + mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(), + mBlurUtils.radiusForRatio(1)); + } else { + background.setAlpha((int) (SHUTDOWN_SCRIM_ALPHA * 255)); + } d.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index 3fcd1c19370f..476af20b78f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -116,6 +116,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic @Override public void onClick(View v) { + if (!hasFooter()) return; mHandler.sendEmptyMessage(H.CLICK); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index e6082dddd6c7..e7e1ba8c28b4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -106,7 +106,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Initialize screenshot notification smart actions provider. mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false); + SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true); if (mSmartActionsEnabled) { mSmartActionsProvider = SystemUIFactory.getInstance() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt index 015c32348bb0..269a7a59f1b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt @@ -43,10 +43,10 @@ class BypassHeadsUpNotifier @Inject constructor( private val headsUpManager: HeadsUpManagerPhone, private val notificationLockscreenUserManager: NotificationLockscreenUserManager, private val mediaManager: NotificationMediaManager, + private val entryManager: NotificationEntryManager, tunerService: TunerService ) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener { - private lateinit var entryManager: NotificationEntryManager private var currentMediaEntry: NotificationEntry? = null private var enabled = true @@ -70,8 +70,7 @@ class BypassHeadsUpNotifier @Inject constructor( }, Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING) } - fun setUp(entryManager: NotificationEntryManager) { - this.entryManager = entryManager + fun setUp() { mediaManager.addCallback(this) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 8ac4d3008179..cb8cef8fdd72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -92,8 +92,8 @@ import javax.inject.Singleton; * {@link #addNotificationLifetimeExtender(NotifLifetimeExtender)}). * * Interested parties can register listeners - * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications are - * added, updated, or removed. + * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications + * events occur. */ @MainThread @Singleton @@ -230,6 +230,8 @@ public class NotifCollection implements Dumpable { entry = new NotificationEntry(sbn, ranking); mNotificationSet.put(sbn.getKey(), entry); + dispatchOnEntryInit(entry); + if (rankingMap != null) { applyRanking(rankingMap); } @@ -297,6 +299,7 @@ public class NotifCollection implements Dumpable { } dispatchOnEntryRemoved(entry, reason, dismissedByUserStats != null /* removedByUser */); + dispatchOnEntryCleanUp(entry); } rebuildList(); @@ -378,6 +381,14 @@ public class NotifCollection implements Dumpable { return ranking; } + private void dispatchOnEntryInit(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryInit(entry); + } + mAmDispatchingToOtherCode = false; + } + private void dispatchOnEntryAdded(NotificationEntry entry) { mAmDispatchingToOtherCode = true; for (NotifCollectionListener listener : mNotifCollectionListeners) { @@ -413,6 +424,14 @@ public class NotifCollection implements Dumpable { mAmDispatchingToOtherCode = false; } + private void dispatchOnEntryCleanUp(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryCleanUp(entry); + } + mAmDispatchingToOtherCode = false; + } + private final BatchableNotificationHandler mNotifHandler = new BatchableNotificationHandler() { @Override public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java index 0377f57a7a9c..9142388374e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java @@ -90,7 +90,8 @@ public class NotifPipeline { } /** - * Registers a listener to be informed when notifications are added, removed or updated. + * Registers a listener to be informed when there is a notification entry event such as an add, + * update, or remove. */ public void addCollectionListener(NotifCollectionListener listener) { mNotifCollection.addCollectionListener(listener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 6adcabd86fe6..ff6da12bd0bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -18,15 +18,25 @@ package com.android.systemui.statusbar.notification.collection.notifcollection; import android.service.notification.NotificationListenerService; -import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** - * Listener interface for {@link NotifCollection}. + * Listener interface for {@link NotificationEntry} events. */ public interface NotifCollectionListener { /** + * Called whenever a new {@link NotificationEntry} is initialized. This should be used for + * initializing any decorated state tied to the notification. + * + * Do not reference other registered {@link NotifCollectionListener} implementations here as + * there is no guarantee of order and they may not have had a chance to initialize yet. Instead, + * use {@link #onEntryAdded} which is called after all initialization. + */ + default void onEntryInit(NotificationEntry entry) { + } + + /** * Called whenever a notification with a new key is posted. */ default void onEntryAdded(NotificationEntry entry) { @@ -51,6 +61,18 @@ public interface NotifCollectionListener { } /** + * Called whenever a {@link NotificationEntry} is considered deleted. This should be used for + * cleaning up any state tied to the notification. + * + * This is the deletion parallel of {@link #onEntryInit} and similarly means that you cannot + * expect other {@link NotifCollectionListener} implementations to still have valid state for + * the entry during this call. Instead, use {@link #onEntryRemoved} which will be called before + * deletion. + */ + default void onEntryCleanUp(NotificationEntry entry) { + } + + /** * Called whenever the RankingMap is updated by system server. By the time this listener is * called, the Rankings of all entries will have been updated. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java new file mode 100644 index 000000000000..c7666e47d4b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.dagger; + +import android.content.Context; + +import com.android.systemui.R; +import com.android.systemui.statusbar.notification.init.NotificationsController; +import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; +import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; + +import javax.inject.Singleton; + +import dagger.Lazy; +import dagger.Module; +import dagger.Provides; + +/** Module for classes related to the notifications data pipeline */ +@Module +public class NotificationsModule { + /** Initializes the notification data pipeline (can be disabled via config). */ + @Singleton + @Provides + static NotificationsController provideNotificationsController( + Context context, + Lazy<NotificationsControllerImpl> realController, + Lazy<NotificationsControllerStub> stubController) { + if (context.getResources().getBoolean(R.bool.config_renderNotifications)) { + return realController.get(); + } else { + return stubController.get(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt new file mode 100644 index 000000000000..9da8b8a3fd92 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.init + +import android.service.notification.StatusBarNotification +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.StatusBar +import java.io.FileDescriptor +import java.io.PrintWriter + +/** + * The master controller for all notifications-related work + * + * Split into two implementations: [NotificationsControllerImpl] (most cases) and + * [NotificationsControllerStub] (for builds that disable notification rendering). + */ +interface NotificationsController { + fun initialize( + statusBar: StatusBar, + presenter: NotificationPresenter, + listContainer: NotificationListContainer, + notificationActivityStarter: NotificationActivityStarter, + bindRowCallback: NotificationRowBinderImpl.BindRowCallback + ) + + fun requestNotificationUpdate(reason: String) + fun resetUserExpandedStates() + fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) + fun getActiveNotificationsCount(): Int + fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) + fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>, dumpTruck: Boolean) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt new file mode 100644 index 000000000000..61e3192eba3c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.init + +import android.service.notification.StatusBarNotification +import com.android.systemui.bubbles.BubbleController +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption +import com.android.systemui.statusbar.FeatureFlags +import com.android.systemui.statusbar.NotificationListener +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.NotificationClicker +import com.android.systemui.statusbar.notification.NotificationEntryManager +import com.android.systemui.statusbar.notification.NotificationListController +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper +import com.android.systemui.statusbar.phone.NotificationGroupManager +import com.android.systemui.statusbar.phone.StatusBar +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.RemoteInputUriController +import dagger.Lazy +import java.io.FileDescriptor +import java.io.PrintWriter +import java.util.Optional +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Master controller for all notifications-related work + * + * At the moment exposes a number of event-handler-esque methods; these are for historical reasons. + * Once we migrate away from the need for such things, this class becomes primarily a place to do + * any initialization work that notifications require. + */ +@Singleton +class NotificationsControllerImpl @Inject constructor( + private val featureFlags: FeatureFlags, + private val notificationListener: NotificationListener, + private val entryManager: NotificationEntryManager, + private val newNotifPipeline: Lazy<NotifPipelineInitializer>, + private val deviceProvisionedController: DeviceProvisionedController, + private val notificationRowBinder: NotificationRowBinderImpl, + private val remoteInputUriController: RemoteInputUriController, + private val bubbleController: BubbleController, + private val groupManager: NotificationGroupManager, + private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper, + private val headsUpManager: HeadsUpManager +) : NotificationsController { + + override fun initialize( + statusBar: StatusBar, + presenter: NotificationPresenter, + listContainer: NotificationListContainer, + notificationActivityStarter: NotificationActivityStarter, + bindRowCallback: NotificationRowBinderImpl.BindRowCallback + ) { + notificationListener.registerAsSystemService() + + val listController = + NotificationListController( + entryManager, + listContainer, + deviceProvisionedController) + listController.bind() + + notificationRowBinder.setNotificationClicker( + NotificationClicker( + Optional.of(statusBar), + bubbleController, + notificationActivityStarter)) + notificationRowBinder.setUpWithPresenter( + presenter, + listContainer, + headsUpManager, + bindRowCallback) + + if (featureFlags.isNewNotifPipelineEnabled) { + newNotifPipeline.get().initialize(notificationListener, notificationRowBinder) + } + + if (featureFlags.isNewNotifPipelineRenderingEnabled) { + // TODO + } else { + notificationRowBinder.setInflationCallback(entryManager) + + remoteInputUriController.attach(entryManager) + groupAlertTransferHelper.bind(entryManager, groupManager) + headsUpManager.addListener(groupManager) + headsUpManager.addListener(groupAlertTransferHelper) + groupManager.setHeadsUpManager(headsUpManager) + groupAlertTransferHelper.setHeadsUpManager(headsUpManager) + + entryManager.attach(notificationListener) + } + } + + override fun dump( + fd: FileDescriptor, + pw: PrintWriter, + args: Array<String>, + dumpTruck: Boolean + ) { + if (dumpTruck) { + entryManager.dump(pw, " ") + } + groupManager.dump(fd, pw, args) + } + + // TODO: Convert all functions below this line into listeners instead of public methods + + override fun requestNotificationUpdate(reason: String) { + entryManager.updateNotifications(reason) + } + + override fun resetUserExpandedStates() { + for (entry in entryManager.visibleNotifications) { + entry.resetUserExpansion() + } + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) { + if (snoozeOption.snoozeCriterion != null) { + notificationListener.snoozeNotification(sbn.key, snoozeOption.snoozeCriterion.id) + } else { + notificationListener.snoozeNotification( + sbn.key, + snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong()) + } + } + + override fun getActiveNotificationsCount(): Int { + return entryManager.activeNotificationsCount + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) { + notificationListener.snoozeNotification( + sbn.key, + hoursToSnooze * 60 * 60 * 1000.toLong()) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt new file mode 100644 index 000000000000..ded855dd84f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.init + +import android.service.notification.StatusBarNotification +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption +import com.android.systemui.statusbar.NotificationListener +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.StatusBar +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject + +/** + * Implementation of [NotificationsController] that's used when notifications rendering is disabled. + */ +class NotificationsControllerStub @Inject constructor( + private val notificationListener: NotificationListener +) : NotificationsController { + + override fun initialize( + statusBar: StatusBar, + presenter: NotificationPresenter, + listContainer: NotificationListContainer, + notificationActivityStarter: NotificationActivityStarter, + bindRowCallback: NotificationRowBinderImpl.BindRowCallback + ) { + // Always connect the listener even if notification-handling is disabled. Being a listener + // grants special permissions and it's not clear if other things will break if we lose those + notificationListener.registerAsSystemService() + } + + override fun requestNotificationUpdate(reason: String) { + } + + override fun resetUserExpandedStates() { + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) { + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) { + } + + override fun getActiveNotificationsCount(): Int { + return 0 + } + + override fun dump( + fd: FileDescriptor, + pw: PrintWriter, + args: Array<String>, + dumpTruck: Boolean + ) { + pw.println() + pw.println("Notification handling disabled") + pw.println() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 1726b4884aff..11f70796748c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -168,12 +168,10 @@ import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.EmptyShadeView; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -191,15 +189,11 @@ import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationClicker; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; -import com.android.systemui.statusbar.notification.NotificationListController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -218,7 +212,6 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.volume.VolumeComponent; @@ -356,7 +349,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final Object mQueueLock = new Object(); - private final FeatureFlags mFeatureFlags; private final StatusBarIconController mIconController; private final PulseExpansionHandler mPulseExpansionHandler; private final NotificationWakeUpCoordinator mWakeUpCoordinator; @@ -365,7 +357,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final HeadsUpManagerPhone mHeadsUpManager; private final DynamicPrivacyController mDynamicPrivacyController; private final BypassHeadsUpNotifier mBypassHeadsUpNotifier; - private final Lazy<NotifPipelineInitializer> mNewNotifPipeline; private final FalsingManager mFalsingManager; private final BroadcastDispatcher mBroadcastDispatcher; private final ConfigurationController mConfigurationController; @@ -374,7 +365,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; private final Provider<StatusBarComponent.Builder> mStatusBarComponentBuilder; private final PluginManager mPluginManager; - private final RemoteInputUriController mRemoteInputUriController; private final Optional<Divider> mDividerOptional; private final StatusBarNotificationActivityStarter.Builder mStatusBarNotificationActivityStarterBuilder; @@ -387,8 +377,8 @@ public class StatusBar extends SystemUI implements DemoMode, private final KeyguardDismissUtil mKeyguardDismissUtil; private final ExtensionController mExtensionController; private final UserInfoControllerImpl mUserInfoControllerImpl; - private final NotificationRowBinderImpl mNotificationRowBinder; private final DismissCallbackRegistry mDismissCallbackRegistry; + private NotificationsController mNotificationsController; // expanded notifications // the sliding/resizing panel within the notification window @@ -412,8 +402,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; - private final NotificationEntryManager mEntryManager; - private NotificationListController mNotificationListController; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final NotificationViewHierarchyManager mViewHierarchyManager; private final KeyguardViewMediator mKeyguardViewMediator; @@ -589,7 +577,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onStrongAuthStateChanged(int userId) { super.onStrongAuthStateChanged(userId); - mEntryManager.updateNotifications("onStrongAuthStateChanged"); + mNotificationsController.requestNotificationUpdate("onStrongAuthStateChanged"); } }; private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); @@ -614,7 +602,7 @@ public class StatusBar extends SystemUI implements DemoMode, @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public StatusBar( Context context, - FeatureFlags featureFlags, + NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -626,13 +614,11 @@ public class StatusBar extends SystemUI implements DemoMode, HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NotifPipelineInitializer> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationEntryManager notificationEntryManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -653,12 +639,10 @@ public class StatusBar extends SystemUI implements DemoMode, VibratorHelper vibratorHelper, BubbleController bubbleController, NotificationGroupManager groupManager, - NotificationGroupAlertTransferHelper groupAlertTransferHelper, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, Lazy<AssistManager> assistManagerLazy, - NotificationListener notificationListener, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, LockscreenLockIconController lockscreenLockIconController, @@ -676,7 +660,6 @@ public class StatusBar extends SystemUI implements DemoMode, Optional<Recents> recentsOptional, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, - RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, LightsOutNotifController lightsOutNotifController, StatusBarNotificationActivityStarter.Builder @@ -692,10 +675,9 @@ public class StatusBar extends SystemUI implements DemoMode, KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry) { super(context); - mFeatureFlags = featureFlags; + mNotificationsController = notificationsController; mLightBarController = lightBarController; mAutoHideController = autoHideController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -707,13 +689,11 @@ public class StatusBar extends SystemUI implements DemoMode, mHeadsUpManager = headsUpManagerPhone; mDynamicPrivacyController = dynamicPrivacyController; mBypassHeadsUpNotifier = bypassHeadsUpNotifier; - mNewNotifPipeline = newNotifPipeline; mFalsingManager = falsingManager; mBroadcastDispatcher = broadcastDispatcher; mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler; mGutsManager = notificationGutsManager; mNotificationLogger = notificationLogger; - mEntryManager = notificationEntryManager; mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mViewHierarchyManager = notificationViewHierarchyManager; mKeyguardViewMediator = keyguardViewMediator; @@ -734,12 +714,10 @@ public class StatusBar extends SystemUI implements DemoMode, mVibratorHelper = vibratorHelper; mBubbleController = bubbleController; mGroupManager = groupManager; - mGroupAlertTransferHelper = groupAlertTransferHelper; mVisualStabilityManager = visualStabilityManager; mDeviceProvisionedController = deviceProvisionedController; mNavigationBarController = navigationBarController; mAssistManagerLazy = assistManagerLazy; - mNotificationListener = notificationListener; mConfigurationController = configurationController; mNotificationShadeWindowController = notificationShadeWindowController; mLockscreenLockIconController = lockscreenLockIconController; @@ -757,7 +735,6 @@ public class StatusBar extends SystemUI implements DemoMode, mRecentsOptional = recentsOptional; mStatusBarComponentBuilder = statusBarComponentBuilder; mPluginManager = pluginManager; - mRemoteInputUriController = remoteInputUriController; mDividerOptional = dividerOptional; mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder; mShadeController = shadeController; @@ -771,12 +748,11 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardDismissUtil = keyguardDismissUtil; mExtensionController = extensionController; mUserInfoControllerImpl = userInfoControllerImpl; - mNotificationRowBinder = notificationRowBinder; mDismissCallbackRegistry = dismissCallbackRegistry; mBubbleExpandListener = (isExpanding, key) -> { - mEntryManager.updateNotifications("onBubbleExpandChanged"); + mNotificationsController.requestNotificationUpdate("onBubbleExpandChanged"); updateScrimController(); }; @@ -786,11 +762,10 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void start() { - mNotificationListener.registerAsSystemService(); mScreenLifecycle.addObserver(mScreenObserver); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); mUiModeManager = mContext.getSystemService(UiModeManager.class); - mBypassHeadsUpNotifier.setUp(mEntryManager); + mBypassHeadsUpNotifier.setUp(); mBubbleController.setExpandListener(mBubbleExpandListener); mActivityIntentHelper = new ActivityIntentHelper(mContext); @@ -1060,12 +1035,8 @@ public class StatusBar extends SystemUI implements DemoMode, mConfigurationController.addCallback(mHeadsUpManager); mHeadsUpManager.addListener(this); mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener()); - mHeadsUpManager.addListener(mGroupManager); - mHeadsUpManager.addListener(mGroupAlertTransferHelper); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); - mGroupManager.setHeadsUpManager(mHeadsUpManager); - mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager); mNotificationLogger.setHeadsUpManager(mHeadsUpManager); createNavigationBar(result); @@ -1243,16 +1214,10 @@ public class StatusBar extends SystemUI implements DemoMode, mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController, mHeadsUpManager, mNotificationShadeWindowView, mStackScroller, mDozeScrimController, mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController, - mNotificationAlertingManager, mNotificationRowBinder, mKeyguardStateController, + mNotificationAlertingManager, mKeyguardStateController, mKeyguardIndicationController, this /* statusBar */, mShadeController, mCommandQueue, mInitController); - mNotificationListController = - new NotificationListController( - mEntryManager, - (NotificationListContainer) mStackScroller, - mDeviceProvisionedController); - mNotificationShelf.setOnActivatedListener(mPresenter); mRemoteInputManager.getController().addCallback(mNotificationShadeWindowController); @@ -1266,22 +1231,12 @@ public class StatusBar extends SystemUI implements DemoMode, mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); - if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - mNotificationRowBinder.setInflationCallback(mEntryManager); - } - - mRemoteInputUriController.attach(mEntryManager); - - mNotificationRowBinder.setNotificationClicker(new NotificationClicker( - Optional.of(this), mBubbleController, mNotificationActivityStarter)); - - mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); - mNotificationListController.bind(); - - if (mFeatureFlags.isNewNotifPipelineEnabled()) { - mNewNotifPipeline.get().initialize(mNotificationListener, mNotificationRowBinder); - } - mEntryManager.attach(mNotificationListener); + mNotificationsController.initialize( + this, + mPresenter, + (NotificationListContainer) mStackScroller, + mNotificationActivityStarter, + mPresenter); } /** @@ -1518,7 +1473,7 @@ public class StatusBar extends SystemUI implements DemoMode, * @param reason why we're requesting a notification update */ public void requestNotificationUpdate(String reason) { - mEntryManager.updateNotifications(reason); + mNotificationsController.requestNotificationUpdate(reason); } /** @@ -1726,7 +1681,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - mEntryManager.updateNotifications("onHeadsUpStateChanged"); + mNotificationsController.requestNotificationUpdate("onHeadsUpStateChanged"); if (mStatusBarStateController.isDozing() && isHeadsUp) { entry.setPulseSuppressed(false); mDozeServiceHost.fireNotificationPulse(entry); @@ -2485,11 +2440,9 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarKeyguardViewManager.dump(pw); } - if (DUMPTRUCK) { - synchronized (mEntryManager) { - mEntryManager.dump(pw, " "); - } + mNotificationsController.dump(fd, pw, args, DUMPTRUCK); + if (DUMPTRUCK) { if (false) { pw.println("see the logcat for a dump of the views we have created."); // must happen on ui thread @@ -2512,11 +2465,6 @@ public class StatusBar extends SystemUI implements DemoMode, } else { pw.println(" mHeadsUpManager: null"); } - if (mGroupManager != null) { - mGroupManager.dump(fd, pw, args); - } else { - pw.println(" mGroupManager: null"); - } if (mBubbleController != null) { mBubbleController.dump(fd, pw, args); @@ -2747,9 +2695,7 @@ public class StatusBar extends SystemUI implements DemoMode, }; public void resetUserExpandedStates() { - for (NotificationEntry entry : mEntryManager.getVisibleNotifications()) { - entry.resetUserExpansion(); - } + mNotificationsController.resetUserExpandedStates(); } private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen) { @@ -2855,7 +2801,7 @@ public class StatusBar extends SystemUI implements DemoMode, try { // consider the transition from peek to expanded to be a panel open, // but not one that clears notification effects. - int notificationLoad = mEntryManager.getActiveNotificationsCount(); + int notificationLoad = mNotificationsController.getActiveNotificationsCount(); mBarService.onPanelRevealed(false, notificationLoad); } catch (RemoteException ex) { // Won't fail unless the world has ended. @@ -2873,7 +2819,7 @@ public class StatusBar extends SystemUI implements DemoMode, !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mEntryManager.getActiveNotificationsCount(); + int notificationLoad = mNotificationsController.getActiveNotificationsCount(); if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { notificationLoad = 1; } @@ -3514,7 +3460,7 @@ public class StatusBar extends SystemUI implements DemoMode, updateQsExpansionEnabled(); mKeyguardViewMediator.setDozing(mDozing); - mEntryManager.updateNotifications("onDozingChanged"); + mNotificationsController.requestNotificationUpdate("onDozingChanged"); updateDozingState(); mDozeServiceHost.updateDozing(); updateScrimController(); @@ -3965,7 +3911,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected ViewGroup mStackScroller; private final NotificationGroupManager mGroupManager; - private final NotificationGroupAlertTransferHelper mGroupAlertTransferHelper; // handling reordering private final VisualStabilityManager mVisualStabilityManager; @@ -4032,21 +3977,12 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - private final NotificationListener mNotificationListener; - public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) { - if (snoozeOption.getSnoozeCriterion() != null) { - mNotificationListener.snoozeNotification(sbn.getKey(), - snoozeOption.getSnoozeCriterion().getId()); - } else { - mNotificationListener.snoozeNotification(sbn.getKey(), - snoozeOption.getMinutesToSnoozeFor() * 60 * 1000); - } + mNotificationsController.setNotificationSnoozed(sbn, snoozeOption); } public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) { - mNotificationListener.snoozeNotification(sbn.getKey(), - hoursToSnooze * 60 * 60 * 1000); + mNotificationsController.setNotificationSnoozed(sbn, hoursToSnooze); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java index 7615bf826287..15a0e08e285f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java @@ -46,9 +46,7 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -61,12 +59,10 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; @@ -77,7 +73,6 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.volume.VolumeComponent; @@ -105,7 +100,7 @@ public class StatusBarModule { @Singleton static StatusBar provideStatusBar( Context context, - FeatureFlags featureFlags, + NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -117,13 +112,11 @@ public class StatusBarModule { HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NotifPipelineInitializer> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationEntryManager notificationEntryManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -144,12 +137,10 @@ public class StatusBarModule { VibratorHelper vibratorHelper, BubbleController bubbleController, NotificationGroupManager groupManager, - NotificationGroupAlertTransferHelper groupAlertTransferHelper, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, Lazy<AssistManager> assistManagerLazy, - NotificationListener notificationListener, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, LockscreenLockIconController lockscreenLockIconController, @@ -167,7 +158,6 @@ public class StatusBarModule { Optional<Recents> recentsOptional, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, - RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, LightsOutNotifController lightsOutNotifController, StatusBarNotificationActivityStarter.Builder @@ -183,11 +173,10 @@ public class StatusBarModule { KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry) { return new StatusBar( context, - featureFlags, + notificationsController, lightBarController, autoHideController, keyguardUpdateMonitor, @@ -199,13 +188,11 @@ public class StatusBarModule { headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - newNotifPipeline, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, notificationGutsManager, notificationLogger, - notificationEntryManager, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, @@ -226,12 +213,10 @@ public class StatusBarModule { vibratorHelper, bubbleController, groupManager, - groupAlertTransferHelper, visualStabilityManager, deviceProvisionedController, navigationBarController, assistManagerLazy, - notificationListener, configurationController, notificationShadeWindowController, lockscreenLockIconController, @@ -249,7 +234,6 @@ public class StatusBarModule { recentsOptional, statusBarComponentBuilder, pluginManager, - remoteInputUriController, dividerOptional, lightsOutNotifController, statusBarNotificationActivityStarterBuilder, @@ -264,7 +248,6 @@ public class StatusBarModule { keyguardDismissUtil, extensionController, userInfoControllerImpl, - notificationRowBinder, dismissCallbackRegistry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 1336b2de82d3..2485513abba5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -141,7 +141,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, ActivityLaunchAnimator activityLaunchAnimator, DynamicPrivacyController dynamicPrivacyController, NotificationAlertingManager notificationAlertingManager, - NotificationRowBinderImpl notificationRowBinder, KeyguardStateController keyguardStateController, KeyguardIndicationController keyguardIndicationController, StatusBar statusBar, @@ -216,8 +215,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mEntryManager.addNotificationLifetimeExtender(mGutsManager); mEntryManager.addNotificationLifetimeExtenders( remoteInputManager.getLifetimeExtenders()); - notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager, - this); mNotificationInterruptionStateProvider.setUpWithPresenter( this, mHeadsUpManager, this::canHeadsUp); mLockscreenUserManager.setUpWithPresenter(this); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 5cfb4b39b16b..333b4a778621 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -274,7 +274,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testRemoveBubble_withDismissedNotif() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); @@ -317,7 +317,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertFalse(mBubbleController.isStackExpanded()); // Mark it as a bubble and add it explicitly - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // We should have bubbles & their notifs should not be suppressed @@ -347,8 +347,8 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testCollapseAfterChangingExpandedBubble() { // Mark it as a bubble and add it explicitly - mEntryListener.onNotificationAdded(mRow.getEntry()); - mEntryListener.onNotificationAdded(mRow2.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.updateBubble(mRow2.getEntry()); @@ -390,7 +390,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testExpansionRemovesShowInShadeAndDot() { // Mark it as a bubble and add it explicitly - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // We should have bubbles & their notifs should not be suppressed @@ -416,7 +416,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() { // Mark it as a bubble and add it explicitly - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // We should have bubbles & their notifs should not be suppressed @@ -452,8 +452,8 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testRemoveLastExpandedCollapses() { // Mark it as a bubble and add it explicitly - mEntryListener.onNotificationAdded(mRow.getEntry()); - mEntryListener.onNotificationAdded(mRow2.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.updateBubble(mRow2.getEntry()); verify(mBubbleStateChangeListener).onHasBubblesChanged(true); @@ -496,7 +496,7 @@ public class BubbleControllerTest extends SysuiTestCase { Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */); // Add the auto expand bubble - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // Expansion shouldn't change @@ -514,7 +514,7 @@ public class BubbleControllerTest extends SysuiTestCase { Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */); // Add the auto expand bubble - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // Expansion should change @@ -532,7 +532,7 @@ public class BubbleControllerTest extends SysuiTestCase { Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); // Add the suppress notif bubble - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // Notif should be suppressed because we were foreground @@ -576,7 +576,7 @@ public class BubbleControllerTest extends SysuiTestCase { public void testExpandStackAndSelectBubble_removedFirst() { final String key = mRow.getEntry().getKey(); - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // Simulate notification cancellation. @@ -588,7 +588,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testMarkNewNotificationAsShowInShade() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -598,7 +598,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testAddNotif_notBubble() { - mEntryListener.onNotificationAdded(mNonBubbleNotifRow.getEntry()); + mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry()); mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry()); verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean()); @@ -643,7 +643,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testRemoveBubble_succeeds_appCancel() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); @@ -658,7 +658,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void removeBubble_fails_clearAll() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); @@ -681,7 +681,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void removeBubble_fails_userDismissNotif() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); @@ -704,7 +704,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void removeBubble_succeeds_userDismissBubble_userDimissNotif() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); @@ -730,7 +730,7 @@ public class BubbleControllerTest extends SysuiTestCase { mock(BubbleController.NotificationSuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( @@ -753,7 +753,7 @@ public class BubbleControllerTest extends SysuiTestCase { mock(BubbleController.NotificationSuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt index 7c8c7c8f7be6..c1ebae735c7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.controls.controller import android.content.ComponentName import android.content.Context import android.os.Binder +import android.os.UserHandle import android.service.controls.Control import android.service.controls.DeviceTypes import android.testing.AndroidTestingRunner @@ -38,6 +39,7 @@ import org.mockito.Mockito import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -55,6 +57,9 @@ class ControlsBindingControllerTest : SysuiTestCase() { @Mock private lateinit var mockControlsController: ControlsController + private val user = UserHandle.of(mContext.userId) + private val otherUser = UserHandle.of(user.identifier + 1) + private val executor = FakeExecutor(FakeSystemClock()) private lateinit var controller: ControlsBindingController private val providers = TestableControlsBindingControllerImpl.providers @@ -75,6 +80,11 @@ class ControlsBindingControllerTest : SysuiTestCase() { } @Test + fun testStartOnUser() { + assertEquals(user.identifier, controller.currentUserId) + } + + @Test fun testBindAndLoad() { val callback: (List<Control>) -> Unit = {} controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback) @@ -145,6 +155,41 @@ class ControlsBindingControllerTest : SysuiTestCase() { verify(it).unsubscribe() } } + + @Test + fun testCurrentUserId() { + controller.changeUser(otherUser) + assertEquals(otherUser.identifier, controller.currentUserId) + } + + @Test + fun testChangeUsers_providersHaveCorrectUser() { + controller.bindServices(listOf(TEST_COMPONENT_NAME_1)) + controller.changeUser(otherUser) + controller.bindServices(listOf(TEST_COMPONENT_NAME_2)) + + val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 } + assertEquals(user, provider1.user) + val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 } + assertEquals(otherUser, provider2.user) + } + + @Test + fun testChangeUsers_providersUnbound() { + controller.bindServices(listOf(TEST_COMPONENT_NAME_1)) + controller.changeUser(otherUser) + + val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 } + verify(provider1).unbindService() + + controller.bindServices(listOf(TEST_COMPONENT_NAME_2)) + controller.changeUser(user) + + reset(provider1) + val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 } + verify(provider2).unbindService() + verify(provider1, never()).unbindService() + } } class TestableControlsBindingControllerImpl( @@ -157,12 +202,16 @@ class TestableControlsBindingControllerImpl( val providers = mutableSetOf<ControlsProviderLifecycleManager>() } + // Replaces the real provider with a mock and puts the mock in a visible set. + // The mock has the same componentName and user as the real one would have override fun createProviderManager(component: ComponentName): ControlsProviderLifecycleManager { + val realProvider = super.createProviderManager(component) val provider = mock(ControlsProviderLifecycleManager::class.java) val token = Binder() - `when`(provider.componentName).thenReturn(component) + `when`(provider.componentName).thenReturn(realProvider.componentName) `when`(provider.token).thenReturn(token) + `when`(provider.user).thenReturn(realProvider.user) providers.add(provider) return provider } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index be86a9c15e5f..897091f69f36 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -17,7 +17,12 @@ package com.android.systemui.controls.controller import android.app.PendingIntent +import android.content.BroadcastReceiver import android.content.ComponentName +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.os.UserHandle import android.provider.Settings import android.service.controls.Control import android.service.controls.DeviceTypes @@ -26,6 +31,8 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.DumpController import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.util.concurrency.FakeExecutor @@ -37,10 +44,11 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers -import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.`when` +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -61,18 +69,25 @@ class ControlsControllerImplTest : SysuiTestCase() { private lateinit var pendingIntent: PendingIntent @Mock private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper + @Mock + private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock + private lateinit var listingController: ControlsListingController @Captor private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>> @Captor private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit> + @Captor + private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> private lateinit var delayableExecutor: FakeExecutor private lateinit var controller: ControlsController companion object { fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() - fun <T : Any> safeEq(value: T): T = eq(value) ?: value + fun <T> eq(value: T): T = Mockito.eq(value) ?: value + fun <T> any(): T = Mockito.any<T>() private val TEST_COMPONENT = ComponentName("test.pkg", "test.class") private const val TEST_CONTROL_ID = "control1" @@ -89,24 +104,39 @@ class ControlsControllerImplTest : SysuiTestCase() { TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2) } + private val user = mContext.userId + private val otherUser = user + 1 + @Before fun setUp() { MockitoAnnotations.initMocks(this) Settings.Secure.putInt(mContext.contentResolver, ControlsControllerImpl.CONTROLS_AVAILABLE, 1) + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 1, otherUser) delayableExecutor = FakeExecutor(FakeSystemClock()) + val wrapper = object : ContextWrapper(mContext) { + override fun createContextAsUser(user: UserHandle, flags: Int): Context { + return baseContext + } + } + controller = ControlsControllerImpl( - mContext, + wrapper, delayableExecutor, uiController, bindingController, + listingController, + broadcastDispatcher, Optional.of(persistenceWrapper), dumpController ) assertTrue(controller.available) + verify(broadcastDispatcher).registerReceiver( + capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL)) } private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder { @@ -115,6 +145,11 @@ class ControlsControllerImplTest : SysuiTestCase() { } @Test + fun testStartOnUser() { + assertEquals(user, controller.currentUserId) + } + + @Test fun testStartWithoutFavorites() { assertTrue(controller.getFavoriteControls().isEmpty()) } @@ -127,6 +162,8 @@ class ControlsControllerImplTest : SysuiTestCase() { delayableExecutor, uiController, bindingController, + listingController, + broadcastDispatcher, Optional.of(persistenceWrapper), dumpController ) @@ -190,7 +227,7 @@ class ControlsControllerImplTest : SysuiTestCase() { controller.loadForComponent(TEST_COMPONENT) {} reset(persistenceWrapper) - verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.invoke(listOf(control)) @@ -262,7 +299,7 @@ class ControlsControllerImplTest : SysuiTestCase() { assertEquals(ControlStatus(control, false), controlStatus) } - verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.invoke(listOf(control)) @@ -287,7 +324,7 @@ class ControlsControllerImplTest : SysuiTestCase() { assertEquals(ControlStatus(control2, false), controlStatus2) } - verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.invoke(listOf(control, control2)) @@ -309,7 +346,7 @@ class ControlsControllerImplTest : SysuiTestCase() { assertTrue(controlStatus.removed) } - verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.invoke(emptyList()) @@ -325,7 +362,7 @@ class ControlsControllerImplTest : SysuiTestCase() { controller.loadForComponent(TEST_COMPONENT) {} - verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.invoke(listOf(control)) @@ -358,4 +395,26 @@ class ControlsControllerImplTest : SysuiTestCase() { controller.clearFavorites() assertTrue(controller.getFavoriteControls().isEmpty()) } -} + + @Test + fun testSwitchUsers() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + reset(persistenceWrapper) + val intent = Intent(Intent.ACTION_USER_SWITCHED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, otherUser) + } + val pendingResult = mock(BroadcastReceiver.PendingResult::class.java) + `when`(pendingResult.sendingUserId).thenReturn(otherUser) + broadcastReceiverCaptor.value.pendingResult = pendingResult + + broadcastReceiverCaptor.value.onReceive(mContext, intent) + + verify(persistenceWrapper).changeFile(any()) + verify(persistenceWrapper).readFavorites() + verify(bindingController).changeUser(UserHandle.of(otherUser)) + verify(listingController).changeUser(UserHandle.of(otherUser)) + assertTrue(controller.getFavoriteControls().isEmpty()) + assertEquals(otherUser, controller.currentUserId) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt index 4fc1cca76be6..3f1435be8b3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.controls.controller import android.content.ComponentName +import android.os.UserHandle import android.service.controls.Control import android.service.controls.IControlsActionCallback import android.service.controls.IControlsLoadCallback @@ -86,6 +87,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { loadCallback, actionCallback, subscriber, + UserHandle.of(0), componentName ) } @@ -148,4 +150,4 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { eq(actionCallback)) assertEquals(action, wrapperCaptor.getValue().getWrappedAction()) } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt index f09aab97a219..85e937e40acd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt @@ -17,12 +17,15 @@ package com.android.systemui.controls.management import android.content.ComponentName +import android.content.Context +import android.content.ContextWrapper import android.content.pm.ServiceInfo +import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.settingslib.applications.ServiceListing -import com.android.settingslib.widget.CandidateInfo import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import org.junit.After @@ -69,13 +72,22 @@ class ControlsListingControllerImplTest : SysuiTestCase() { private var serviceListingCallbackCaptor = ArgumentCaptor.forClass(ServiceListing.Callback::class.java) + private val user = mContext.userId + private val otherUser = user + 1 + @Before fun setUp() { MockitoAnnotations.initMocks(this) `when`(serviceInfo.componentName).thenReturn(componentName) - controller = ControlsListingControllerImpl(mContext, executor, mockSL) + val wrapper = object : ContextWrapper(mContext) { + override fun createContextAsUser(user: UserHandle, flags: Int): Context { + return baseContext + } + } + + controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }) verify(mockSL).addCallback(capture(serviceListingCallbackCaptor)) } @@ -86,6 +98,11 @@ class ControlsListingControllerImplTest : SysuiTestCase() { } @Test + fun testStartsOnUser() { + assertEquals(user, controller.currentUserId) + } + + @Test fun testNoServices_notListening() { assertTrue(controller.getCurrentServices().isEmpty()) } @@ -167,8 +184,9 @@ class ControlsListingControllerImplTest : SysuiTestCase() { controller.addCallback(mockCallbackOther) @Suppress("unchecked_cast") - val captor: ArgumentCaptor<List<CandidateInfo>> = - ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>> + val captor: ArgumentCaptor<List<ControlsServiceInfo>> = + ArgumentCaptor.forClass(List::class.java) + as ArgumentCaptor<List<ControlsServiceInfo>> executor.runAllReady() reset(mockCallback) @@ -185,4 +203,11 @@ class ControlsListingControllerImplTest : SysuiTestCase() { assertEquals(1, captor.value.size) assertEquals(componentName.flattenToString(), captor.value[0].key) } + + @Test + fun testChangeUser() { + controller.changeUser(UserHandle.of(otherUser)) + executor.runAllReady() + assertEquals(otherUser, controller.currentUserId) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java index 752e145d79bd..00d333fb593b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -53,6 +53,7 @@ public class DozeConfigurationUtil { when(config.pickupGestureEnabled(anyInt())).thenReturn(false); when(config.pulseOnNotificationEnabled(anyInt())).thenReturn(true); when(config.alwaysOnEnabled(anyInt())).thenReturn(false); + when(config.dozeSuppressed(anyInt())).thenReturn(false); when(config.enabled(anyInt())).thenReturn(true); when(config.getWakeLockScreenDebounce()).thenReturn(0L); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index f4cf314aa8fd..63cbca9255a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -139,6 +139,65 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testInitialize_dozeSuppressed_alwaysOnDisabled_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test + public void testInitialize_dozeSuppressed_alwaysOnEnabled_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test + public void testInitialize_dozeSuppressed_afterDocked_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mDockManager.isDocked()).thenReturn(true); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test + public void testInitialize_dozeSuppressed_alwaysOnDisabled_afterDockPaused_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); + when(mDockManager.isDocked()).thenReturn(true); + when(mDockManager.isHidden()).thenReturn(true); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test + public void testInitialize_dozeSuppressed_alwaysOnEnabled_afterDockPaused_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mDockManager.isDocked()).thenReturn(true); + when(mDockManager.isHidden()).thenReturn(true); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test public void testPulseDone_goesToDoze() { when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); mMachine.requestState(INITIALIZED); @@ -165,6 +224,20 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testPulseDone_dozeSuppressed_goesToSuppressed() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + mMachine.requestState(INITIALIZED); + mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); + mMachine.requestState(DOZE_PULSING); + + mMachine.requestState(DOZE_PULSE_DONE); + + verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test public void testPulseDone_afterDocked_goesToDockedAoD() { when(mDockManager.isDocked()).thenReturn(true); mMachine.requestState(INITIALIZED); @@ -178,6 +251,20 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testPulseDone_dozeSuppressed_afterDocked_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mDockManager.isDocked()).thenReturn(true); + mMachine.requestState(INITIALIZED); + mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); + mMachine.requestState(DOZE_PULSING); + + mMachine.requestState(DOZE_PULSE_DONE); + + verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test public void testPulseDone_afterDockPaused_goesToDoze() { when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); when(mDockManager.isDocked()).thenReturn(true); @@ -193,6 +280,22 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testPulseDone_dozeSuppressed_afterDockPaused_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mDockManager.isDocked()).thenReturn(true); + when(mDockManager.isHidden()).thenReturn(true); + mMachine.requestState(INITIALIZED); + mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); + mMachine.requestState(DOZE_PULSING); + + mMachine.requestState(DOZE_PULSE_DONE); + + verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test public void testFinished_staysFinished() { mMachine.requestState(INITIALIZED); mMachine.requestState(FINISH); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressedHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressedHandlerTest.java new file mode 100644 index 000000000000..5bdca76d449c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressedHandlerTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.doze; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.hardware.display.AmbientDisplayConfiguration; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.doze.DozeSuppressedHandler.DozeSuppressedSettingObserver; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class DozeSuppressedHandlerTest extends SysuiTestCase { + @Mock private DozeMachine mMachine; + @Mock private DozeSuppressedSettingObserver mObserver; + private AmbientDisplayConfiguration mConfig; + private DozeSuppressedHandler mSuppressedHandler; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mConfig = DozeConfigurationUtil.createMockConfig(); + mSuppressedHandler = new DozeSuppressedHandler(mContext, mConfig, mMachine, mObserver); + } + + @Test + public void transitionTo_initialized_registersObserver() throws Exception { + mSuppressedHandler.transitionTo(DozeMachine.State.UNINITIALIZED, + DozeMachine.State.INITIALIZED); + + verify(mObserver).register(); + } + + @Test + public void transitionTo_finish_unregistersObserver() throws Exception { + mSuppressedHandler.transitionTo(DozeMachine.State.INITIALIZED, + DozeMachine.State.FINISH); + + verify(mObserver).unregister(); + } + + @Test + public void transitionTo_doze_doesNothing() throws Exception { + mSuppressedHandler.transitionTo(DozeMachine.State.INITIALIZED, + DozeMachine.State.DOZE); + + verify(mObserver, never()).register(); + verify(mObserver, never()).unregister(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java index 47933ba9fdaa..e58a3a6bf5d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java @@ -16,8 +16,11 @@ package com.android.systemui.qs; import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -447,6 +450,21 @@ public class QSSecurityFooterTest extends SysuiTestCase { view.findViewById(R.id.vpn_subtitle).getVisibility()); } + @Test + public void testNoClickWhenGone() { + QSTileHost mockHost = mock(QSTileHost.class); + mFooter.setHostEnvironment(mockHost); + mFooter.refreshState(); + + TestableLooper.get(this).processAllMessages(); + + assertFalse(mFooter.hasFooter()); + mFooter.onClick(mFooter.getView()); + + // Proxy for dialog being created + verify(mockHost, never()).collapsePanels(); + } + private CharSequence addLink(CharSequence description) { final SpannableStringBuilder message = new SpannableStringBuilder(); message.append(description); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 9a7e97b5d55a..b65298097ff1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -139,9 +139,10 @@ public class NotifCollectionTest extends SysuiTestCase { .setRank(4747)); // THEN the listener is notified - verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture()); - + verify(mCollectionListener).onEntryInit(mEntryCaptor.capture()); NotificationEntry entry = mEntryCaptor.getValue(); + + verify(mCollectionListener).onEntryAdded(entry); assertEquals(notif1.key, entry.getKey()); assertEquals(notif1.sbn, entry.getSbn()); assertEquals(notif1.ranking, entry.getRanking()); @@ -236,6 +237,7 @@ public class NotifCollectionTest extends SysuiTestCase { // THEN the listener is notified verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL, false); + verify(mCollectionListener).onEntryCleanUp(entry); assertEquals(notif.sbn, entry.getSbn()); assertEquals(notif.ranking, entry.getRanking()); } @@ -606,6 +608,10 @@ public class NotifCollectionTest extends SysuiTestCase { private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>(); @Override + public void onEntryInit(NotificationEntry entry) { + } + + @Override public void onEntryAdded(NotificationEntry entry) { mLastSeenEntries.put(entry.getKey(), entry); } @@ -618,6 +624,10 @@ public class NotifCollectionTest extends SysuiTestCase { public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) { } + @Override + public void onEntryCleanUp(NotificationEntry entry) { + } + public NotificationEntry getEntry(String key) { if (!mLastSeenEntries.containsKey(key)) { throw new RuntimeException("Key not found: " + key); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index dd896be0e120..b9d2d229cd69 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -54,7 +54,6 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -113,10 +112,9 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { notificationShadeWindowView, mock(NotificationListContainerViewGroup.class), mock(DozeScrimController.class), mock(ScrimController.class), mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class), - mock(NotificationAlertingManager.class), - mock(NotificationRowBinderImpl.class), mock(KeyguardStateController.class), - mock(KeyguardIndicationController.class), - mStatusBar, mock(ShadeControllerImpl.class), mCommandQueue, mInitController); + mock(NotificationAlertingManager.class), mock(KeyguardStateController.class), + mock(KeyguardIndicationController.class), mStatusBar, + mock(ShadeControllerImpl.class), mCommandQueue, mInitController); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index e90e398ba061..db17a6e106b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -95,7 +95,6 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.NotificationListener; @@ -120,8 +119,7 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; @@ -133,10 +131,8 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.volume.VolumeComponent; @@ -149,7 +145,6 @@ import org.mockito.MockitoAnnotations; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Optional; import javax.inject.Provider; @@ -166,7 +161,7 @@ public class StatusBarTest extends SysuiTestCase { private PowerManager mPowerManager; private TestableNotificationInterruptionStateProvider mNotificationInterruptionStateProvider; - @Mock private FeatureFlags mFeatureFlags; + @Mock private NotificationsController mNotificationsController; @Mock private LightBarController mLightBarController; @Mock private StatusBarIconController mStatusBarIconController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -180,7 +175,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private IDreamManager mDreamManager; @Mock private ScrimController mScrimController; @Mock private DozeScrimController mDozeScrimController; - @Mock private ArrayList<NotificationEntry> mNotificationList; @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; @Mock private BiometricUnlockController mBiometricUnlockController; @Mock private NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor; @@ -190,7 +184,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private RemoteInputController mRemoteInputController; - @Mock private RemoteInputUriController mRemoteInputUriController; @Mock private StatusBarStateControllerImpl mStatusBarStateController; @Mock private BatteryController mBatteryController; @Mock private DeviceProvisionedController mDeviceProvisionedController; @@ -214,8 +207,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator; @Mock private KeyguardBypassController mKeyguardBypassController; @Mock private DynamicPrivacyController mDynamicPrivacyController; - @Mock private NotifPipelineInitializer mNewNotifPipeline; - @Mock private ZenModeController mZenModeController; @Mock private AutoHideController mAutoHideController; @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager; @Mock private UserSwitcherController mUserSwitcherController; @@ -223,7 +214,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private VibratorHelper mVibratorHelper; @Mock private BubbleController mBubbleController; @Mock private NotificationGroupManager mGroupManager; - @Mock private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper; @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Mock private NotificationIconAreaController mNotificationIconAreaController; @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController; @@ -247,7 +237,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private ViewMediatorCallback mViewMediatorCallback; @Mock private DismissCallbackRegistry mDismissCallbackRegistry; @Mock private ScreenPinningRequest mScreenPinningRequest; - @Mock private NotificationEntryManager mEntryManager; @Mock private LockscreenLockIconController mLockscreenLockIconController; @Mock private StatusBarNotificationActivityStarter.Builder mStatusBarNotificationActivityStarterBuilder; @@ -256,7 +245,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private KeyguardDismissUtil mKeyguardDismissUtil; @Mock private ExtensionController mExtensionController; @Mock private UserInfoControllerImpl mUserInfoControllerImpl; - @Mock private NotificationRowBinderImpl mNotificationRowBinder; private ShadeController mShadeController; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private InitController mInitController = new InitController(); @@ -280,7 +268,7 @@ public class StatusBarTest extends SysuiTestCase { mMetricsLogger = new FakeMetricsLogger(); NotificationLogger notificationLogger = new NotificationLogger(mNotificationListener, - mUiBgExecutor, mEntryManager, mStatusBarStateController, + mUiBgExecutor, mock(NotificationEntryManager.class), mStatusBarStateController, mExpansionStateLogger); notificationLogger.setVisibilityReporter(mock(Runnable.class)); @@ -334,7 +322,7 @@ public class StatusBarTest extends SysuiTestCase { mStatusBar = new StatusBar( mContext, - mFeatureFlags, + mNotificationsController, mLightBarController, mAutoHideController, mKeyguardUpdateMonitor, @@ -346,7 +334,6 @@ public class StatusBarTest extends SysuiTestCase { mHeadsUpManager, mDynamicPrivacyController, mBypassHeadsUpNotifier, - () -> mNewNotifPipeline, new FalsingManagerFake(), mBroadcastDispatcher, new RemoteInputQuickSettingsDisabler( @@ -356,7 +343,6 @@ public class StatusBarTest extends SysuiTestCase { ), mNotificationGutsManager, notificationLogger, - mEntryManager, mNotificationInterruptionStateProvider, mNotificationViewHierarchyManager, mKeyguardViewMediator, @@ -377,12 +363,10 @@ public class StatusBarTest extends SysuiTestCase { mVibratorHelper, mBubbleController, mGroupManager, - mGroupAlertTransferHelper, mVisualStabilityManager, mDeviceProvisionedController, mNavigationBarController, () -> mAssistManager, - mNotificationListener, configurationController, mNotificationShadeWindowController, mLockscreenLockIconController, @@ -399,7 +383,6 @@ public class StatusBarTest extends SysuiTestCase { Optional.of(mRecents), mStatusBarComponentBuilderProvider, mPluginManager, - mRemoteInputUriController, Optional.of(mDivider), mLightsOutNotifController, mStatusBarNotificationActivityStarterBuilder, @@ -414,7 +397,6 @@ public class StatusBarTest extends SysuiTestCase { mKeyguardDismissUtil, mExtensionController, mUserInfoControllerImpl, - mNotificationRowBinder, mDismissCallbackRegistry); when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn( @@ -675,7 +657,7 @@ public class StatusBarTest extends SysuiTestCase { public void testPanelOpenForHeadsUp() { when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); - when(mEntryManager.getActiveNotificationsCount()).thenReturn(5); + when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(true); mStatusBar.setBarStateForTest(StatusBarState.SHADE); @@ -693,7 +675,7 @@ public class StatusBarTest extends SysuiTestCase { @Test public void testPanelOpenAndClear() { when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - when(mEntryManager.getActiveNotificationsCount()).thenReturn(5); + when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); mStatusBar.setBarStateForTest(StatusBarState.SHADE); @@ -712,7 +694,7 @@ public class StatusBarTest extends SysuiTestCase { @Test public void testPanelOpenAndNoClear() { when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - when(mEntryManager.getActiveNotificationsCount()).thenReturn(5); + when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD); diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java index 7dc5c5f2db8a..020b32adcfd7 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java @@ -77,8 +77,8 @@ public class TetheringService extends Service { mLog.mark("onCreate"); mDeps = getTetheringDependencies(); mContext = mDeps.getContext(); - mTethering = makeTethering(mDeps); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mTethering = makeTethering(mDeps); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java index 1fe162c86408..5d170d34d77d 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -18,9 +18,17 @@ package com.android.server.accessibility.gestures; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD; @@ -110,6 +118,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mGestures.add(new Swipe(context, UP, DOWN, GESTURE_SWIPE_UP_AND_DOWN, this)); mGestures.add(new Swipe(context, UP, LEFT, GESTURE_SWIPE_UP_AND_LEFT, this)); mGestures.add(new Swipe(context, UP, RIGHT, GESTURE_SWIPE_UP_AND_RIGHT, this)); + // Set up multi-finger gestures to be enabled later. // Two-finger taps. mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP, this)); @@ -124,6 +133,24 @@ class GestureManifold implements GestureMatcher.StateChangeListener { new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this)); mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this)); + // Two-finger swipes. + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 2, DOWN, GESTURE_2_FINGER_SWIPE_DOWN, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 2, LEFT, GESTURE_2_FINGER_SWIPE_LEFT, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 2, RIGHT, GESTURE_2_FINGER_SWIPE_RIGHT, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 2, UP, GESTURE_2_FINGER_SWIPE_UP, this)); + // Three-finger swipes. + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 3, DOWN, GESTURE_3_FINGER_SWIPE_DOWN, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 3, LEFT, GESTURE_3_FINGER_SWIPE_LEFT, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 3, RIGHT, GESTURE_3_FINGER_SWIPE_RIGHT, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 3, UP, GESTURE_3_FINGER_SWIPE_UP, this)); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java index 0f5dd08e02b4..ac6748089314 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureUtils.java @@ -8,6 +8,9 @@ import android.view.MotionEvent; */ public final class GestureUtils { + public static int MM_PER_CM = 10; + public static float CM_PER_INCH = 2.54f; + private GestureUtils() { /* cannot be instantiated */ } @@ -85,4 +88,12 @@ public final class GestureUtils { return true; } + + /** + * Gets the index of the pointer that went up or down from a motion event. + */ + public static int getActionIndex(MotionEvent event) { + return (event.getAction() + & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + } } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java new file mode 100644 index 000000000000..8249239e3602 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.gestures; + +import static android.view.MotionEvent.INVALID_POINTER_ID; + +import static com.android.server.accessibility.gestures.GestureUtils.MM_PER_CM; +import static com.android.server.accessibility.gestures.GestureUtils.getActionIndex; +import static com.android.server.accessibility.gestures.Swipe.CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS; +import static com.android.server.accessibility.gestures.Swipe.CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS; +import static com.android.server.accessibility.gestures.Swipe.GESTURE_CONFIRM_CM; +import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG; + +import android.content.Context; +import android.graphics.PointF; +import android.os.Handler; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * This class is responsible for matching one-finger swipe gestures. Each instance matches one swipe + * gesture. A swipe is specified as a series of one or more directions e.g. left, left and up, etc. + * At this time swipes with more than two directions are not supported. + */ +class MultiFingerSwipe extends GestureMatcher { + + // Direction constants. + public static final int LEFT = 0; + public static final int RIGHT = 1; + public static final int UP = 2; + public static final int DOWN = 3; + // This is the calculated movement threshold used track if the user is still + // moving their finger. + private final float mGestureDetectionThresholdPixels; + + // Buffer for storing points for gesture detection. + private final ArrayList<PointF>[] mStrokeBuffers; + + // The swipe direction for this matcher. + private int mDirection; + private int[] mPointerIds; + // The starting point of each finger's path in the gesture. + private PointF[] mBase; + // The most recent entry in each finger's gesture path. + private PointF[] mPreviousGesturePoint; + private int mTargetFingerCount; + private int mCurrentFingerCount; + // Whether the appropriate number of fingers have gone down at some point. This is reset only on + // clear. + private boolean mTargetFingerCountReached = false; + // Constants for sampling motion event points. + // We sample based on a minimum distance between points, primarily to improve accuracy by + // reducing noisy minor changes in direction. + private static final float MIN_CM_BETWEEN_SAMPLES = 0.25f; + private final float mMinPixelsBetweenSamplesX; + private final float mMinPixelsBetweenSamplesY; + // The minmimum distance the finger must travel before we evaluate the initial direction of the + // swipe. + // Anything less is still considered a touch. + private int mTouchSlop; + + MultiFingerSwipe( + Context context, + int fingerCount, + int direction, + int gesture, + GestureMatcher.StateChangeListener listener) { + super(gesture, new Handler(context.getMainLooper()), listener); + mTargetFingerCount = fingerCount; + mPointerIds = new int[mTargetFingerCount]; + mBase = new PointF[mTargetFingerCount]; + mPreviousGesturePoint = new PointF[mTargetFingerCount]; + mStrokeBuffers = new ArrayList[mTargetFingerCount]; + mDirection = direction; + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + mGestureDetectionThresholdPixels = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, MM_PER_CM, displayMetrics) + * GESTURE_CONFIRM_CM; + // Calculate minimum gesture velocity + final float pixelsPerCmX = displayMetrics.xdpi / GestureUtils.CM_PER_INCH; + final float pixelsPerCmY = displayMetrics.ydpi / GestureUtils.CM_PER_INCH; + mMinPixelsBetweenSamplesX = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmX; + mMinPixelsBetweenSamplesY = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmY; + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + clear(); + } + + @Override + protected void clear() { + mTargetFingerCountReached = false; + mCurrentFingerCount = 0; + for (int i = 0; i < mTargetFingerCount; ++i) { + mPointerIds[i] = INVALID_POINTER_ID; + if (mBase[i] == null) { + mBase[i] = new PointF(); + } + mBase[i].x = Float.NaN; + mBase[i].y = Float.NaN; + if (mPreviousGesturePoint[i] == null) { + mPreviousGesturePoint[i] = new PointF(); + } + mPreviousGesturePoint[i].x = Float.NaN; + mPreviousGesturePoint[i].y = Float.NaN; + if (mStrokeBuffers[i] == null) { + mStrokeBuffers[i] = new ArrayList<>(100); + } + mStrokeBuffers[i].clear(); + } + super.clear(); + } + + @Override + protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mCurrentFingerCount > 0) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + mCurrentFingerCount = 1; + final int actionIndex = getActionIndex(rawEvent); + final int pointerId = rawEvent.getPointerId(actionIndex); + int pointerIndex = rawEvent.getPointerCount() - 1; + if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) { + // Nonsensical pointer id. + cancelGesture(event, rawEvent, policyFlags); + return; + } + if (mPointerIds[pointerIndex] != INVALID_POINTER_ID) { + // Inconsistent event stream. + cancelGesture(event, rawEvent, policyFlags); + return; + } + mPointerIds[pointerIndex] = pointerId; + cancelAfterPauseThreshold(event, rawEvent, policyFlags); + if (Float.isNaN(mBase[pointerIndex].x) && Float.isNaN(mBase[pointerIndex].y)) { + final float x = rawEvent.getX(actionIndex); + final float y = rawEvent.getY(actionIndex); + if (x < 0f || y < 0f) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + mBase[pointerIndex].x = x; + mBase[pointerIndex].y = y; + mPreviousGesturePoint[pointerIndex].x = x; + mPreviousGesturePoint[pointerIndex].y = y; + } else { + // This event doesn't make sense in the middle of a gesture. + cancelGesture(event, rawEvent, policyFlags); + return; + } + } + + @Override + protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (event.getPointerCount() > mTargetFingerCount) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + mCurrentFingerCount += 1; + if (mCurrentFingerCount != rawEvent.getPointerCount()) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + if (mCurrentFingerCount == mTargetFingerCount) { + mTargetFingerCountReached = true; + } + final int actionIndex = getActionIndex(rawEvent); + final int pointerId = rawEvent.getPointerId(actionIndex); + if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) { + // Nonsensical pointer id. + cancelGesture(event, rawEvent, policyFlags); + return; + } + int pointerIndex = mCurrentFingerCount - 1; + if (mPointerIds[pointerIndex] != INVALID_POINTER_ID) { + // Inconsistent event stream. + cancelGesture(event, rawEvent, policyFlags); + return; + } + mPointerIds[pointerIndex] = pointerId; + cancelAfterPauseThreshold(event, rawEvent, policyFlags); + if (Float.isNaN(mBase[pointerIndex].x) && Float.isNaN(mBase[pointerIndex].y)) { + final float x = rawEvent.getX(actionIndex); + final float y = rawEvent.getY(actionIndex); + if (x < 0f || y < 0f) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + mBase[pointerIndex].x = x; + mBase[pointerIndex].y = y; + mPreviousGesturePoint[pointerIndex].x = x; + mPreviousGesturePoint[pointerIndex].y = y; + } else { + cancelGesture(event, rawEvent, policyFlags); + return; + } + } + + @Override + protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (!mTargetFingerCountReached) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + mCurrentFingerCount -= 1; + final int actionIndex = getActionIndex(event); + final int pointerId = event.getPointerId(actionIndex); + if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) { + // Nonsensical pointer id. + cancelGesture(event, rawEvent, policyFlags); + return; + } + final int pointerIndex = Arrays.binarySearch(mPointerIds, pointerId); + if (pointerIndex < 0) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + final float x = rawEvent.getX(actionIndex); + final float y = rawEvent.getY(actionIndex); + if (x < 0f || y < 0f) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + final float dX = Math.abs(x - mPreviousGesturePoint[pointerIndex].x); + final float dY = Math.abs(y - mPreviousGesturePoint[pointerIndex].y); + if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) { + mStrokeBuffers[pointerIndex].add(new PointF(x, y)); + } + // We will evaluate all the paths on ACTION_UP. + } + + @Override + protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + for (int pointerIndex = 0; pointerIndex < rawEvent.getPointerCount(); ++pointerIndex) { + if (DEBUG) { + Slog.d(getGestureName(), "Processing move on finger " + pointerIndex); + } + int index = rawEvent.findPointerIndex(mPointerIds[pointerIndex]); + final float x = rawEvent.getX(index); + final float y = rawEvent.getY(index); + if (x < 0f || y < 0f) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + final float dX = Math.abs(x - mPreviousGesturePoint[pointerIndex].x); + final float dY = Math.abs(y - mPreviousGesturePoint[pointerIndex].y); + final double moveDelta = + Math.hypot( + Math.abs(x - mBase[pointerIndex].x), + Math.abs(y - mBase[pointerIndex].y)); + if (DEBUG) { + Slog.d( + getGestureName(), + "moveDelta:" + + Double.toString(moveDelta) + + " mGestureDetectionThreshold: " + + Float.toString(mGestureDetectionThresholdPixels)); + } + if (getState() == STATE_CLEAR) { + if (moveDelta < mTouchSlop) { + // This still counts as a touch not a swipe. + continue; + } else if (mStrokeBuffers[pointerIndex].size() == 0) { + // First, make sure we have the right number of fingers down. + if (mCurrentFingerCount != mTargetFingerCount) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + // Then, make sure the pointer is going in the right direction. + int direction = + toDirection(x - mBase[pointerIndex].x, y - mBase[pointerIndex].y); + if (direction != mDirection) { + cancelGesture(event, rawEvent, policyFlags); + return; + } else { + // This is confirmed to be some kind of swipe so start tracking points. + cancelAfterPauseThreshold(event, rawEvent, policyFlags); + for (int i = 0; i < mTargetFingerCount; ++i) { + mStrokeBuffers[i].add(new PointF(mBase[i])); + } + } + } + if (moveDelta > mGestureDetectionThresholdPixels) { + // Try to cancel if the finger starts to go the wrong way. + // Note that this only works because this matcher assumes one direction. + int direction = + toDirection(x - mBase[pointerIndex].x, y - mBase[pointerIndex].y); + if (direction != mDirection) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + // If the pointer has moved more than the threshold, + // update the stored values. + mBase[pointerIndex].x = x; + mBase[pointerIndex].y = y; + mPreviousGesturePoint[pointerIndex].x = x; + mPreviousGesturePoint[pointerIndex].y = y; + if (getState() == STATE_CLEAR) { + startGesture(event, rawEvent, policyFlags); + cancelAfterPauseThreshold(event, rawEvent, policyFlags); + } + } + } + if (getState() == STATE_GESTURE_STARTED) { + if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) { + // Sample every 2.5 MM in order to guard against minor variations in path. + mPreviousGesturePoint[pointerIndex].x = x; + mPreviousGesturePoint[pointerIndex].y = y; + mStrokeBuffers[pointerIndex].add(new PointF(x, y)); + cancelAfterPauseThreshold(event, rawEvent, policyFlags); + } + } + } + } + + @Override + protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (getState() != STATE_GESTURE_STARTED) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + mCurrentFingerCount = 0; + final int actionIndex = getActionIndex(event); + final int pointerId = event.getPointerId(actionIndex); + final int pointerIndex = Arrays.binarySearch(mPointerIds, pointerId); + if (pointerIndex < 0) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + final float x = rawEvent.getX(actionIndex); + final float y = rawEvent.getY(actionIndex); + if (x < 0f || y < 0f) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + final float dX = Math.abs(x - mPreviousGesturePoint[pointerIndex].x); + final float dY = Math.abs(y - mPreviousGesturePoint[pointerIndex].y); + if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) { + mStrokeBuffers[pointerIndex].add(new PointF(x, y)); + } + recognizeGesture(event, rawEvent, policyFlags); + } + + /** + * queues a transition to STATE_GESTURE_CANCEL based on the current state. If we have + * transitioned to STATE_GESTURE_STARTED the delay is longer. + */ + private void cancelAfterPauseThreshold( + MotionEvent event, MotionEvent rawEvent, int policyFlags) { + cancelPendingTransitions(); + switch (getState()) { + case STATE_CLEAR: + cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS, event, rawEvent, policyFlags); + break; + case STATE_GESTURE_STARTED: + cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS, event, rawEvent, policyFlags); + break; + default: + break; + } + } + /** + * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then transitions + * to the complete or cancel state depending on the result. + */ + private void recognizeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // Check the path of each finger against the specified direction. + // Note that we sample every 2.5 MMm, and the direction matching is extremely tolerant (each + // direction has a 90-degree arch of tolerance) meaning that minor perpendicular movements + // should not create false negatives. + for (int i = 0; i < mTargetFingerCount; ++i) { + if (DEBUG) { + Slog.d(getGestureName(), "Recognizing finger: " + i); + } + if (mStrokeBuffers[i].size() < 2) { + Slog.d(getGestureName(), "Too few points."); + cancelGesture(event, rawEvent, policyFlags); + return; + } + ArrayList<PointF> path = mStrokeBuffers[i]; + + if (DEBUG) { + Slog.d(getGestureName(), "path=" + path.toString()); + } + // Classify line segments, and call Listener callbacks. + if (!recognizeGesturePath(event, rawEvent, policyFlags, path)) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + } + // If we reach this point then all paths match. + completeGesture(event, rawEvent, policyFlags); + } + + /** + * Tests the path of a given finger against the direction specified in this matcher. + * + * @return True if the path matches the specified direction for this matcher, otherwise false. + */ + private boolean recognizeGesturePath( + MotionEvent event, MotionEvent rawEvent, int policyFlags, ArrayList<PointF> path) { + + final int displayId = event.getDisplayId(); + for (int i = 0; i < path.size() - 1; ++i) { + PointF start = path.get(i); + PointF end = path.get(i + 1); + + float dX = end.x - start.x; + float dY = end.y - start.y; + int direction = toDirection(dX, dY); + if (direction != mDirection) { + if (DEBUG) { + Slog.d( + getGestureName(), + "Found direction " + + directionToString(direction) + + " when expecting " + + directionToString(mDirection)); + } + return false; + } + } + if (DEBUG) { + Slog.d(getGestureName(), "Completed."); + } + return true; + } + + private static int toDirection(float dX, float dY) { + if (Math.abs(dX) > Math.abs(dY)) { + // Horizontal + return (dX < 0) ? LEFT : RIGHT; + } else { + // Vertical + return (dY < 0) ? UP : DOWN; + } + } + + public static String directionToString(int direction) { + switch (direction) { + case LEFT: + return "left"; + case RIGHT: + return "right"; + case UP: + return "up"; + case DOWN: + return "down"; + default: + return "Unknown Direction"; + } + } + + @Override + String getGestureName() { + StringBuilder builder = new StringBuilder(); + builder.append(mTargetFingerCount).append("-finger "); + builder.append("Swipe ").append(directionToString(mDirection)); + return builder.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(super.toString()); + if (getState() != STATE_GESTURE_CANCELED) { + builder.append(", mBase: ") + .append(mBase.toString()) + .append(", mGestureDetectionThreshold:") + .append(mGestureDetectionThresholdPixels) + .append(", mMinPixelsBetweenSamplesX:") + .append(mMinPixelsBetweenSamplesX) + .append(", mMinPixelsBetweenSamplesY:") + .append(mMinPixelsBetweenSamplesY); + } + return builder.toString(); + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java index 8a566fcdb48a..ada251f2363c 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java @@ -18,6 +18,8 @@ package com.android.server.accessibility.gestures; import static android.view.MotionEvent.INVALID_POINTER_ID; +import static com.android.server.accessibility.gestures.GestureUtils.getActionIndex; + import android.content.Context; import android.os.Handler; import android.view.MotionEvent; @@ -155,11 +157,6 @@ class SecondFingerMultiTap extends GestureMatcher { return moveDelta <= slop; } - private int getActionIndex(MotionEvent event) { - return event.getAction() - & MotionEvent.ACTION_POINTER_INDEX_MASK << MotionEvent.ACTION_POINTER_INDEX_SHIFT; - } - @Override public String toString() { return super.toString() diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java index a285c10cc6ff..9108c69ce1f6 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java @@ -16,10 +16,10 @@ package com.android.server.accessibility.gestures; +import static com.android.server.accessibility.gestures.GestureUtils.MM_PER_CM; import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG; import android.content.Context; -import android.gesture.GesturePoint; import android.graphics.PointF; import android.os.Handler; import android.util.DisplayMetrics; @@ -44,10 +44,10 @@ class Swipe extends GestureMatcher { public static final int DOWN = 3; // This is the calculated movement threshold used track if the user is still // moving their finger. - private final float mGestureDetectionThreshold; + private final float mGestureDetectionThresholdPixels; // Buffer for storing points for gesture detection. - private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100); + private final ArrayList<PointF> mStrokeBuffer = new ArrayList<>(100); // The minimal delta between moves to add a gesture point. private static final int TOUCH_TOLERANCE_PIX = 3; @@ -56,7 +56,7 @@ class Swipe extends GestureMatcher { private static final float MIN_PREDICTION_SCORE = 2.0f; // Distance a finger must travel before we decide if it is a gesture or not. - private static final int GESTURE_CONFIRM_CM = 1; + public static final int GESTURE_CONFIRM_CM = 1; // Time threshold used to determine if an interaction is a gesture or not. // If the first movement of 1cm takes longer than this value, we assume it's @@ -67,17 +67,16 @@ class Swipe extends GestureMatcher { // all gestures started with the initial movement taking less than 100ms. // When touch exploring, the first movement almost always takes longer than // 200ms. - private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150; + public static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150; // Time threshold used to determine if a gesture should be cancelled. If // the finger takes more than this time to move 1cm, the ongoing gesture is // cancelled. - private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300; + public static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300; private int[] mDirections; private float mBaseX; private float mBaseY; - private long mBaseTime; private float mPreviousGestureX; private float mPreviousGestureY; // Constants for sampling motion event points. @@ -119,8 +118,8 @@ class Swipe extends GestureMatcher { super(gesture, new Handler(context.getMainLooper()), listener); mDirections = directions; DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - mGestureDetectionThreshold = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10, displayMetrics) + mGestureDetectionThresholdPixels = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, MM_PER_CM, displayMetrics) * GESTURE_CONFIRM_CM; // Calculate minimum gesture velocity final float pixelsPerCmX = displayMetrics.xdpi / 2.54f; @@ -135,18 +134,16 @@ class Swipe extends GestureMatcher { protected void clear() { mBaseX = Float.NaN; mBaseY = Float.NaN; - mBaseTime = 0; mStrokeBuffer.clear(); super.clear(); } @Override protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - cancelAfterDelay(event, rawEvent, policyFlags); + cancelAfterPauseThreshold(event, rawEvent, policyFlags); if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) { mBaseX = rawEvent.getX(); mBaseY = rawEvent.getY(); - mBaseTime = event.getEventTime(); mPreviousGestureX = mBaseX; mPreviousGestureY = mBaseY; } @@ -157,10 +154,8 @@ class Swipe extends GestureMatcher { protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) { final float x = rawEvent.getX(); final float y = rawEvent.getY(); - final long time = event.getEventTime(); final float dX = Math.abs(x - mPreviousGestureX); final float dY = Math.abs(y - mPreviousGestureY); - final long timeDelta = time - mBaseTime; final double moveDelta = Math.hypot(Math.abs(x - mBaseX), Math.abs(y - mBaseY)); if (DEBUG) { Slog.d( @@ -168,7 +163,7 @@ class Swipe extends GestureMatcher { "moveDelta:" + Double.toString(moveDelta) + " mGestureDetectionThreshold: " - + Float.toString(mGestureDetectionThreshold)); + + Float.toString(mGestureDetectionThresholdPixels)); } if (getState() == STATE_CLEAR) { if (moveDelta < mTouchSlop) { @@ -176,25 +171,24 @@ class Swipe extends GestureMatcher { return; } else if (mStrokeBuffer.size() == 0) { // First, make sure the pointer is going in the right direction. - cancelAfterDelay(event, rawEvent, policyFlags); + cancelAfterPauseThreshold(event, rawEvent, policyFlags); int direction = toDirection(x - mBaseX, y - mBaseY); if (direction != mDirections[0]) { cancelGesture(event, rawEvent, policyFlags); return; } else { // This is confirmed to be some kind of swipe so start tracking points. - mStrokeBuffer.add(new GesturePoint(mBaseX, mBaseY, mBaseTime)); + mStrokeBuffer.add(new PointF(mBaseX, mBaseY)); } } - if (moveDelta > mGestureDetectionThreshold) { + if (moveDelta > mGestureDetectionThresholdPixels) { // If the pointer has moved more than the threshold, // update the stored values. mBaseX = x; mBaseY = y; - mBaseTime = time; if (getState() == STATE_CLEAR) { startGesture(event, rawEvent, policyFlags); - cancelAfterDelay(event, rawEvent, policyFlags); + cancelAfterPauseThreshold(event, rawEvent, policyFlags); } } } @@ -202,8 +196,8 @@ class Swipe extends GestureMatcher { if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) { mPreviousGestureX = x; mPreviousGestureY = y; - mStrokeBuffer.add(new GesturePoint(x, y, time)); - cancelAfterDelay(event, rawEvent, policyFlags); + mStrokeBuffer.add(new PointF(x, y)); + cancelAfterPauseThreshold(event, rawEvent, policyFlags); } } } @@ -217,11 +211,10 @@ class Swipe extends GestureMatcher { final float x = rawEvent.getX(); final float y = rawEvent.getY(); - final long time = event.getEventTime(); final float dX = Math.abs(x - mPreviousGestureX); final float dY = Math.abs(y - mPreviousGestureY); if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) { - mStrokeBuffer.add(new GesturePoint(x, y, time)); + mStrokeBuffer.add(new PointF(x, y)); } recognizeGesture(event, rawEvent, policyFlags); } @@ -240,7 +233,8 @@ class Swipe extends GestureMatcher { * queues a transition to STATE_GESTURE_CANCEL based on the current state. If we have * transitioned to STATE_GESTURE_STARTED the delay is longer. */ - private void cancelAfterDelay(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + private void cancelAfterPauseThreshold( + MotionEvent event, MotionEvent rawEvent, int policyFlags) { cancelPendingTransitions(); switch (getState()) { case STATE_CLEAR: @@ -275,7 +269,7 @@ class Swipe extends GestureMatcher { // 90 degrees. ArrayList<PointF> path = new ArrayList<>(); - PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y); + PointF lastDelimiter = mStrokeBuffer.get(0); path.add(lastDelimiter); float dX = 0; // Sum of unit vectors from last delimiter to each following point @@ -283,9 +277,9 @@ class Swipe extends GestureMatcher { int count = 0; // Number of points since last delimiter float length = 0; // Vector length from delimiter to most recent point - PointF next = new PointF(); + PointF next = null; for (int i = 1; i < mStrokeBuffer.size(); ++i) { - next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y); + next = mStrokeBuffer.get(i); if (count > 0) { // Average of unit vectors from delimiter to following points float currentDX = dX / count; @@ -428,7 +422,7 @@ class Swipe extends GestureMatcher { .append(", mBaseY: ") .append(mBaseY) .append(", mGestureDetectionThreshold:") - .append(mGestureDetectionThreshold) + .append(mGestureDetectionThresholdPixels) .append(", mMinPixelsBetweenSamplesX:") .append(mMinPixelsBetweenSamplesX) .append(", mMinPixelsBetweenSamplesY:") diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 4f18f3547449..1a4fc32a76cd 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -154,6 +154,13 @@ final class AutofillManagerServiceImpl @GuardedBy("mLock") private FillEventHistory mEventHistory; + /** + * The last inline augmented autofill selection. Note that we don't log the selection from the + * dropdown UI since the service owns the UI in that case. + */ + @GuardedBy("mLock") + private FillEventHistory mAugmentedAutofillEventHistory; + /** Shared instance, doesn't need to be logged */ private final AutofillCompatState mAutofillCompatState; @@ -707,6 +714,13 @@ final class AutofillManagerServiceImpl } } + void setLastAugmentedAutofillResponse(int sessionId) { + synchronized (mLock) { + mAugmentedAutofillEventHistory = new FillEventHistory(sessionId, /* clientState= */ + null); + } + } + /** * Resets the last fill selection. */ @@ -716,6 +730,12 @@ final class AutofillManagerServiceImpl } } + void resetLastAugmentedAutofillResponse() { + synchronized (mLock) { + mAugmentedAutofillEventHistory = null; + } + } + @GuardedBy("mLock") private boolean isValidEventLocked(String method, int sessionId) { if (mEventHistory == null) { @@ -798,6 +818,32 @@ final class AutofillManagerServiceImpl } } + void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, + @Nullable Bundle clientState) { + synchronized (mLock) { + if (mAugmentedAutofillEventHistory == null + || mAugmentedAutofillEventHistory.getSessionId() != sessionId) { + return; + } + mAugmentedAutofillEventHistory.addEvent( + new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null, + null, null, null, null, null, null)); + } + } + + void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState) { + synchronized (mLock) { + if (mAugmentedAutofillEventHistory == null + || mAugmentedAutofillEventHistory.getSessionId() != sessionId) { + return; + } + mAugmentedAutofillEventHistory.addEvent( + new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null, + null, null, null, null, null)); + + } + } + /** * Updates the last fill response when an autofill context is committed. */ @@ -881,8 +927,8 @@ final class AutofillManagerServiceImpl * Gets the fill event history. * * @param callingUid The calling uid - * - * @return The history or {@code null} if there is none. + * @return The history for the autofill or the augmented autofill events depending on the {@code + * callingUid}, or {@code null} if there is none. */ FillEventHistory getFillEventHistory(int callingUid) { synchronized (mLock) { @@ -890,6 +936,10 @@ final class AutofillManagerServiceImpl && isCalledByServiceLocked("getFillEventHistory", callingUid)) { return mEventHistory; } + if (mAugmentedAutofillEventHistory != null && isCalledByAugmentedAutofillServiceLocked( + "getFillEventHistory", callingUid)) { + return mAugmentedAutofillEventHistory; + } } return null; } @@ -1163,8 +1213,32 @@ final class AutofillManagerServiceImpl Slog.v(TAG, "getRemoteAugmentedAutofillServiceLocked(): " + componentName); } - mRemoteAugmentedAutofillService = new RemoteAugmentedAutofillService(getContext(), - componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() { + final RemoteAugmentedAutofillServiceCallbacks callbacks = + new RemoteAugmentedAutofillServiceCallbacks() { + @Override + public void resetLastResponse() { + AutofillManagerServiceImpl.this.resetLastAugmentedAutofillResponse(); + } + + @Override + public void setLastResponse(int sessionId) { + AutofillManagerServiceImpl.this.setLastAugmentedAutofillResponse( + sessionId); + } + + @Override + public void logAugmentedAutofillShown(int sessionId, Bundle clientState) { + AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId, + clientState); + } + + @Override + public void logAugmentedAutofillSelected(int sessionId, String suggestionId, + Bundle clientState) { + AutofillManagerServiceImpl.this.logAugmentedAutofillSelected(sessionId, + suggestionId, clientState); + } + @Override public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) { Slog.w(TAG, "remote augmented autofill service died"); @@ -1175,8 +1249,10 @@ final class AutofillManagerServiceImpl } mRemoteAugmentedAutofillService = null; } - }, mMaster.isInstantServiceAllowed(), mMaster.verbose, - mMaster.mAugmentedServiceIdleUnbindTimeoutMs, + }; + mRemoteAugmentedAutofillService = new RemoteAugmentedAutofillService(getContext(), + componentName, mUserId, callbacks, mMaster.isInstantServiceAllowed(), + mMaster.verbose, mMaster.mAugmentedServiceIdleUnbindTimeoutMs, mMaster.mAugmentedServiceRequestTimeoutMs); } diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java index c7be80cd538b..cb6c8f5d3ea5 100644 --- a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java @@ -28,7 +28,6 @@ import android.util.Slog; import android.view.SurfaceControl; import android.view.View; import android.view.autofill.AutofillId; -import android.view.autofill.IAutoFillManagerClient; import android.view.inputmethod.InlineSuggestion; import android.view.inputmethod.InlineSuggestionInfo; import android.view.inputmethod.InlineSuggestionsResponse; @@ -49,15 +48,24 @@ public final class InlineSuggestionFactory { private static final String TAG = "InlineSuggestionFactory"; /** + * Callback from the inline suggestion Ui. + */ + public interface InlineSuggestionUiCallback { + /** + * Callback to autofill a dataset to the client app. + */ + void autofill(@NonNull Dataset dataset); + } + + /** * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by * augmented autofill service. */ public static InlineSuggestionsResponse createAugmentedInlineSuggestionsResponse( - int sessionId, @NonNull Dataset[] datasets, @NonNull AutofillId autofillId, @NonNull Context context, - @NonNull IAutoFillManagerClient client) { + @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback) { if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called"); final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>(); @@ -74,8 +82,8 @@ public final class InlineSuggestionFactory { Slog.w(TAG, "InlinePresentation not found in dataset"); return null; } - InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(sessionId, dataset, - inlinePresentation, inlineSuggestionUi, client); + InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(dataset, + inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback); inlineSuggestions.add(inlineSuggestion); } return new InlineSuggestionsResponse(inlineSuggestions); @@ -114,22 +122,17 @@ public final class InlineSuggestionFactory { return new InlineSuggestionsResponse(inlineSuggestions); } - private static InlineSuggestion createAugmentedInlineSuggestion(int sessionId, - @NonNull Dataset dataset, + private static InlineSuggestion createAugmentedInlineSuggestion(@NonNull Dataset dataset, @NonNull InlinePresentation inlinePresentation, @NonNull InlineSuggestionUi inlineSuggestionUi, - @NonNull IAutoFillManagerClient client) { + @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback) { // TODO(b/146453195): fill in the autofill hint properly. final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo( inlinePresentation.getInlinePresentationSpec(), InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""}, InlineSuggestionInfo.TYPE_SUGGESTION); final View.OnClickListener onClickListener = v -> { - try { - client.autofill(sessionId, dataset.getFieldIds(), dataset.getFieldValues()); - } catch (RemoteException e) { - Slog.w(TAG, "Encounter exception autofilling the values"); - } + inlineSuggestionUiCallback.autofill(dataset); }; final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo, createInlineContentProvider(inlinePresentation, inlineSuggestionUi, diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 5fbdd25ea6c6..5e6f6fea4dda 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -69,6 +69,7 @@ final class RemoteAugmentedAutofillService private final int mIdleUnbindTimeoutMs; private final int mRequestTimeoutMs; private final ComponentName mComponentName; + private final RemoteAugmentedAutofillServiceCallbacks mCallbacks; RemoteAugmentedAutofillService(Context context, ComponentName serviceName, int userId, RemoteAugmentedAutofillServiceCallbacks callbacks, @@ -81,6 +82,7 @@ final class RemoteAugmentedAutofillService mIdleUnbindTimeoutMs = idleUnbindTimeoutMs; mRequestTimeoutMs = requestTimeoutMs; mComponentName = serviceName; + mCallbacks = callbacks; // Bind right away. connect(); @@ -159,9 +161,12 @@ final class RemoteAugmentedAutofillService focusedId, focusedValue, requestTime, inlineSuggestionsRequest, new IFillCallback.Stub() { @Override - public void onSuccess(@Nullable Dataset[] inlineSuggestionsData) { - maybeHandleInlineSuggestions(sessionId, inlineSuggestionsData, - focusedId, inlineSuggestionsCallback, client); + public void onSuccess(@Nullable Dataset[] inlineSuggestionsData, + @Nullable Bundle clientState) { + mCallbacks.resetLastResponse(); + maybeRequestShowInlineSuggestions(sessionId, + inlineSuggestionsData, focusedId, + inlineSuggestionsCallback, client, clientState); requestAutofill.complete(null); } @@ -223,20 +228,32 @@ final class RemoteAugmentedAutofillService }); } - private void maybeHandleInlineSuggestions(int sessionId, + private void maybeRequestShowInlineSuggestions(int sessionId, @Nullable Dataset[] inlineSuggestionsData, @NonNull AutofillId focusedId, @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback, - @NonNull IAutoFillManagerClient client) { + @NonNull IAutoFillManagerClient client, @Nullable Bundle clientState) { if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null) { return; } + mCallbacks.setLastResponse(sessionId); try { inlineSuggestionsCallback.onInlineSuggestionsResponse( - InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(sessionId, - inlineSuggestionsData, focusedId, mContext, client)); + InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse( + inlineSuggestionsData, focusedId, mContext, + dataset -> { + mCallbacks.logAugmentedAutofillSelected(sessionId, + dataset.getId(), clientState); + try { + client.autofill(sessionId, dataset.getFieldIds(), + dataset.getFieldValues()); + } catch (RemoteException e) { + Slog.w(TAG, "Encounter exception autofilling the values"); + } + })); } catch (RemoteException e) { Slog.w(TAG, "Exception sending inline suggestions response back to IME."); } + mCallbacks.logAugmentedAutofillShown(sessionId, clientState); } @Override @@ -254,8 +271,13 @@ final class RemoteAugmentedAutofillService public interface RemoteAugmentedAutofillServiceCallbacks extends AbstractRemoteService.VultureCallback<RemoteAugmentedAutofillService> { - // NOTE: so far we don't need to notify the callback implementation (an inner class on - // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this - // callback interface is empty. + void resetLastResponse(); + + void setLastResponse(int sessionId); + + void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState); + + void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, + @Nullable Bundle clientState); } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index ee37de56ca15..415ecd8cfd63 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2674,7 +2674,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (response.supportsInlineSuggestions()) { if (requestShowInlineSuggestions(response)) { - //TODO(b/137800469): Add logging instead of bypassing below logic. + //TODO(b/137800469): Fix it to log showed only when IME asks for inflation, rather + // than here where framework sends back the response. + mService.logDatasetShown(id, mClientState); return; } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 2982dc9fab1b..0194a213f185 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -557,13 +557,17 @@ public class ConnectivityService extends IConnectivityManager.Stub .asInterface(ServiceManager.getService("dnsresolver")); } - /** Handler thread used for both of the handlers below. */ + /** Handler thread used for all of the handlers below. */ @VisibleForTesting protected final HandlerThread mHandlerThread; /** Handler used for internal events. */ final private InternalHandler mHandler; /** Handler used for incoming {@link NetworkStateTracker} events. */ final private NetworkStateTrackerHandler mTrackerHandler; + /** Handler used for processing {@link android.net.ConnectivityDiagnosticsManager} events */ + @VisibleForTesting + final ConnectivityDiagnosticsHandler mConnectivityDiagnosticsHandler; + private final DnsManager mDnsManager; private boolean mSystemReady; @@ -630,6 +634,10 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting final MultipathPolicyTracker mMultipathPolicyTracker; + @VisibleForTesting + final Map<IConnectivityDiagnosticsCallback, ConnectivityDiagnosticsCallbackInfo> + mConnectivityDiagnosticsCallbacks = new HashMap<>(); + /** * Implements support for the legacy "one network per network type" model. * @@ -962,6 +970,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandlerThread.start(); mHandler = new InternalHandler(mHandlerThread.getLooper()); mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper()); + mConnectivityDiagnosticsHandler = + new ConnectivityDiagnosticsHandler(mHandlerThread.getLooper()); mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000); @@ -3391,18 +3401,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nri.unlinkDeathRecipient(); mNetworkRequests.remove(nri.request); - synchronized (mUidToNetworkRequestCount) { - int requests = mUidToNetworkRequestCount.get(nri.mUid, 0); - if (requests < 1) { - Slog.wtf(TAG, "BUG: too small request count " + requests + " for UID " + - nri.mUid); - } else if (requests == 1) { - mUidToNetworkRequestCount.removeAt( - mUidToNetworkRequestCount.indexOfKey(nri.mUid)); - } else { - mUidToNetworkRequestCount.put(nri.mUid, requests - 1); - } - } + decrementNetworkRequestPerUidCount(nri); mNetworkRequestInfoLogs.log("RELEASE " + nri); if (nri.request.isRequest()) { @@ -3473,6 +3472,19 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private void decrementNetworkRequestPerUidCount(final NetworkRequestInfo nri) { + synchronized (mUidToNetworkRequestCount) { + final int requests = mUidToNetworkRequestCount.get(nri.mUid, 0); + if (requests < 1) { + Slog.wtf(TAG, "BUG: too small request count " + requests + " for UID " + nri.mUid); + } else if (requests == 1) { + mUidToNetworkRequestCount.removeAt(mUidToNetworkRequestCount.indexOfKey(nri.mUid)); + } else { + mUidToNetworkRequestCount.put(nri.mUid, requests - 1); + } + } + } + @Override public void setAcceptUnvalidated(Network network, boolean accept, boolean always) { enforceNetworkStackSettingsOrSetup(); @@ -5091,6 +5103,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + NetworkRequestInfo(NetworkRequest r) { + this(r, null); + } + private void enforceRequestCountLimit() { synchronized (mUidToNetworkRequestCount) { int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1; @@ -6165,7 +6181,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private void callCallbackForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent, int notificationType, int arg1) { if (nri.messenger == null) { - return; // Default request has no msgr + // Default request has no msgr. Also prevents callbacks from being invoked for + // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks + // are Type.LISTEN, but should not have NetworkCallbacks invoked. + return; } Bundle bundle = new Bundle(); // TODO: check if defensive copies of data is needed. @@ -6319,12 +6338,34 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + static class RequestReassignment { + @NonNull public final NetworkRequestInfo mRequest; + @Nullable public final NetworkAgentInfo mOldNetwork; + @Nullable public final NetworkAgentInfo mNewNetwork; + RequestReassignment(@NonNull final NetworkRequestInfo request, + @Nullable final NetworkAgentInfo oldNetwork, + @Nullable final NetworkAgentInfo newNetwork) { + mRequest = request; + mOldNetwork = oldNetwork; + mNewNetwork = newNetwork; + } + } + @NonNull private final Set<NetworkBgStatePair> mRematchedNetworks = new ArraySet<>(); + @NonNull private final List<RequestReassignment> mReassignments = new ArrayList<>(); @NonNull Iterable<NetworkBgStatePair> getRematchedNetworks() { return mRematchedNetworks; } + @NonNull Iterable<RequestReassignment> getRequestReassignments() { + return mReassignments; + } + + void addRequestReassignment(@NonNull final RequestReassignment reassignment) { + mReassignments.add(reassignment); + } + void addRematchedNetwork(@NonNull final NetworkBgStatePair network) { mRematchedNetworks.add(network); } @@ -6400,20 +6441,13 @@ public class ConnectivityService extends IConnectivityManager.Stub changes.addRematchedNetwork(new NetworkReassignment.NetworkBgStatePair(newNetwork, newNetwork.isBackgroundNetwork())); - final int score = newNetwork.getCurrentScore(); - if (VDBG || DDBG) log("rematching " + newNetwork.name()); final ArrayMap<NetworkRequestInfo, NetworkAgentInfo> reassignedRequests = computeRequestReassignmentForNetwork(newNetwork); - NetworkCapabilities nc = newNetwork.networkCapabilities; - if (VDBG) log(" network has: " + nc); - // Find and migrate to this Network any NetworkRequests for // which this network is now the best. - final ArrayList<NetworkAgentInfo> removedRequests = new ArrayList<>(); - final ArrayList<NetworkRequestInfo> addedRequests = new ArrayList<>(); for (final Map.Entry<NetworkRequestInfo, NetworkAgentInfo> entry : reassignedRequests.entrySet()) { final NetworkRequestInfo nri = entry.getKey(); @@ -6427,7 +6461,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } previousSatisfier.removeRequest(nri.request.requestId); previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs); - removedRequests.add(previousSatisfier); } else { if (VDBG || DDBG) log(" accepting network in place of null"); } @@ -6436,7 +6469,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!newSatisfier.addRequest(nri.request)) { Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request); } - addedRequests.add(nri); + changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( + nri, previousSatisfier, newSatisfier)); // Tell NetworkProviders about the new score, so they can stop // trying to connect if they know they cannot match it. // TODO - this could get expensive if we have a lot of requests for this @@ -6493,21 +6527,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // Have a new default network, release the transition wakelock in scheduleReleaseNetworkTransitionWakelock(); } - - if (!newNetwork.networkCapabilities.equalRequestableCapabilities(nc)) { - Slog.wtf(TAG, String.format( - "BUG: %s changed requestable capabilities during rematch: %s -> %s", - newNetwork.name(), nc, newNetwork.networkCapabilities)); - } - if (newNetwork.getCurrentScore() != score) { - Slog.wtf(TAG, String.format( - "BUG: %s changed score during rematch: %d -> %d", - newNetwork.name(), score, newNetwork.getCurrentScore())); - } - - // Notify requested networks are available after the default net is switched, but - // before LegacyTypeTracker sends legacy broadcasts - for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri); } /** @@ -6536,6 +6555,15 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkAgentInfo newDefaultNetwork = getDefaultNetwork(); + // Notify requested networks are available after the default net is switched, but + // before LegacyTypeTracker sends legacy broadcasts + for (final NetworkReassignment.RequestReassignment event : + changes.getRequestReassignments()) { + if (null != event.mNewNetwork) { + notifyNetworkAvailable(event.mNewNetwork, event.mRequest); + } + } + for (final NetworkReassignment.NetworkBgStatePair event : changes.getRematchedNetworks()) { // Process listen requests and update capabilities if the background state has // changed for this network. For consistency with previous behavior, send onLost @@ -7321,19 +7349,161 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * Handler used for managing all Connectivity Diagnostics related functions. + * + * @see android.net.ConnectivityDiagnosticsManager + * + * TODO(b/147816404): Explore moving ConnectivityDiagnosticsHandler to a separate file + */ + @VisibleForTesting + class ConnectivityDiagnosticsHandler extends Handler { + /** + * Used to handle ConnectivityDiagnosticsCallback registration events from {@link + * android.net.ConnectivityDiagnosticsManager}. + * obj = ConnectivityDiagnosticsCallbackInfo with IConnectivityDiagnosticsCallback and + * NetworkRequestInfo to be registered + */ + private static final int EVENT_REGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK = 1; + + /** + * Used to handle ConnectivityDiagnosticsCallback unregister events from {@link + * android.net.ConnectivityDiagnosticsManager}. + * obj = the IConnectivityDiagnosticsCallback to be unregistered + * arg1 = the uid of the caller + */ + private static final int EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK = 2; + + private ConnectivityDiagnosticsHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_REGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK: { + handleRegisterConnectivityDiagnosticsCallback( + (ConnectivityDiagnosticsCallbackInfo) msg.obj); + break; + } + case EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK: { + handleUnregisterConnectivityDiagnosticsCallback( + (IConnectivityDiagnosticsCallback) msg.obj, msg.arg1); + break; + } + } + } + } + + /** Class used for cleaning up IConnectivityDiagnosticsCallback instances after their death. */ + @VisibleForTesting + class ConnectivityDiagnosticsCallbackInfo implements Binder.DeathRecipient { + @NonNull private final IConnectivityDiagnosticsCallback mCb; + @NonNull private final NetworkRequestInfo mRequestInfo; + + @VisibleForTesting + ConnectivityDiagnosticsCallbackInfo( + @NonNull IConnectivityDiagnosticsCallback cb, @NonNull NetworkRequestInfo nri) { + mCb = cb; + mRequestInfo = nri; + } + + @Override + public void binderDied() { + log("ConnectivityDiagnosticsCallback IBinder died."); + unregisterConnectivityDiagnosticsCallback(mCb); + } + } + + private void handleRegisterConnectivityDiagnosticsCallback( + @NonNull ConnectivityDiagnosticsCallbackInfo cbInfo) { + ensureRunningOnConnectivityServiceThread(); + + final IConnectivityDiagnosticsCallback cb = cbInfo.mCb; + final NetworkRequestInfo nri = cbInfo.mRequestInfo; + + // This means that the client registered the same callback multiple times. Do + // not override the previous entry, and exit silently. + if (mConnectivityDiagnosticsCallbacks.containsKey(cb)) { + if (VDBG) log("Diagnostics callback is already registered"); + + // Decrement the reference count for this NetworkRequestInfo. The reference count is + // incremented when the NetworkRequestInfo is created as part of + // enforceRequestCountLimit(). + decrementNetworkRequestPerUidCount(nri); + return; + } + + mConnectivityDiagnosticsCallbacks.put(cb, cbInfo); + + try { + cb.asBinder().linkToDeath(cbInfo, 0); + } catch (RemoteException e) { + cbInfo.binderDied(); + } + } + + private void handleUnregisterConnectivityDiagnosticsCallback( + @NonNull IConnectivityDiagnosticsCallback cb, int uid) { + ensureRunningOnConnectivityServiceThread(); + + if (!mConnectivityDiagnosticsCallbacks.containsKey(cb)) { + if (VDBG) log("Removing diagnostics callback that is not currently registered"); + return; + } + + final NetworkRequestInfo nri = mConnectivityDiagnosticsCallbacks.get(cb).mRequestInfo; + + if (uid != nri.mUid) { + if (VDBG) loge("Different uid than registrant attempting to unregister cb"); + return; + } + + cb.asBinder().unlinkToDeath(mConnectivityDiagnosticsCallbacks.remove(cb), 0); + } + @Override public void registerConnectivityDiagnosticsCallback( @NonNull IConnectivityDiagnosticsCallback callback, @NonNull NetworkRequest request) { - // TODO(b/146444622): implement register IConnectivityDiagnosticsCallback functionality - throw new UnsupportedOperationException( - "registerConnectivityDiagnosticsCallback not yet implemented"); + if (request.legacyType != TYPE_NONE) { + throw new IllegalArgumentException("ConnectivityManager.TYPE_* are deprecated." + + " Please use NetworkCapabilities instead."); + } + + // This NetworkCapabilities is only used for matching to Networks. Clear out its owner uid + // and administrator uids to be safe. + final NetworkCapabilities nc = new NetworkCapabilities(request.networkCapabilities); + restrictRequestUidsForCaller(nc); + + final NetworkRequest requestWithId = + new NetworkRequest( + nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); + + // NetworkRequestInfos created here count towards MAX_NETWORK_REQUESTS_PER_UID limit. + // + // nri is not bound to the death of callback. Instead, callback.bindToDeath() is set in + // handleRegisterConnectivityDiagnosticsCallback(). nri will be cleaned up as part of the + // callback's binder death. + final NetworkRequestInfo nri = new NetworkRequestInfo(requestWithId); + final ConnectivityDiagnosticsCallbackInfo cbInfo = + new ConnectivityDiagnosticsCallbackInfo(callback, nri); + + mConnectivityDiagnosticsHandler.sendMessage( + mConnectivityDiagnosticsHandler.obtainMessage( + ConnectivityDiagnosticsHandler + .EVENT_REGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK, + cbInfo)); } @Override public void unregisterConnectivityDiagnosticsCallback( @NonNull IConnectivityDiagnosticsCallback callback) { - // TODO(b/146444622): implement register IConnectivityDiagnosticsCallback functionality - throw new UnsupportedOperationException( - "unregisterConnectivityDiagnosticsCallback not yet implemented"); + mConnectivityDiagnosticsHandler.sendMessage( + mConnectivityDiagnosticsHandler.obtainMessage( + ConnectivityDiagnosticsHandler + .EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK, + Binder.getCallingUid(), + 0, + callback)); } } diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index e342dd7977fb..50843b0999a1 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -2644,7 +2644,12 @@ public class LocationManagerService extends ILocationManager.Stub { if (noGPSLocation == null && lastNoGPSLocation != null) { // New location has no no-GPS location: adopt last no-GPS location. This is set // directly into location because we do not want to notify COARSE clients. - location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation); + Bundle extras = location.getExtras(); + if (extras == null) { + extras = new Bundle(); + } + extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation); + location.setExtras(extras); } } lastLocation.set(location); diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 9d4c78383183..1bb3c3ac0cda 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -86,12 +86,12 @@ import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.SparseIntArray; -import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.HexDump; import com.android.internal.util.Preconditions; @@ -412,8 +412,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid); } catch (RemoteException e) { } - StatsLog.write_non_chained(StatsLog.MOBILE_RADIO_POWER_STATE_CHANGED, uid, null, - powerState); + FrameworkStatsLog.write_non_chained( + FrameworkStatsLog.MOBILE_RADIO_POWER_STATE_CHANGED, uid, null, powerState); } } @@ -424,8 +424,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid); } catch (RemoteException e) { } - StatsLog.write_non_chained(StatsLog.WIFI_RADIO_POWER_STATE_CHANGED, uid, null, - powerState); + FrameworkStatsLog.write_non_chained( + FrameworkStatsLog.WIFI_RADIO_POWER_STATE_CHANGED, uid, null, powerState); } } diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index bacffb6140ed..3e5c278a7fd4 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -393,8 +393,8 @@ public class RescueParty { @Override public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage, @FailureReasons int failureReason) { - if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH - || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { + if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH + || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) { return mapRescueLevelToUserImpact(getNextRescueLevel()); } else { return PackageHealthObserverImpact.USER_IMPACT_NONE; diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index 14cd3a5d5c0d..b43ae36c7ef5 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -49,6 +49,8 @@ import android.util.Log; import com.android.internal.content.PackageMonitor; import com.android.internal.util.Preconditions; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.List; import java.util.Objects; import java.util.concurrent.Callable; @@ -183,9 +185,7 @@ public class ServiceWatcher implements ServiceConnection { // write from handler thread only, read anywhere private volatile ServiceInfo mServiceInfo; - - // read/write from handler thread only - private IBinder mBinder; + private volatile IBinder mBinder; public ServiceWatcher(Context context, Handler handler, String action, @Nullable BinderRunner onBind, @Nullable Runnable onUnbind, @@ -345,20 +345,25 @@ public class ServiceWatcher implements ServiceConnection { } mBinder = binder; + + // we always run the on bind callback even if we know that the binder is dead already so + // that there are always balance pairs of bind/unbind callbacks if (mOnBind != null) { - runOnBinder(mOnBind); + try { + mOnBind.run(binder); + } catch (RuntimeException | RemoteException e) { + // binders may propagate some specific non-RemoteExceptions from the other side + // through the binder as well - we cannot allow those to crash the system server + Log.e(TAG, getLogPrefix() + " exception running on " + mServiceInfo, e); + } } - } - @Override - public void onBindingDied(ComponentName component) { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - - if (D) { - Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died"); + try { + // setting the binder to null lets us skip queued transactions + binder.linkToDeath(() -> mBinder = null, 0); + } catch (RemoteException e) { + mBinder = null; } - - onBestServiceChanged(true); } @Override @@ -375,6 +380,17 @@ public class ServiceWatcher implements ServiceConnection { } } + @Override + public void onBindingDied(ComponentName component) { + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); + + if (D) { + Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died"); + } + + onBestServiceChanged(true); + } + private void onUserSwitched(@UserIdInt int userId) { mCurrentUserId = userId; onBestServiceChanged(false); @@ -398,7 +414,7 @@ public class ServiceWatcher implements ServiceConnection { * RemoteException thrown during execution. */ public final void runOnBinder(BinderRunner runner) { - runOnHandler(() -> { + mHandler.post(() -> { if (mBinder == null) { return; } @@ -447,14 +463,6 @@ public class ServiceWatcher implements ServiceConnection { } } - private void runOnHandler(Runnable r) { - if (Looper.myLooper() == mHandler.getLooper()) { - r.run(); - } else { - mHandler.post(r); - } - } - private <T> T runOnHandlerBlocking(Callable<T> c) throws InterruptedException, TimeoutException { if (Looper.myLooper() == mHandler.getLooper()) { @@ -489,4 +497,12 @@ public class ServiceWatcher implements ServiceConnection { public String toString() { return mServiceInfo.toString(); } + + /** + * Dump for debugging. + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("service=" + mServiceInfo); + pw.println("connected=" + (mBinder != null)); + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 08625bed061c..6560777edd53 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -355,7 +355,6 @@ import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.firewall.IntentFirewall; import com.android.server.job.JobSchedulerInternal; import com.android.server.pm.Installer; -import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.uri.GrantUri; import com.android.server.uri.UriGrantsManagerInternal; @@ -5270,26 +5269,6 @@ public class ActivityManagerService extends IActivityManager.Stub mCallFinishBooting = false; } - ArraySet<String> completedIsas = new ArraySet<String>(); - for (String abi : Build.SUPPORTED_ABIS) { - ZYGOTE_PROCESS.establishZygoteConnectionForAbi(abi); - final String instructionSet = VMRuntime.getInstructionSet(abi); - if (!completedIsas.contains(instructionSet)) { - try { - mInstaller.markBootComplete(VMRuntime.getInstructionSet(abi)); - } catch (InstallerException e) { - if (!VMRuntime.didPruneDalvikCache()) { - // This is technically not the right filter, as different zygotes may - // have made different pruning decisions. But the log is best effort, - // anyways. - Slog.w(TAG, "Unable to mark boot complete for abi: " + abi + " (" + - e.getMessage() +")"); - } - } - completedIsas.add(instructionSet); - } - } - // Let the ART runtime in zygote and system_server know that the boot completed. ZYGOTE_PROCESS.bootCompleted(); VMRuntime.bootCompleted(); diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java index bab133f58c11..60aba277569b 100644 --- a/services/core/java/com/android/server/am/AppExitInfoTracker.java +++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java @@ -17,6 +17,7 @@ package com.android.server.am; import static android.app.ActivityManager.RunningAppProcessInfo.procStateToImportance; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; @@ -52,6 +53,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ProcessMap; import com.android.server.IoThread; +import com.android.server.ServiceThread; import com.android.server.SystemServiceManager; import java.io.File; @@ -179,9 +181,12 @@ public final class AppExitInfoTracker { mRawRecordsPool = new SynchronizedPool<ApplicationExitInfo>(APP_EXIT_RAW_INFO_POOL_SIZE); } - void init(ActivityManagerService service, Looper looper) { + void init(ActivityManagerService service) { mService = service; - mKillHandler = new KillHandler(looper); + ServiceThread thread = new ServiceThread(TAG + ":killHandler", + THREAD_PRIORITY_BACKGROUND, true /* allowIo */); + thread.start(); + mKillHandler = new KillHandler(thread.getLooper()); mProcExitInfoFile = new File(SystemServiceManager.ensureSystemDir(), APP_EXIT_INFO_FILE); mAppExitInfoHistoryListSize = service.mContext.getResources().getInteger( diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index d5fc14b1ac14..39f79ca2f13b 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -550,7 +550,6 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { return data; } } - Slog.e(TAG, "no controller energy info supplied for " + receiver.getName()); } catch (TimeoutException e) { Slog.w(TAG, "timeout reading " + receiver.getName() + " stats"); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index b19f2b37e61b..3c7d6b80f557 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -739,7 +739,7 @@ public final class ProcessList { mSystemServerSocketForZygote.getFileDescriptor(), EVENT_INPUT, this::handleZygoteMessages); } - mAppExitInfoTracker.init(mService, sKillThread.getLooper()); + mAppExitInfoTracker.init(mService); mImperceptibleKillRunner = new ImperceptibleKillRunner(sKillThread.getLooper()); } } diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index bb8b12e86e16..2fc9d04623f8 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -25,7 +25,6 @@ import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; -import android.util.StatsLog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.AndroidBuildClassifier; @@ -54,7 +53,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { public PlatformCompat(Context context) { mContext = context; mChangeReporter = new ChangeReporter( - StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER); + ChangeReporter.SOURCE_SYSTEM_SERVER); mCompatConfig = CompatConfig.create(new AndroidBuildClassifier(), mContext); } @@ -62,14 +61,14 @@ public class PlatformCompat extends IPlatformCompat.Stub { PlatformCompat(Context context, CompatConfig compatConfig) { mContext = context; mChangeReporter = new ChangeReporter( - StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER); + ChangeReporter.SOURCE_SYSTEM_SERVER); mCompatConfig = compatConfig; } @Override public void reportChange(long changeId, ApplicationInfo appInfo) { reportChange(changeId, appInfo.uid, - StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED); + ChangeReporter.STATE_LOGGED); } @Override @@ -83,18 +82,18 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void reportChangeByUid(long changeId, int uid) { - reportChange(changeId, uid, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED); + reportChange(changeId, uid, ChangeReporter.STATE_LOGGED); } @Override public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { if (mCompatConfig.isChangeEnabled(changeId, appInfo)) { reportChange(changeId, appInfo.uid, - StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED); + ChangeReporter.STATE_ENABLED); return true; } reportChange(changeId, appInfo.uid, - StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED); + ChangeReporter.STATE_DISABLED); return false; } diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 17e2f69e9bf1..f3d201289f0e 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -35,11 +35,11 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; -import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.BitUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.RingBuffer; import com.android.internal.util.TokenBucket; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; @@ -278,7 +278,7 @@ public class NetdEventListenerService extends INetdEventListener.Stub { addWakeupEvent(event); String dstMac = event.dstHwAddr.toString(); - StatsLog.write(StatsLog.PACKET_WAKEUP_OCCURRED, + FrameworkStatsLog.write(FrameworkStatsLog.PACKET_WAKEUP_OCCURRED, uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort); } diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index decb1f9bbcf7..96532f4a21ac 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -1050,6 +1050,7 @@ public class DisplayModeDirector { public void dumpLocked(PrintWriter pw) { pw.println(" BrightnessObserver"); + pw.println(" mAmbientLux: " + mAmbientLux); pw.println(" mRefreshRateInZone: " + mRefreshRateInZone); for (int d: mDisplayBrightnessThresholds) { @@ -1059,6 +1060,8 @@ public class DisplayModeDirector { for (int d: mAmbientBrightnessThresholds) { pw.println(" mAmbientBrightnessThreshold: " + d); } + + mLightSensorListener.dumpLocked(pw); } public void onDisplayChanged(int displayId) { @@ -1222,6 +1225,10 @@ public class DisplayModeDirector { final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS; private float mLastSensorData; + public void dumpLocked(PrintWriter pw) { + pw.println(" mLastSensorData: " + mLastSensorData); + } + @Override public void onSensorChanged(SensorEvent event) { mLastSensorData = event.values[0]; diff --git a/services/core/java/com/android/server/location/LocationFudger.java b/services/core/java/com/android/server/location/LocationFudger.java index ae71fe36c31c..04c7714f07f7 100644 --- a/services/core/java/com/android/server/location/LocationFudger.java +++ b/services/core/java/com/android/server/location/LocationFudger.java @@ -16,17 +16,19 @@ package com.android.server.location; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.security.SecureRandom; import android.content.Context; import android.database.ContentObserver; import android.location.Location; +import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; import android.provider.Settings; import android.util.Log; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.security.SecureRandom; + /** * Contains the logic to obfuscate (fudge) locations for coarse applications. @@ -177,7 +179,12 @@ public class LocationFudger { private Location addCoarseLocationExtraLocked(Location location) { Location coarse = createCoarseLocked(location); - location.setExtraLocation(Location.EXTRA_COARSE_LOCATION, coarse); + Bundle extras = location.getExtras(); + if (extras == null) { + extras = new Bundle(); + } + extras.putParcelable(Location.EXTRA_COARSE_LOCATION, coarse); + location.setExtras(extras); return coarse; } diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index 19fb6694bbb5..96ffaa6c0bff 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -139,13 +139,13 @@ public class LocationProviderProxy extends AbstractLocationProvider { @GuardedBy("mLock") private boolean mBound; - @GuardedBy("mLock") - private ProviderRequest mRequest; + + private volatile ProviderRequest mRequest; private LocationProviderProxy(Context context, String action, int enableOverlayResId, int nonOverlayPackageResId) { - // safe to use direct executor even though this class has internal locks - all of our - // callbacks go to oneway binder transactions which cannot possibly be re-entrant + // safe to use direct executor since our locks are not acquired in a code path invoked by + // our owning provider super(DIRECT_EXECUTOR, Collections.emptySet()); mContext = context; @@ -167,8 +167,10 @@ public class LocationProviderProxy extends AbstractLocationProvider { mBound = true; provider.setLocationProviderManager(mManager); - if (!mRequest.equals(ProviderRequest.EMPTY_REQUEST)) { - provider.setRequest(mRequest, mRequest.workSource); + + ProviderRequest request = mRequest; + if (!request.equals(ProviderRequest.EMPTY_REQUEST)) { + provider.setRequest(request, request.workSource); } ComponentName service = mServiceWatcher.getBoundService().component; @@ -187,10 +189,7 @@ public class LocationProviderProxy extends AbstractLocationProvider { @Override public void onSetRequest(ProviderRequest request) { - synchronized (mLock) { - mRequest = request; - } - + mRequest = request; mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); service.setRequest(request, request.workSource); @@ -215,9 +214,6 @@ public class LocationProviderProxy extends AbstractLocationProvider { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("service=" + mServiceWatcher); - synchronized (mLock) { - pw.println("bound=" + mBound); - } + mServiceWatcher.dump(fd, pw, args); } } diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index 8179c32ae2df..669f1ac6750c 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -186,6 +186,7 @@ class BluetoothRouteProvider { .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED) .setDescription(mContext.getResources().getText( R.string.bluetooth_a2dp_audio_route_name).toString()) + .setDeviceType(MediaRoute2Info.DEVICE_TYPE_BLUETOOTH) .build(); newBtRoute.connectedProfiles = new SparseBooleanArray(); return newBtRoute; diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 1cd8aad3f0c5..5123362d84ba 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -36,6 +36,7 @@ abstract class MediaRoute2Provider { final Object mLock = new Object(); Callback mCallback; + boolean mIsSystemRouteProvider; private volatile MediaRoute2ProviderInfo mProviderInfo; @GuardedBy("mLock") @@ -85,6 +86,7 @@ abstract class MediaRoute2Provider { } else { mProviderInfo = new MediaRoute2ProviderInfo.Builder(providerInfo) .setUniqueId(mUniqueId) + .setSystemRouteProvider(mIsSystemRouteProvider) .build(); } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 558eb8d05783..924a9b71c8ec 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -83,6 +83,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { super(sComponentName); setCallback(callback); + mIsSystemRouteProvider = true; + mContext = context; mHandler = new Handler(Looper.getMainLooper()); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7104790d0a7c..68cc0141e657 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2735,7 +2735,7 @@ public class NotificationManagerService extends SystemService { // TODO(b/144152069): Remove informative toast mUiHandler.post(() -> Toast.makeText(getContext(), "Background custom toast blocked for package " + pkg + ".\n" - + "See go/r-toast-block.", + + "See g.co/dev/toast.", Toast.LENGTH_SHORT).show()); Slog.w(TAG, "Blocking custom toast from package " + pkg + " due to package not in the foreground"); diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index c7124314cae0..ba7583fe7f7a 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -36,9 +36,9 @@ import android.os.UserHandle; import android.os.storage.StorageManager; import android.util.ArraySet; import android.util.Log; -import android.util.StatsLog; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.PinnerService; import com.android.server.pm.dex.DexManager; @@ -444,7 +444,7 @@ public class BackgroundDexOptService extends JobService { } if (dex_opt_performed) { - StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before, + FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED, pkg, package_size_before, getPackageSize(pm, pkg), /*aggressive=*/ false); } return dex_opt_performed; diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index f962eedb5111..40ea6cfb6d4a 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -450,16 +450,6 @@ public class Installer extends SystemService { } } - public void markBootComplete(String instructionSet) throws InstallerException { - assertValidInstructionSet(instructionSet); - if (!checkBeforeRemote()) return; - try { - mInstalld.markBootComplete(instructionSet); - } catch (Exception e) { - throw InstallerException.from(e); - } - } - public void freeCache(String uuid, long targetFreeBytes, long cacheReservedBytes, int flags) throws InstallerException { if (!checkBeforeRemote()) return; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 7500fc2a6cfc..148c5adbdce0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -288,7 +288,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; -import android.util.StatsLog; import android.util.TimingsTraceLog; import android.util.Xml; import android.util.apk.ApkSignatureVerifier; @@ -310,6 +309,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.AttributeCache; @@ -2163,7 +2163,8 @@ public class PackageManagerService extends IPackageManager.Stub getPackageExternalStorageType(volume, isExternal(res.pkg)); // If the package was installed externally, log it. if (packageExternalStorageType != StorageEnums.UNKNOWN) { - StatsLog.write(StatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED, + FrameworkStatsLog.write( + FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED, packageExternalStorageType, res.pkg.getPackageName()); } } @@ -2727,7 +2728,7 @@ public class PackageManagerService extends IPackageManager.Stub t.traceBegin("get system config"); SystemConfig systemConfig = SystemConfig.getInstance(); mAvailableFeatures = systemConfig.getAvailableFeatures(); - ApplicationPackageManager.invalidateSysFeatureCache(); + ApplicationPackageManager.invalidateHasSystemFeatureCache(); t.traceEnd(); mProtectedPackages = new ProtectedPackages(mContext); @@ -22177,13 +22178,15 @@ public class PackageManagerService extends IPackageManager.Stub if (!isPreviousLocationExternal && isExternal(pkg)) { // Move from internal to external storage. - StatsLog.write(StatsLog.APP_MOVED_STORAGE_REPORTED, packageExternalStorageType, - StatsLog.APP_MOVED_STORAGE_REPORTED__MOVE_TYPE__TO_EXTERNAL, + FrameworkStatsLog.write(FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED, + packageExternalStorageType, + FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED__MOVE_TYPE__TO_EXTERNAL, packageName); } else if (isPreviousLocationExternal && !isExternal(pkg)) { // Move from external to internal storage. - StatsLog.write(StatsLog.APP_MOVED_STORAGE_REPORTED, packageExternalStorageType, - StatsLog.APP_MOVED_STORAGE_REPORTED__MOVE_TYPE__TO_INTERNAL, + FrameworkStatsLog.write(FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED, + packageExternalStorageType, + FrameworkStatsLog.APP_MOVED_STORAGE_REPORTED__MOVE_TYPE__TO_INTERNAL, packageName); } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 4bab22478e6a..4425c0acba2a 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -52,6 +52,7 @@ import android.os.UserManager; import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.IntArray; +import android.util.Log; import android.util.LongArrayQueue; import android.util.Slog; import android.util.SparseBooleanArray; @@ -94,7 +95,7 @@ import java.util.concurrent.TimeUnit; class RollbackManagerServiceImpl extends IRollbackManager.Stub { private static final String TAG = "RollbackManager"; - private static final boolean LOCAL_LOGV = false; + private static final boolean LOCAL_LOGV = Log.isLoggable(TAG, Log.VERBOSE); // Rollbacks expire after 14 days. private static final long DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS = 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 4f8a86d3f3c6..96f1219861ec 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -256,6 +256,8 @@ public class StatsPullAtomService extends SystemService { mContext = context; } + private native void nativeInit(); + /** * Use of this StatsPullAtomCallbackImpl means we avoid one class per tagId, which we would * get if we used lambdas. @@ -399,6 +401,7 @@ public class StatsPullAtomService extends SystemService { super.onBootPhase(phase); if (phase == PHASE_SYSTEM_SERVICES_READY) { BackgroundThread.getHandler().post(() -> { + nativeInit(); initializePullersState(); registerAllPullers(); registerEventListeners(); @@ -896,7 +899,6 @@ public class StatsPullAtomService extends SystemService { return data; } } - Slog.e(TAG, "no controller energy info supplied for " + receiver.getName()); } catch (TimeoutException e) { Slog.w(TAG, "timeout reading " + receiver.getName() + " stats"); } diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index e95fc4a4a938..a1e643f15a8e 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -101,11 +101,12 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { private TimestampedValue<Long> mLastAutoSystemClockTimeSet; /** - * A mapping from phoneId to a time suggestion. We typically expect one or two mappings: devices - * will have a small number of telephony devices and phoneIds are assumed to be stable. + * A mapping from slotIndex to a time suggestion. We typically expect one or two mappings: + * devices will have a small number of telephony devices and slotIndexs are assumed to be + * stable. */ @GuardedBy("this") - private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId = + private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); @GuardedBy("this") @@ -155,7 +156,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } // Perform validation / input filtering and record the validated suggestion against the - // phoneId. + // slotIndex. if (!validateAndStorePhoneSuggestion(timeSuggestion)) { return; } @@ -202,7 +203,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { ipw.println("Phone suggestion history:"); ipw.increaseIndent(); // level 2 - mSuggestionByPhoneId.dump(ipw); + mSuggestionBySlotIndex.dump(ipw); ipw.decreaseIndent(); // level 2 ipw.println("Network suggestion history:"); @@ -223,8 +224,8 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { return false; } - int phoneId = suggestion.getPhoneId(); - PhoneTimeSuggestion previousSuggestion = mSuggestionByPhoneId.get(phoneId); + int slotIndex = suggestion.getSlotIndex(); + PhoneTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex); if (previousSuggestion != null) { // We can log / discard suggestions with obvious issues with the reference time clock. if (previousSuggestion.getUtcTime() == null @@ -249,7 +250,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } // Store the latest suggestion. - mSuggestionByPhoneId.put(phoneId, suggestion); + mSuggestionBySlotIndex.put(slotIndex, suggestion); return true; } @@ -323,15 +324,15 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // // [1] https://en.wikipedia.org/wiki/NITZ // - // Generally, when there are suggestions from multiple phoneIds they should usually + // Generally, when there are suggestions from multiple slotIndexs they should usually // approximately agree. In cases where signals *are* inaccurate we don't want to vacillate - // between signals from two phoneIds. However, it is known for NITZ signals to be incorrect - // occasionally, which means we also don't want to stick forever with one phoneId. Without - // cross-referencing across sources (e.g. the current device time, NTP), or doing some kind - // of statistical analysis of consistency within and across phoneIds, we can't know which - // suggestions are more correct. + // between signals from two slotIndexs. However, it is known for NITZ signals to be + // incorrect occasionally, which means we also don't want to stick forever with one + // slotIndex. Without cross-referencing across sources (e.g. the current device time, NTP), + // or doing some kind of statistical analysis of consistency within and across slotIndexs, + // we can't know which suggestions are more correct. // - // For simplicity, we try to value recency, then consistency of phoneId. + // For simplicity, we try to value recency, then consistency of slotIndex. // // The heuristic works as follows: // Recency: The most recent suggestion from each phone is scored. The score is based on a @@ -339,20 +340,20 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // bucket, thus applying a loose reference time ordering. The suggestion with the highest // score is used. // Consistency: If there a multiple suggestions with the same score, the suggestion with the - // lowest phoneId is always taken. + // lowest slotIndex is always taken. // // In the trivial case with a single ID this will just mean that the latest received // suggestion is used. PhoneTimeSuggestion bestSuggestion = null; int bestScore = PHONE_INVALID_SCORE; - for (int i = 0; i < mSuggestionByPhoneId.size(); i++) { - Integer phoneId = mSuggestionByPhoneId.keyAt(i); - PhoneTimeSuggestion candidateSuggestion = mSuggestionByPhoneId.valueAt(i); + for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) { + Integer slotIndex = mSuggestionBySlotIndex.keyAt(i); + PhoneTimeSuggestion candidateSuggestion = mSuggestionBySlotIndex.valueAt(i); if (candidateSuggestion == null) { // Unexpected - null suggestions should never be stored. - Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for phoneId." - + " phoneId=" + phoneId); + Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for slotIndex." + + " slotIndex=" + slotIndex); continue; } else if (candidateSuggestion.getUtcTime() == null) { // Unexpected - we do not store empty suggestions. @@ -372,10 +373,10 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { bestSuggestion = candidateSuggestion; bestScore = candidateScore; } else if (bestScore == candidateScore) { - // Tie! Use the suggestion with the lowest phoneId. - int candidatePhoneId = candidateSuggestion.getPhoneId(); - int bestPhoneId = bestSuggestion.getPhoneId(); - if (candidatePhoneId < bestPhoneId) { + // Tie! Use the suggestion with the lowest slotIndex. + int candidateSlotIndex = candidateSuggestion.getSlotIndex(); + int bestSlotIndex = bestSuggestion.getSlotIndex(); + if (candidateSlotIndex < bestSlotIndex) { bestSuggestion = candidateSuggestion; } } @@ -396,7 +397,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } // The score is based on the age since receipt. Suggestions are bucketed so two - // suggestions in the same bucket from different phoneIds are scored the same. + // suggestions in the same bucket from different slotIndexs are scored the same. long ageMillis = elapsedRealtimeMillis - utcTime.getReferenceTimeMillis(); // Turn the age into a discrete value: 0 <= bucketIndex < PHONE_BUCKET_COUNT. @@ -560,8 +561,8 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ @VisibleForTesting @Nullable - public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) { - return mSuggestionByPhoneId.get(phoneId); + public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int slotIndex) { + return mSuggestionBySlotIndex.get(slotIndex); } /** diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index b4a439991dd9..b0e006908231 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -172,12 +172,12 @@ public class TimeZoneDetectorStrategy { private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */); /** - * A mapping from phoneId to a phone time zone suggestion. We typically expect one or two - * mappings: devices will have a small number of telephony devices and phoneIds are assumed to + * A mapping from slotIndex to a phone time zone suggestion. We typically expect one or two + * mappings: devices will have a small number of telephony devices and slotIndexs are assumed to * be stable. */ @GuardedBy("this") - private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionByPhoneId = + private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE); /** @@ -205,7 +205,7 @@ public class TimeZoneDetectorStrategy { /** * Suggests a time zone for the device, or withdraws a previous suggestion if * {@link PhoneTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to a - * specific {@link PhoneTimeZoneSuggestion#getPhoneId() phone}. + * specific {@link PhoneTimeZoneSuggestion#getSlotIndex() phone}. * See {@link PhoneTimeZoneSuggestion} for an explanation of the metadata associated with a * suggestion. The strategy uses suggestions to decide whether to modify the device's time zone * setting and what to set it to. @@ -221,8 +221,8 @@ public class TimeZoneDetectorStrategy { QualifiedPhoneTimeZoneSuggestion scoredSuggestion = new QualifiedPhoneTimeZoneSuggestion(suggestion, score); - // Store the suggestion against the correct phoneId. - mSuggestionByPhoneId.put(suggestion.getPhoneId(), scoredSuggestion); + // Store the suggestion against the correct slotIndex. + mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion); // Now perform auto time zone detection. The new suggestion may be used to modify the time // zone setting. @@ -384,8 +384,9 @@ public class TimeZoneDetectorStrategy { // and find the best. Note that we deliberately do not look at age: the caller can // rate-limit so age is not a strong indicator of confidence. Instead, the callers are // expected to withdraw suggestions they no longer have confidence in. - for (int i = 0; i < mSuggestionByPhoneId.size(); i++) { - QualifiedPhoneTimeZoneSuggestion candidateSuggestion = mSuggestionByPhoneId.valueAt(i); + for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) { + QualifiedPhoneTimeZoneSuggestion candidateSuggestion = + mSuggestionBySlotIndex.valueAt(i); if (candidateSuggestion == null) { // Unexpected continue; @@ -396,10 +397,10 @@ public class TimeZoneDetectorStrategy { } else if (candidateSuggestion.score > bestSuggestion.score) { bestSuggestion = candidateSuggestion; } else if (candidateSuggestion.score == bestSuggestion.score) { - // Tie! Use the suggestion with the lowest phoneId. - int candidatePhoneId = candidateSuggestion.suggestion.getPhoneId(); - int bestPhoneId = bestSuggestion.suggestion.getPhoneId(); - if (candidatePhoneId < bestPhoneId) { + // Tie! Use the suggestion with the lowest slotIndex. + int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex(); + int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex(); + if (candidateSlotIndex < bestSlotIndex) { bestSuggestion = candidateSuggestion; } } @@ -455,7 +456,7 @@ public class TimeZoneDetectorStrategy { ipw.println("Phone suggestion history:"); ipw.increaseIndent(); // level 2 - mSuggestionByPhoneId.dump(ipw); + mSuggestionBySlotIndex.dump(ipw); ipw.decreaseIndent(); // level 2 ipw.decreaseIndent(); // level 1 ipw.flush(); @@ -465,8 +466,8 @@ public class TimeZoneDetectorStrategy { * A method used to inspect strategy state during tests. Not intended for general use. */ @VisibleForTesting - public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int phoneId) { - return mSuggestionByPhoneId.get(phoneId); + public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int slotIndex) { + return mSuggestionBySlotIndex.get(slotIndex); } /** diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b05c25082f85..7af097b3f260 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -196,6 +196,7 @@ import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.TaskPersister.DEBUG; import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; @@ -311,6 +312,8 @@ import com.android.server.protolog.common.ProtoLog; import com.android.server.uri.UriPermissionOwner; import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot; import com.android.server.wm.ActivityStack.ActivityState; +import com.android.server.wm.SurfaceAnimator.AnimationType; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import com.android.server.wm.WindowManagerService.H; import com.android.server.wm.utils.InsetUtils; @@ -4080,7 +4083,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - boolean isVoiceInteraction, @Nullable Runnable animationFinishedCallback) { + boolean isVoiceInteraction, + @Nullable OnAnimationFinishedCallback animationFinishedCallback) { if (mUseTransferredAnimation) { return false; } @@ -4158,7 +4162,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // We aren't delayed anything, but exiting windows rely on the animation finished // callback being called in case the ActivityRecord was pretending to be delayed, // which we might have done because we were in closing/opening apps list. - onAnimationFinished(); + onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */); if (visible) { // The token was made immediately visible, there will be no entrance animation. // We need to inform the client the enter animation was finished. @@ -6054,8 +6058,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @Override - protected void onAnimationFinished() { - super.onAnimationFinished(); + protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) { + super.onAnimationFinished(type, anim); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AR#onAnimationFinished"); mTransit = TRANSIT_UNSET; diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index ddf0117987dd..0d72d84d1649 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -341,6 +341,9 @@ class ActivityStack extends Task implements BoundsAnimationTarget { private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1; + // TODO(task-hierarchy): remove when tiles can be actual parents + TaskTile mTile = null; + private final Handler mHandler; private class ActivityStackHandler extends Handler { @@ -638,11 +641,20 @@ class ActivityStack extends Task implements BoundsAnimationTarget { } @Override + public void resolveOverrideConfiguration(Configuration newParentConfig) { + super.resolveOverrideConfiguration(newParentConfig); + if (mTile != null) { + // If this is a virtual child of a tile, simulate the parent-child relationship + mTile.updateResolvedConfig(getResolvedOverrideConfiguration()); + } + } + + @Override public void onConfigurationChanged(Configuration newParentConfig) { // Calling Task#onConfigurationChanged() for leaf task since the ops in this method are // particularly for ActivityStack, like preventing bounds changes when inheriting certain // windowing mode. - if (!isRootTask()) { + if (!isRootTask() || this instanceof TaskTile) { super.onConfigurationChanged(newParentConfig); return; } @@ -3944,7 +3956,6 @@ class ActivityStack extends Task implements BoundsAnimationTarget { ? ((WindowContainer) newParent).getDisplayContent() : null; final DisplayContent oldDisplay = oldParent != null ? ((WindowContainer) oldParent).getDisplayContent() : null; - super.onParentChanged(newParent, oldParent); if (display != null && inSplitScreenPrimaryWindowingMode() @@ -3963,6 +3974,11 @@ class ActivityStack extends Task implements BoundsAnimationTarget { if (oldDisplay != null && oldDisplay.isRemoving()) { postReparent(); } + if (mTile != null && getSurfaceControl() != null) { + // by now, the TaskStack should already have been reparented, so we can reparent its + // surface here + reparentSurfaceControl(getPendingTransaction(), mTile.getSurfaceControl()); + } } void reparent(DisplayContent newParent, boolean onTop) { @@ -4000,7 +4016,16 @@ class ActivityStack extends Task implements BoundsAnimationTarget { @Override void getRelativeDisplayedPosition(Point outPos) { - super.getRelativeDisplayedPosition(outPos); + // check for tile which is "virtually" a parent. + if (mTile != null) { + final Rect dispBounds = getDisplayedBounds(); + outPos.set(dispBounds.left, dispBounds.top); + final Rect parentBounds = mTile.getBounds(); + outPos.offset(-parentBounds.left, -parentBounds.top); + } else { + super.getRelativeDisplayedPosition(outPos); + } + final int outset = getStackOutset(); outPos.x -= outset; outPos.y -= outset; @@ -4010,6 +4035,16 @@ class ActivityStack extends Task implements BoundsAnimationTarget { if (mSurfaceControl == null) { return; } + if (mTile != null) { + // Tile controls crop, so the app needs to be able to draw its background outside of + // the stack bounds for when the tile crop gets bigger than the stack. + if (mLastSurfaceSize.equals(0, 0)) { + return; + } + transaction.setWindowCrop(mSurfaceControl, null); + mLastSurfaceSize.set(0, 0); + return; + } final Rect stackBounds = getDisplayedBounds(); int width = stackBounds.width(); @@ -4033,6 +4068,9 @@ class ActivityStack extends Task implements BoundsAnimationTarget { @Override void onDisplayChanged(DisplayContent dc) { + if (mTile != null && dc != mTile.getDisplay()) { + mTile.removeChild(this); + } super.onDisplayChanged(dc); if (isRootTask()) { updateSurfaceBounds(); @@ -4848,6 +4886,42 @@ class ActivityStack extends Task implements BoundsAnimationTarget { return shouldSleepActivities() || mAtmService.mShuttingDown; } + TaskTile getTile() { + return mTile; + } + + /** + * Don't call this directly. instead use {@link TaskTile#addChild} or + * {@link TaskTile#removeChild}. + */ + void setTile(TaskTile tile) { + TaskTile origTile = mTile; + mTile = tile; + final ConfigurationContainer parent = getParent(); + if (parent != null) { + onConfigurationChanged(parent.getConfiguration()); + } + + // Reparent to tile surface or back to original parent + if (getSurfaceControl() == null) { + return; + } + if (mTile != null) { + reparentSurfaceControl(getPendingTransaction(), mTile.getSurfaceControl()); + } else if (mTile == null && origTile != null) { + reparentSurfaceControl(getPendingTransaction(), getParentSurfaceControl()); + } + } + + @Override + void removeImmediately() { + // TODO(task-hierarchy): remove this override when tiles are in hierarchy + if (mTile != null) { + mTile.removeChild(this); + } + super.removeImmediately(); + } + @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index f019013eedf1..d6e707795d50 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -144,6 +144,7 @@ import android.app.IApplicationThread; import android.app.IAssistDataReceiver; import android.app.INotificationManager; import android.app.IRequestFinishCallback; +import android.app.ITaskOrganizerController; import android.app.ITaskStackListener; import android.app.Notification; import android.app.NotificationManager; @@ -225,10 +226,8 @@ import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.IRecentsAnimationRunner; -import android.view.ITaskOrganizer; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; -import android.view.WindowContainerTransaction; import android.view.WindowManager; import com.android.internal.R; @@ -292,7 +291,6 @@ import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -344,10 +342,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** This activity is being relaunched due to a free-resize operation. */ public static final int RELAUNCH_REASON_FREE_RESIZE = 2; - /** Flag indicating that an applied transaction may have effected lifecycle */ - private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1; - private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1; - Context mContext; /** @@ -669,8 +663,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** * Stores the registration and state of TaskOrganizers in use. */ - TaskOrganizerController mTaskOrganizerController = - new TaskOrganizerController(this, mGlobalLock); + TaskOrganizerController mTaskOrganizerController = new TaskOrganizerController(this); private int mDeviceOwnerUid = Process.INVALID_UID; @@ -1286,15 +1279,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public final void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { - enforceCallerIsRecentsOrHasPermission( - MANAGE_ACTIVITY_STACKS, "registerTaskOrganizer()"); - synchronized (mGlobalLock) { - mTaskOrganizerController.registerTaskOrganizer(organizer, windowingMode); - } - } - - @Override public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) { int callingUid = Binder.getCallingUid(); if (UserHandle.getAppId(callingUid) != SYSTEM_UID) { @@ -3304,116 +3288,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - private int sanitizeAndApplyChange(WindowContainer container, - WindowContainerTransaction.Change change) { - if (!(container instanceof Task || container instanceof ActivityStack)) { - throw new RuntimeException("Invalid token in task transaction"); - } - // The "client"-facing API should prevent bad changes; however, just in case, sanitize - // masks here. - int configMask = change.getConfigSetMask(); - int windowMask = change.getWindowSetMask(); - configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION - | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; - windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS; - int effects = 0; - if (configMask != 0) { - Configuration c = new Configuration(container.getRequestedOverrideConfiguration()); - c.setTo(change.getConfiguration(), configMask, windowMask); - container.onRequestedOverrideConfigurationChanged(c); - // TODO(b/145675353): remove the following once we could apply new bounds to the - // pinned stack together with its children. - resizePinnedStackIfNeeded(container, configMask, windowMask, c); - effects |= TRANSACT_EFFECTS_CLIENT_CONFIG; - } - if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) { - if (container.setFocusable(change.getFocusable())) { - effects |= TRANSACT_EFFECTS_LIFECYCLE; - } - } - return effects; - } - - private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask, - int windowMask, Configuration config) { - if ((container instanceof ActivityStack) - && ((configMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0) - && ((windowMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)) { - final ActivityStack stack = (ActivityStack) container; - if (stack.inPinnedWindowingMode()) { - stack.resize(config.windowConfiguration.getBounds(), - null /* tempTaskBounds */, null /* tempTaskInsetBounds */, - PRESERVE_WINDOWS, true /* deferResume */); - } - } - } - - private int applyWindowContainerChange(WindowContainer wc, - WindowContainerTransaction.Change c) { - int effects = sanitizeAndApplyChange(wc, c); - - Rect enterPipBounds = c.getEnterPipBounds(); - if (enterPipBounds != null) { - Task tr = (Task) wc; - mStackSupervisor.updatePictureInPictureMode(tr, - enterPipBounds, true); - } - return effects; - } - - @Override - public void applyContainerTransaction(WindowContainerTransaction t) { - mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()"); - if (t == null) { - return; - } - long ident = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - int effects = 0; - deferWindowLayout(); - try { - ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>(); - Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = - t.getChanges().entrySet().iterator(); - while (entries.hasNext()) { - final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = - entries.next(); - final WindowContainer wc = WindowContainer.RemoteToken.fromBinder( - entry.getKey()).getContainer(); - int containerEffect = applyWindowContainerChange(wc, entry.getValue()); - effects |= containerEffect; - // Lifecycle changes will trigger ensureConfig for everything. - if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0 - && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { - haveConfigChanges.add(wc); - } - } - if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { - // Already calls ensureActivityConfig - mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); - } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { - final PooledConsumer f = PooledLambda.obtainConsumer( - ActivityRecord::ensureActivityConfiguration, - PooledLambda.__(ActivityRecord.class), 0, - false /* preserveWindow */); - try { - for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { - haveConfigChanges.valueAt(i).forAllActivities(f); - } - } finally { - f.recycle(); - } - } - } finally { - continueWindowLayout(); - } - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - @Override public boolean releaseActivityInstance(IBinder token) { synchronized (mGlobalLock) { @@ -4442,6 +4316,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public ITaskOrganizerController getTaskOrganizerController() { + mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, + "getTaskOrganizerController()"); + return mTaskOrganizerController; + } + /** * Check that we have the features required for VR-related API calls, and throw an exception if * not. diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index 1be3d614de33..0519b80c732a 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -21,6 +21,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.animation.Animation; +import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import java.io.PrintWriter; @@ -47,9 +48,10 @@ interface AnimationAdapter { * component running the animation after {@code finishCallback} has been * invoked, or after the animation was cancelled. * @param t The Transaction to apply the initial frame of the animation. + * @param type The type of the animation. * @param finishCallback The callback to be invoked when the animation has finished. */ - void startAnimation(SurfaceControl animationLeash, Transaction t, + void startAnimation(SurfaceControl animationLeash, Transaction t, @AnimationType int type, OnAnimationFinishedCallback finishCallback); /** diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 0798a910c860..f72020ea8988 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -377,9 +377,9 @@ public class AppTransitionController { transitioningDecendants.add(app); } } - wc.applyAnimation(animLp, transit, visible, voiceInteraction, () -> { + wc.applyAnimation(animLp, transit, visible, voiceInteraction, (type, anim) -> { for (int j = 0; j < transitioningDecendants.size(); ++j) { - transitioningDecendants.get(j).onAnimationFinished(); + transitioningDecendants.get(j).onAnimationFinished(type, anim); } }); } diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index 16aff9c10c7d..537ca08f49fc 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -20,6 +20,7 @@ import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; import static com.android.server.wm.AlphaAnimationSpecProto.FROM; import static com.android.server.wm.AlphaAnimationSpecProto.TO; import static com.android.server.wm.AnimationSpecProto.ALPHA; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; import android.annotation.Nullable; import android.graphics.Rect; @@ -29,6 +30,8 @@ import android.view.Surface; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.wm.SurfaceAnimator.AnimationType; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import java.io.PrintWriter; @@ -135,7 +138,7 @@ class Dimmer { mDimLayer = dimLayer; mDimming = true; final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer); - mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, () -> { + mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> { if (!mDimming) { dimAnimatable.removeSurface(); } @@ -157,8 +160,8 @@ class Dimmer { @VisibleForTesting interface SurfaceAnimatorStarter { void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, - AnimationAdapter anim, boolean hidden, - @Nullable Runnable animationFinishedCallback); + AnimationAdapter anim, boolean hidden, @AnimationType int type, + @Nullable OnAnimationFinishedCallback animationFinishedCallback); } Dimmer(WindowContainer host) { @@ -345,7 +348,7 @@ class Dimmer { mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter( new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */, - null /* animationFinishedCallback */); + ANIMATION_TYPE_DIMMER, null /* animationFinishedCallback */); } private long getDimDuration(WindowContainer container) { diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java new file mode 100644 index 000000000000..b3edc91a4129 --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; + +import static com.android.internal.util.Preconditions.checkState; +import static com.android.server.wm.DisplayAreaChildProto.DISPLAY_AREA; +import static com.android.server.wm.DisplayAreaChildProto.UNKNOWN; +import static com.android.server.wm.DisplayAreaChildProto.WINDOW; +import static com.android.server.wm.DisplayAreaProto.CHILDREN; +import static com.android.server.wm.DisplayAreaProto.NAME; +import static com.android.server.wm.DisplayAreaProto.WINDOW_CONTAINER; +import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; + +import android.graphics.Rect; +import android.util.proto.ProtoOutputStream; + +import com.android.server.policy.WindowManagerPolicy; +import com.android.server.protolog.common.ProtoLog; + +import java.util.Comparator; +import java.util.function.Predicate; + +/** + * Container for grouping WindowContainer below DisplayContent. + * + * DisplayAreas are managed by a {@link DisplayAreaPolicy}, and can override configurations and + * can be leashed. + * + * DisplayAreas can contain nested DisplayAreas. + * + * DisplayAreas come in three flavors, to ensure that windows have the right Z-Order: + * - BELOW_TASKS: Can only contain BELOW_TASK DisplayAreas and WindowTokens that go below tasks. + * - ABOVE_TASKS: Can only contain ABOVE_TASK DisplayAreas and WindowTokens that go above tasks. + * - ANY: Can contain any kind of DisplayArea, and any kind of WindowToken or the Task container. + * Cannot have a sibling that is of type ANY. + * + * @param <T> type of the children of the DisplayArea. + */ +public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { + + protected final Type mType; + private final String mName; + + DisplayArea(WindowManagerService wms, Type type, String name) { + super(wms); + // TODO(display-area): move this up to ConfigurationContainer + mOrientation = SCREEN_ORIENTATION_UNSET; + mType = type; + mName = name; + } + + @Override + void onChildPositionChanged(WindowContainer child) { + super.onChildPositionChanged(child); + + // Verify that we have proper ordering + Type.checkChild(mType, Type.typeOf(child)); + + if (child instanceof ActivityStack) { + // TODO(display-area): ActivityStacks are type ANY, but are allowed to have siblings. + // They might need a separate type. + return; + } + + for (int i = 1; i < getChildCount(); i++) { + final WindowContainer top = getChildAt(i - 1); + final WindowContainer bottom = getChildAt(i); + if (child == top || child == bottom) { + Type.checkSiblings(Type.typeOf(top), Type.typeOf(bottom)); + } + } + } + + @Override + boolean fillsParent() { + return true; + } + + @Override + String getName() { + return mName; + } + + @Override + public final void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) { + final long token = proto.start(fieldId); + super.dumpDebug(proto, WINDOW_CONTAINER, logLevel); + proto.write(NAME, mName); + for (int i = 0; i < getChildCount(); i++) { + final long childToken = proto.start(CHILDREN); + final T child = getChildAt(i); + if (child instanceof ActivityStack) { + // TODO(display-area): Dump stacks & tasks here, instead of in DisplayContent's + // dumpDebug. For now, skip them here to avoid dumping them as UNKNOWN. + } else if (child instanceof WindowToken) { + ((WindowToken) child).dumpDebug(proto, WINDOW, logLevel); + } else if (child instanceof DisplayArea) { + child.dumpDebug(proto, DISPLAY_AREA, logLevel); + } else { + proto.write(UNKNOWN, child.getClass().getSimpleName()); + } + proto.end(childToken); + } + proto.end(token); + } + + /** + * DisplayArea that contains WindowTokens, and orders them according to their type. + */ + public static class Tokens extends DisplayArea<WindowToken> { + int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + + private final Comparator<WindowToken> mWindowComparator = + Comparator.comparingInt(WindowToken::getWindowLayerFromType); + + private final Predicate<WindowState> mGetOrientingWindow = w -> { + final WindowManagerPolicy policy = mWmService.mPolicy; + if (policy.isKeyguardHostWindow(w.mAttrs)) { + if (mWmService.mKeyguardGoingAway) { + return false; + } + // Consider unoccluding only when all unknown visibilities have been + // resolved, as otherwise we just may be starting another occluding activity. + final boolean isUnoccluding = + mDisplayContent.mAppTransition.getAppTransition() + == TRANSIT_KEYGUARD_UNOCCLUDE + && mDisplayContent.mUnknownAppVisibilityController.allResolved(); + // If keyguard is showing, or we're unoccluding, force the keyguard's orientation, + // even if SystemUI hasn't updated the attrs yet. + if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) { + return true; + } + } + final int req = w.mAttrs.screenOrientation; + if (req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND + || req == SCREEN_ORIENTATION_UNSET) { + return false; + } + return true; + }; + + Tokens(WindowManagerService wms, Type type, String name) { + super(wms, type, name); + } + + void addChild(WindowToken token) { + addChild(token, mWindowComparator); + } + + @Override + int getOrientation(int candidate) { + // Find a window requesting orientation. + final WindowState win = getWindow(mGetOrientingWindow); + + if (win == null) { + return candidate; + } + int req = win.mAttrs.screenOrientation; + ProtoLog.v(WM_DEBUG_ORIENTATION, "%s forcing orientation to %d for display id=%d", + win, req, mDisplayContent.getDisplayId()); + if (mWmService.mPolicy.isKeyguardHostWindow(win.mAttrs)) { + // SystemUI controls the Keyguard orientation asynchronously, and mAttrs may be + // stale. We record / use the last known override. + if (req != SCREEN_ORIENTATION_UNSET && req != SCREEN_ORIENTATION_UNSPECIFIED) { + mLastKeyguardForcedOrientation = req; + } else { + req = mLastKeyguardForcedOrientation; + } + } + return req; + } + } + + /** + * Top-most DisplayArea under DisplayContent. + */ + public static class Root extends DisplayArea<DisplayArea> { + private final Dimmer mDimmer = new Dimmer(this); + private final Rect mTmpDimBoundsRect = new Rect(); + + Root(WindowManagerService wms) { + super(wms, Type.ANY, "DisplayArea.Root"); + } + + @Override + Dimmer getDimmer() { + return mDimmer; + } + + @Override + void prepareSurfaces() { + mDimmer.resetDimStates(); + super.prepareSurfaces(); + getBounds(mTmpDimBoundsRect); + + if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { + scheduleAnimation(); + } + } + } + + enum Type { + /** Can only contain WindowTokens above the APPLICATION_LAYER. */ + ABOVE_TASKS, + /** Can only contain WindowTokens below the APPLICATION_LAYER. */ + BELOW_TASKS, + /** Can contain anything. */ + ANY; + + static void checkSiblings(Type bottom, Type top) { + checkState(!(bottom == ANY && top == ANY), "ANY cannot be a sibling of ANY"); + checkState(!(bottom != BELOW_TASKS && top == BELOW_TASKS), + bottom + " must be above BELOW_TASKS"); + checkState(!(bottom == ABOVE_TASKS && top != ABOVE_TASKS), + top + " must be below ABOVE_TASKS"); + } + + static void checkChild(Type parent, Type child) { + switch (parent) { + case ABOVE_TASKS: + checkState(child == ABOVE_TASKS, "ABOVE_TASKS can only contain ABOVE_TASKS"); + break; + case BELOW_TASKS: + checkState(child == BELOW_TASKS, "BELOW_TASKS can only contain BELOW_TASKS"); + break; + } + } + + static Type typeOf(WindowContainer c) { + if (c instanceof DisplayArea) { + return ((DisplayArea) c).mType; + } else if (c instanceof WindowToken && !(c instanceof ActivityRecord)) { + return typeOf((WindowToken) c); + } else if (c instanceof ActivityStack) { + return ANY; + } else { + throw new IllegalArgumentException("Unknown container: " + c); + } + } + + private static Type typeOf(WindowToken c) { + return c.getWindowLayerFromType() < APPLICATION_LAYER ? BELOW_TASKS : ABOVE_TASKS; + } + } +} diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java new file mode 100644 index 000000000000..06e7b48a1f04 --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; + +import com.android.server.wm.DisplayContent.TaskContainers; + +/** + * Policy that manages DisplayAreas. + */ +public abstract class DisplayAreaPolicy { + protected final WindowManagerService mWmService; + protected final DisplayContent mContent; + + /** + * The root DisplayArea. Attach all DisplayAreas to this area (directly or indirectly). + */ + protected final DisplayArea.Root mRoot; + + /** + * The IME container. The IME's windows are automatically added to this container. + */ + protected final DisplayArea<? extends WindowContainer> mImeContainer; + + /** + * The Tasks container. Tasks etc. are automatically added to this container. + */ + protected final TaskContainers mTaskContainers; + + DisplayAreaPolicy(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) { + mWmService = wmService; + mContent = content; + mRoot = root; + mImeContainer = imeContainer; + mTaskContainers = taskContainers; + } + + /** + * Called to ask the policy to set up the DisplayArea hierarchy. At a minimum this must: + * + * - attach mImeContainer to mRoot (or one of its descendants) + * - attach mTaskStacks to mRoot (or one of its descendants) + * + * Additionally, this is the right place to set up any other DisplayAreas as desired. + */ + public abstract void attachDisplayAreas(); + + /** + * Called to ask the policy to attach the given WindowToken to the DisplayArea hierarchy. + * + * This must attach the token to mRoot (or one of its descendants). + */ + public abstract void addWindow(WindowToken token); + + /** + * Default policy that has no special features. + */ + public static class Default extends DisplayAreaPolicy { + + public Default(WindowManagerService wmService, DisplayContent content, + DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + TaskContainers taskContainers) { + super(wmService, content, root, imeContainer, taskContainers); + } + + private final DisplayArea.Tokens mBelow = new DisplayArea.Tokens(mWmService, + DisplayArea.Type.BELOW_TASKS, "BelowTasks"); + private final DisplayArea<DisplayArea> mAbove = new DisplayArea<>(mWmService, + DisplayArea.Type.ABOVE_TASKS, "AboveTasks"); + private final DisplayArea.Tokens mAboveBelowIme = new DisplayArea.Tokens(mWmService, + DisplayArea.Type.ABOVE_TASKS, "AboveTasksBelowIme"); + private final DisplayArea.Tokens mAboveAboveIme = new DisplayArea.Tokens(mWmService, + DisplayArea.Type.ABOVE_TASKS, "AboveTasksAboveIme"); + + @Override + public void attachDisplayAreas() { + mRoot.addChild(mBelow, 0); + mRoot.addChild(mTaskContainers, 1); + mRoot.addChild(mAbove, 2); + + mAbove.addChild(mAboveBelowIme, 0); + mAbove.addChild(mImeContainer, 1); + mAbove.addChild(mAboveAboveIme, 2); + } + + @Override + public void addWindow(WindowToken token) { + switch (DisplayArea.Type.typeOf(token)) { + case ABOVE_TASKS: + if (token.getWindowLayerFromType() + < mWmService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)) { + mAboveBelowIme.addChild(token); + } else { + mAboveAboveIme.addChild(token); + } + break; + case BELOW_TASKS: + mBelow.addChild(token); + break; + default: + throw new IllegalArgumentException("don't know how to sort " + token); + } + } + } +} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 6e479b2b53e0..81af0fe24ba7 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -80,7 +80,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; -import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_TASK_OPEN; import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; @@ -98,9 +97,7 @@ import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK; -import static com.android.server.wm.DisplayContentProto.ABOVE_APP_WINDOWS; import static com.android.server.wm.DisplayContentProto.APP_TRANSITION; -import static com.android.server.wm.DisplayContentProto.BELOW_APP_WINDOWS; import static com.android.server.wm.DisplayContentProto.CHANGING_APPS; import static com.android.server.wm.DisplayContentProto.CLOSING_APPS; import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES; @@ -109,9 +106,9 @@ import static com.android.server.wm.DisplayContentProto.DOCKED_STACK_DIVIDER_CON import static com.android.server.wm.DisplayContentProto.DPI; import static com.android.server.wm.DisplayContentProto.FOCUSED_APP; import static com.android.server.wm.DisplayContentProto.ID; -import static com.android.server.wm.DisplayContentProto.IME_WINDOWS; import static com.android.server.wm.DisplayContentProto.OPENING_APPS; import static com.android.server.wm.DisplayContentProto.OVERLAY_WINDOWS; +import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA; import static com.android.server.wm.DisplayContentProto.ROTATION; import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION; import static com.android.server.wm.DisplayContentProto.STACKS; @@ -207,6 +204,7 @@ import android.view.DisplayInfo; import android.view.Gravity; import android.view.IDisplayWindowInsetsController; import android.view.ISystemGestureExclusionListener; +import android.view.ITaskOrganizer; import android.view.IWindow; import android.view.InputChannel; import android.view.InputDevice; @@ -295,14 +293,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** The containers below are the only child containers {@link #mWindowContainers} can have. */ // Contains all window containers that are related to apps (Activities) private final TaskContainers mTaskContainers = new TaskContainers(mWmService); - // Contains all non-app window containers that should be displayed above the app containers - // (e.g. Status bar) - private final AboveAppWindowContainers mAboveAppWindowsContainers = - new AboveAppWindowContainers("mAboveAppWindowsContainers", mWmService); - // Contains all non-app window containers that should be displayed below the app containers - // (e.g. Wallpaper). - private final NonAppWindowContainers mBelowAppWindowsContainers = - new NonAppWindowContainers("mBelowAppWindowsContainers", mWmService); + // Contains all IME window containers. Note that the z-ordering of the IME windows will depend // on the IME target. We mainly have this container grouping so we can keep track of all the IME // window containers together and move them in-sync if/when needed. We use a subclass of @@ -310,6 +301,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // TODO(display-area): is "no magnification" in the comment still true? private final ImeContainer mImeWindowsContainers = new ImeContainer(mWmService); + private final DisplayArea.Root mRootDisplayArea = new DisplayArea.Root(mWmService); + + private final DisplayAreaPolicy mDisplayAreaPolicy = new DisplayAreaPolicy.Default( + mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers); + private WindowState mTmpWindow; private WindowState mTmpWindow2; private boolean mUpdateImeTarget; @@ -401,14 +397,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private int mCurrentOverrideConfigurationChanges; /** - * Last orientation forced by the keyguard. It is applied when keyguard is shown and is not - * occluded. - * - * @see NonAppWindowContainers#getOrientation() - */ - private int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; - - /** * The maximum aspect ratio (longerSide/shorterSide) that is treated as close-to-square. The * orientation requests from apps would be ignored if the display is close-to-square. */ @@ -664,6 +652,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final RootWindowContainer.FindTaskResult mTmpFindTaskResult = new RootWindowContainer.FindTaskResult(); + // When non-null, new stacks get put into this tile. + TaskTile mLaunchTile = null; + private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; final ActivityRecord activity = w.mActivityRecord; @@ -1104,18 +1095,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Add non-app token to container hierarchy on the display. App tokens are added through // the parent container managing them (e.g. Tasks). switch (token.windowType) { - case TYPE_WALLPAPER: - mBelowAppWindowsContainers.addChild(token); - break; case TYPE_INPUT_METHOD: case TYPE_INPUT_METHOD_DIALOG: mImeWindowsContainers.addChild(token); break; case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY: + // TODO(display-area): Migrate to DisplayArea mOverlayContainers.addChild(token); break; default: - mAboveAppWindowsContainers.addChild(token); + mDisplayAreaPolicy.addWindow(token); break; } } @@ -2129,13 +2118,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return getLastOrientation(); } } - final int orientation = mAboveAppWindowsContainers.getOrientation(); - if (orientation != SCREEN_ORIENTATION_UNSET) { - return orientation; - } - - // Top system windows are not requesting an orientation. Start searching from apps. - return mTaskContainers.getOrientation(); + return mRootDisplayArea.getOrientation(); } void updateDisplayInfo() { @@ -2782,23 +2765,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final long token = proto.start(fieldId); super.dumpDebug(proto, WINDOW_CONTAINER, logLevel); proto.write(ID, mDisplayId); + mRootDisplayArea.dumpDebug(proto, ROOT_DISPLAY_AREA, logLevel); for (int stackNdx = mTaskContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = mTaskContainers.getChildAt(stackNdx); stack.dumpDebugInnerStackOnly(proto, STACKS, logLevel); } mDividerControllerLocked.dumpDebug(proto, DOCKED_STACK_DIVIDER_CONTROLLER); - for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) { - final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i); - windowToken.dumpDebug(proto, ABOVE_APP_WINDOWS, logLevel); - } - for (int i = mBelowAppWindowsContainers.getChildCount() - 1; i >= 0; --i) { - final WindowToken windowToken = mBelowAppWindowsContainers.getChildAt(i); - windowToken.dumpDebug(proto, BELOW_APP_WINDOWS, logLevel); - } - for (int i = mImeWindowsContainers.getChildCount() - 1; i >= 0; --i) { - final WindowToken windowToken = mImeWindowsContainers.getChildAt(i); - windowToken.dumpDebug(proto, IME_WINDOWS, logLevel); - } for (int i = mOverlayContainers.getChildCount() - 1; i >= 0; --i) { final WindowToken windowToken = mOverlayContainers.getChildAt(i); windowToken.dumpDebug(proto, OVERLAY_WINDOWS, logLevel); @@ -4212,7 +4184,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * Window container class that contains all containers on this display relating to Apps. * I.e Activities. */ - private final class TaskContainers extends DisplayChildWindowContainer<ActivityStack> { + final class TaskContainers extends DisplayArea<ActivityStack> { /** * A control placed at the appropriate level for transitions to occur. */ @@ -4222,16 +4194,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** * Given that the split-screen divider does not have an AppWindowToken, it - * will have to live inside of a "NonAppWindowContainer", in particular - * {@link DisplayContent#mAboveAppWindowsContainers}. However, in visual Z order + * will have to live inside of a "NonAppWindowContainer". However, in visual Z order * it will need to be interleaved with some of our children, appearing on top of * both docked stacks but underneath any assistant stacks. * * To solve this problem we have this anchor control, which will always exist so * we can always assign it the correct value in our {@link #assignChildLayers}. - * Likewise since it always exists, {@link AboveAppWindowContainers} can always + * Likewise since it always exists, we can always * assign the divider a layer relative to it. This way we prevent linking lifecycle - * events between the two containers. + * events between tasks and the divider window. */ SurfaceControl mSplitScreenDividerAnchor = null; @@ -4242,7 +4213,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private ActivityStack mRootSplitScreenPrimaryTask = null; TaskContainers(WindowManagerService service) { - super(service); + super(service, Type.ANY, "TaskContainers"); } /** @@ -4275,8 +4246,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @VisibleForTesting ActivityStack getTopStack() { - return mTaskContainers.getChildCount() > 0 - ? mTaskContainers.getChildAt(mTaskContainers.getChildCount() - 1) : null; + // TODO(task-hierarchy): Just grab index -1 once tiles are in hierarchy. + for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) { + final ActivityStack child = mTaskContainers.getChildAt(i); + if (child instanceof TaskTile) { + continue; + } + return child; + } + return null; } int getIndexOf(ActivityStack stack) { @@ -4318,6 +4296,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } private void addStackReferenceIfNeeded(ActivityStack stack) { + // TODO(task-hierarchy): Remove when tiles are in hierarchy. + if (stack instanceof TaskTile) { + return; + } if (stack.isActivityTypeHome()) { if (mRootHomeTask != null) { if (!stack.isDescendantOf(mRootHomeTask)) { @@ -4735,31 +4717,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mSplitScreenDividerAnchor = null; } } - } - - private final class AboveAppWindowContainers extends NonAppWindowContainers { - AboveAppWindowContainers(String name, WindowManagerService service) { - super(name, service); - } @Override - void assignChildLayers(SurfaceControl.Transaction t) { - boolean needAssignIme = mImeWindowsContainers.getSurfaceControl() != null; - for (int j = 0; j < mChildren.size(); ++j) { - final WindowToken wt = mChildren.get(j); - - wt.assignLayer(t, j); - wt.assignChildLayers(t); - - int layer = mWmService.mPolicy.getWindowLayerFromTypeLw( - wt.windowType, wt.mOwnerCanManageAppTokens); - - if (needAssignIme && layer >= mWmService.mPolicy.getWindowLayerFromTypeLw( - TYPE_INPUT_METHOD_DIALOG, true)) { - mImeWindowsContainers.assignRelativeLayer(t, wt.getSurfaceControl(), -1); - needAssignIme = false; - } + void onChildPositionChanged(WindowContainer child) { + // TODO(task-hierarchy): Move functionality to TaskTile when it's a proper parent. + TaskTile tile = ((ActivityStack) child).getTile(); + if (tile == null) { + return; } + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + tile, false /* force */); } } @@ -4774,13 +4741,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override void assignChildLayers(SurfaceControl.Transaction t) { mImeWindowsContainers.setNeedsLayer(); - mBelowAppWindowsContainers.assignLayer(t, 0); - mTaskContainers.assignLayer(t, 1); - mAboveAppWindowsContainers.assignLayer(t, 2); - final WindowState imeTarget = mInputMethodTarget; - boolean needAssignIme = true; + mRootDisplayArea.assignLayer(t, 0); + final WindowState imeTarget = mInputMethodTarget; // In the case where we have an IME target that is not in split-screen mode IME // assignment is easy. We just need the IME to go directly above the target. This way // children of the target will naturally go above the IME and everyone is happy. @@ -4813,11 +4777,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Above we have assigned layers to our children, now we ask them to assign // layers to their children. - mBelowAppWindowsContainers.assignChildLayers(t); - mTaskContainers.assignChildLayers(t); - mAboveAppWindowsContainers.assignChildLayers(t); - mImeWindowsContainers.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE); - mImeWindowsContainers.assignChildLayers(t); + mRootDisplayArea.assignChildLayers(t); } @Override @@ -4826,10 +4786,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } void addChildren() { - addChild(mBelowAppWindowsContainers, null); - addChild(mTaskContainers, null); - addChild(mAboveAppWindowsContainers, null); - addChild(mImeWindowsContainers, null); + addChild(mRootDisplayArea, 0); + mDisplayAreaPolicy.attachDisplayAreas(); } @Override @@ -4856,32 +4814,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo < mWmService.mPolicy.getWindowLayerFromTypeLw(token2.windowType, token2.mOwnerCanManageAppTokens) ? -1 : 1; - private final Predicate<WindowState> mGetOrientingWindow = w -> { - final WindowManagerPolicy policy = mWmService.mPolicy; - if (policy.isKeyguardHostWindow(w.mAttrs)) { - if (mWmService.mKeyguardGoingAway) { - return false; - } - // Consider unoccluding only when all unknown visibilities have been - // resolved, as otherwise we just may be starting another occluding activity. - final boolean isUnoccluding = - mDisplayContent.mAppTransition.getAppTransition() - == TRANSIT_KEYGUARD_UNOCCLUDE - && mDisplayContent.mUnknownAppVisibilityController.allResolved(); - // If keyguard is showing, or we're unoccluding, force the keyguard's orientation, - // even if SystemUI hasn't updated the attrs yet. - if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) { - return true; - } - } - final int req = w.mAttrs.screenOrientation; - if (req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND - || req == SCREEN_ORIENTATION_UNSET) { - return false; - } - return true; - }; - private final String mName; private final Dimmer mDimmer = new Dimmer(this); private final Rect mTmpDimBoundsRect = new Rect(); @@ -4903,26 +4835,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override int getOrientation(int candidate) { - // Find a window requesting orientation. - final WindowState win = getWindow(mGetOrientingWindow); - - if (win != null) { - int req = win.mAttrs.screenOrientation; - ProtoLog.v(WM_DEBUG_ORIENTATION, - "%s forcing orientation to %d for display id=%d", win, req, - mDisplayId); - if (mWmService.mPolicy.isKeyguardHostWindow(win.mAttrs)) { - // SystemUI controls the Keyguard orientation asynchronously, and mAttrs may be - // stale. We record / use the last known override. - if (req != SCREEN_ORIENTATION_UNSET && req != SCREEN_ORIENTATION_UNSPECIFIED) { - mDisplayContent.mLastKeyguardForcedOrientation = req; - } else { - req = mDisplayContent.mLastKeyguardForcedOrientation; - } - } - return req; - } - return candidate; + ProtoLog.w(WM_DEBUG_ORIENTATION, "NonAppWindowContainer cannot set orientation: %s", + this); + return SCREEN_ORIENTATION_UNSET; } @Override @@ -4957,11 +4872,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * - the container doesn't always participate in window traversal, according to * {@link #skipImeWindowsDuringTraversal()} */ - private class ImeContainer extends NonAppWindowContainers { + private static class ImeContainer extends DisplayArea.Tokens { boolean mNeedsLayer = false; ImeContainer(WindowManagerService wms) { - super("ImeContainer", wms); + super(wms, Type.ABOVE_TASKS, "ImeContainer"); } public void setNeedsLayer() { @@ -6271,6 +6186,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } boolean isTopNotPinnedStack(ActivityStack stack) { + // TODO(task-hierarchy): Remove when tiles are in hierarchy. + if (stack instanceof TaskTile) { + return false; + } for (int i = getStackCount() - 1; i >= 0; --i) { final ActivityStack current = getStackAt(i); if (!current.inPinnedWindowingMode()) { @@ -6685,6 +6604,19 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return getHomeActivityForUser(mRootWindowContainer.mCurrentUser); } + // TODO(task-hierarchy): Remove when tiles are in hierarchy. + void addTile(TaskTile tile) { + mTaskContainers.addChild(tile, POSITION_BOTTOM); + ITaskOrganizer organizer = mAtmService.mTaskOrganizerController.getTaskOrganizer( + tile.getWindowingMode()); + tile.setTaskOrganizer(organizer); + } + + // TODO(task-hierarchy): Remove when tiles are in hierarchy. + void removeTile(TaskTile tile) { + mTaskContainers.removeChild(tile); + } + @Nullable ActivityRecord getHomeActivityForUser(int userId) { final ActivityStack homeStack = getRootHomeTask(); diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index da773143f75c..efe79b36d645 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -526,7 +526,7 @@ public class DisplayRotation { mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout); mIsWaitingForRemoteRotation = false; mDisplayContent.sendNewConfiguration(); - mService.mAtmService.applyContainerTransaction(t); + mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); } } diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 2d4211aa16d8..d0179adadbd7 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -20,17 +20,31 @@ import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.SyncRtSurfaceTransactionApplier.applyParams; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; import android.annotation.Nullable; import android.app.StatusBarManager; import android.util.IntArray; +import android.util.SparseArray; +import android.view.InsetsAnimationControlCallbacks; +import android.view.InsetsAnimationControlImpl; +import android.view.InsetsController; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; +import android.view.SurfaceControl; +import android.view.SyncRtSurfaceTransactionApplier; import android.view.ViewRootImpl; +import android.view.WindowInsetsAnimationCallback; +import android.view.WindowInsetsAnimationControlListener; + +import com.android.server.DisplayThread; /** * Policy that implements who gets control over the windows generating insets. @@ -46,6 +60,8 @@ class InsetsPolicy { private WindowState mFocusedWin; private BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR); private BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR); + private boolean mAnimatingShown; + private final float[] mTmpFloat9 = new float[9]; InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) { mStateController = stateController; @@ -91,11 +107,14 @@ class InsetsPolicy { changed = true; } if (changed) { - updateBarControlTarget(mFocusedWin); - mPolicy.getStatusBarManagerInternal().showTransient(mDisplayContent.getDisplayId(), - mShowingTransientTypes.toArray()); - mStateController.notifyInsetsChanged(); - // TODO(b/118118435): Animation + startAnimation(mShowingTransientTypes, true, () -> { + synchronized (mDisplayContent.mWmService.mGlobalLock) { + mPolicy.getStatusBarManagerInternal().showTransient( + mDisplayContent.getDisplayId(), + mShowingTransientTypes.toArray()); + mStateController.notifyInsetsChanged(); + } + }); } } @@ -103,11 +122,13 @@ class InsetsPolicy { if (mShowingTransientTypes.size() == 0) { return; } - - // TODO(b/118118435): Animation - mShowingTransientTypes.clear(); - updateBarControlTarget(mFocusedWin); - mStateController.notifyInsetsChanged(); + startAnimation(mShowingTransientTypes, false, () -> { + synchronized (mDisplayContent.mWmService.mGlobalLock) { + mShowingTransientTypes.clear(); + mStateController.notifyInsetsChanged(); + updateBarControlTarget(mFocusedWin); + } + }); } boolean isTransient(@InternalInsetsType int type) { @@ -247,6 +268,29 @@ class InsetsPolicy { return isDockedStackVisible || isFreeformStackVisible || isResizing; } + private void startAnimation(IntArray internalTypes, boolean show, Runnable callback) { + int typesReady = 0; + final SparseArray<InsetsSourceControl> controls = new SparseArray<>(); + updateBarControlTarget(mFocusedWin); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + InsetsSourceProvider provider = + mStateController.getSourceProvider(internalTypes.get(i)); + if (provider == null) continue; + InsetsSourceControl control = provider.getControl(provider.getControlTarget()); + if (control == null || control.getLeash() == null) continue; + typesReady |= InsetsState.toPublicType(internalTypes.get(i)); + controls.put(control.getType(), control); + } + controlAnimationUnchecked(typesReady, controls, show, callback); + } + + private void controlAnimationUnchecked(int typesReady, + SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) { + InsetsPolicyAnimationControlListener listener = + new InsetsPolicyAnimationControlListener(show, callback); + listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show); + } + private class BarWindow { private final int mId; @@ -267,7 +311,103 @@ class InsetsPolicy { } } - // TODO(b/118118435): Implement animations for it (with SurfaceAnimator) + private class InsetsPolicyAnimationControlListener extends + InsetsController.InternalAnimationControlListener { + Runnable mFinishCallback; + InsetsPolicyAnimationControlCallbacks mControlCallbacks; + + InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback) { + super(show); + mFinishCallback = finishCallback; + mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this); + } + + @Override + protected void onAnimationFinish() { + super.onAnimationFinish(); + mControlCallbacks.mAnimationControl.finish(mAnimatingShown); + DisplayThread.getHandler().post(mFinishCallback); + } + + private class InsetsPolicyAnimationControlCallbacks implements + InsetsAnimationControlCallbacks { + private InsetsAnimationControlImpl mAnimationControl = null; + private InsetsPolicyAnimationControlListener mListener; + + InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) { + super(); + mListener = listener; + } + + private void controlAnimationUnchecked(int typesReady, + SparseArray<InsetsSourceControl> controls, boolean show) { + if (typesReady == 0) { + // nothing to animate. + return; + } + mAnimatingShown = show; + + mAnimationControl = new InsetsAnimationControlImpl(controls, + mFocusedWin.getDisplayContent().getBounds(), getState(), + mListener, typesReady, this, mListener.getDurationMs(), + InsetsController.INTERPOLATOR, true, + show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN + : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); + } + + /** Called on SurfaceAnimationThread lock without global WM lock held. */ + @Override + public void scheduleApplyChangeInsets() { + InsetsState state = getState(); + if (mAnimationControl.applyChangeInsets(state)) { + mAnimationControl.finish(mAnimatingShown); + } + } + + @Override + public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) { + // Nothing's needed here. Finish steps is handled in the listener + // onAnimationFinished callback. + } + + /** + * This method will return a state with fullscreen frame override. No need to make copy + * after getting state from this method. + * @return The client insets state with full display frame override. + */ + private InsetsState getState() { + // To animate the transient animation correctly, we need to let the state hold + // the full display frame. + InsetsState overrideState = new InsetsState(mFocusedWin.getRequestedInsetsState(), + true); + overrideState.setDisplayFrame(mFocusedWin.getDisplayContent().getBounds()); + return overrideState; + } + + /** Called on SurfaceAnimationThread lock without global WM lock held. */ + @Override + public void applySurfaceParams( + final SyncRtSurfaceTransactionApplier.SurfaceParams... params) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (int i = params.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i]; + applyParams(t, surfaceParams, mTmpFloat9); + } + t.apply(); + } + + /** Called on SurfaceAnimationThread lock without global WM lock held. */ + @Override + public void startAnimation(InsetsAnimationControlImpl controller, + WindowInsetsAnimationControlListener listener, int types, + WindowInsetsAnimationCallback.InsetsAnimation animation, + WindowInsetsAnimationCallback.AnimationBounds bounds, + int layoutDuringAnimation) { + SurfaceAnimationThread.getHandler().post(() -> listener.onReady(controller, types)); + } + } + } + private class TransientControlTarget implements InsetsControlTarget { @Override diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index db434800c979..e6c1969d4a22 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -24,6 +24,7 @@ import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; import static android.view.ViewRootImpl.sNewInsetsMode; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL; import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED; import android.annotation.NonNull; @@ -34,11 +35,11 @@ import android.util.proto.ProtoOutputStream; import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; -import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import com.android.internal.util.function.TriConsumer; +import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import java.io.PrintWriter; @@ -243,7 +244,8 @@ class InsetsSourceProvider { mAdapter = new ControlAdapter(); setClientVisible(InsetsState.getDefaultVisibility(mSource.getType())); final Transaction t = mDisplayContent.getPendingTransaction(); - mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */); + mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */, + ANIMATION_TYPE_INSETS_CONTROL, null /* animationFinishedCallback */); final SurfaceControl leash = mAdapter.mCapturedLeash; final long frameNumber = mFinishSeamlessRotateFrameNumber; mFinishSeamlessRotateFrameNumber = -1; @@ -348,7 +350,7 @@ class InsetsSourceProvider { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, OnAnimationFinishedCallback finishCallback) { // TODO(b/118118435): We can remove the type check when implementing the transient bar // animation. if (mSource.getType() == ITYPE_IME) { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 568966ae8d2e..289ac4cd5d44 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -60,6 +60,8 @@ class InsetsStateController { w.notifyInsetsChanged(); } }; + private final InsetsControlTarget mEmptyImeControlTarget = () -> { + }; InsetsStateController(DisplayContent displayContent) { mDisplayContent = displayContent; @@ -182,7 +184,10 @@ class InsetsStateController { } void onImeControlTargetChanged(@Nullable InsetsControlTarget imeTarget) { - onControlChanged(ITYPE_IME, imeTarget); + + // Make sure that we always have a control target for the IME, even if the IME target is + // null. Otherwise there is no leash that will hide it and IME becomes "randomly" visible. + onControlChanged(ITYPE_IME, imeTarget != null ? imeTarget : mEmptyImeControlTarget); notifyPendingInsetsControlChanged(); } diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java index 5892239edb4d..7c1a6161236a 100644 --- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java @@ -24,6 +24,7 @@ import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import java.io.PrintWriter; @@ -50,9 +51,9 @@ class LocalAnimationAdapter implements AnimationAdapter { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, OnAnimationFinishedCallback finishCallback) { mAnimator.startAnimation(mSpec, animationLeash, t, - () -> finishCallback.onAnimationFinished(this)); + () -> finishCallback.onAnimationFinished(type, this)); } @Override diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 647be0f7038c..9770947ff2c3 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -388,11 +388,12 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // surfaces needs to be done immediately. mWindowManager.executeAppTransition(); - // After reordering the stacks, reset the minimized state. At this point, either - // the target activity is now top-most and we will stay minimized (if in - // split-screen), or we will have returned to the app, and the minimized state - // should be reset - mWindowManager.checkSplitScreenMinimizedChanged(true /* animate */); + if (targetStack.getTile() != null) { + // Client state may have changed during the recents animation, so force + // send task info so the client can synchronize its state. + mService.mTaskOrganizerController.dispatchTaskInfoChanged( + targetStack.mTile, true /* force */); + } } catch (Exception e) { Slog.e(TAG, "Failed to clean up recents activity", e); throw e; diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index da9d074eb7bc..944e0ae3d73f 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -30,6 +30,7 @@ import static com.android.server.wm.AnimationAdapterProto.REMOTE; import static com.android.server.wm.BoundsAnimationController.FADE_IN; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; import android.annotation.IntDef; @@ -61,6 +62,7 @@ import com.android.server.LocalServices; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.protolog.common.ProtoLog; import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import com.android.server.wm.utils.InsetUtils; @@ -432,7 +434,8 @@ public class RecentsAnimationController implements DeathRecipient { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName()); final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task, isRecentTaskInvisible); - task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */); + task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */, + ANIMATION_TYPE_RECENTS); task.commitPendingTransaction(); mPendingAnimations.add(taskAdapter); return taskAdapter; @@ -443,14 +446,16 @@ public class RecentsAnimationController implements DeathRecipient { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "removeAnimation(%d)", taskAdapter.mTask.mTaskId); taskAdapter.mTask.setCanAffectSystemUiFlags(true); - taskAdapter.mCapturedFinishCallback.onAnimationFinished(taskAdapter); + taskAdapter.mCapturedFinishCallback.onAnimationFinished(taskAdapter.mLastAnimationType, + taskAdapter); mPendingAnimations.remove(taskAdapter); } @VisibleForTesting void removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "removeWallpaperAnimation()"); - wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished(wallpaperAdapter); + wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished( + wallpaperAdapter.getLastAnimationType(), wallpaperAdapter); mPendingWallpaperAnimations.remove(wallpaperAdapter); } @@ -638,7 +643,7 @@ public class RecentsAnimationController implements DeathRecipient { taskSnapshot.getColorSpace(), false /* containsSecureLayers */)); mRecentScreenshotAnimator = new SurfaceAnimator( animatable, - () -> { + (type, anim) -> { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "mRecentScreenshotAnimator finish"); mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); }, mService); @@ -827,6 +832,7 @@ public class RecentsAnimationController implements DeathRecipient { private final Task mTask; private SurfaceControl mCapturedLeash; private OnAnimationFinishedCallback mCapturedFinishCallback; + private @AnimationType int mLastAnimationType; private final boolean mIsRecentTaskInvisible; private RemoteAnimationTarget mTarget; private final Point mPosition = new Point(); @@ -868,7 +874,7 @@ public class RecentsAnimationController implements DeathRecipient { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, OnAnimationFinishedCallback finishCallback) { // Restore z-layering, position and stack crop until client has a chance to modify it. t.setLayer(animationLeash, mTask.getPrefixOrderIndex()); t.setPosition(animationLeash, mPosition.x, mPosition.y); @@ -877,6 +883,7 @@ public class RecentsAnimationController implements DeathRecipient { t.setWindowCrop(animationLeash, mTmpRect); mCapturedLeash = animationLeash; mCapturedFinishCallback = finishCallback; + mLastAnimationType = type; } @Override diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 6f7eeabfc4cd..d2dbab841f16 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -40,6 +40,7 @@ import android.view.SurfaceControl.Transaction; import com.android.internal.util.FastPrintWriter; import com.android.server.protolog.ProtoLogImpl; import com.android.server.protolog.common.ProtoLog; +import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import java.io.PrintWriter; @@ -180,12 +181,14 @@ class RemoteAnimationController implements DeathRecipient { if (wrappers.mAdapter != null && wrappers.mAdapter.mCapturedFinishCallback != null) { wrappers.mAdapter.mCapturedFinishCallback - .onAnimationFinished(wrappers.mAdapter); + .onAnimationFinished(wrappers.mAdapter.mAnimationType, + wrappers.mAdapter); } if (wrappers.mThumbnailAdapter != null && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) { wrappers.mThumbnailAdapter.mCapturedFinishCallback - .onAnimationFinished(wrappers.mThumbnailAdapter); + .onAnimationFinished(wrappers.mAdapter.mAnimationType, + wrappers.mThumbnailAdapter); } mPendingAnimations.remove(i); } @@ -221,11 +224,13 @@ class RemoteAnimationController implements DeathRecipient { final RemoteAnimationRecord adapters = mPendingAnimations.get(i); if (adapters.mAdapter != null) { adapters.mAdapter.mCapturedFinishCallback - .onAnimationFinished(adapters.mAdapter); + .onAnimationFinished(adapters.mAdapter.mAnimationType, + adapters.mAdapter); } if (adapters.mThumbnailAdapter != null) { adapters.mThumbnailAdapter.mCapturedFinishCallback - .onAnimationFinished(adapters.mThumbnailAdapter); + .onAnimationFinished(adapters.mAdapter.mAnimationType, + adapters.mThumbnailAdapter); } mPendingAnimations.remove(i); ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tcontainer=%s", @@ -234,7 +239,8 @@ class RemoteAnimationController implements DeathRecipient { for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) { final WallpaperAnimationAdapter adapter = mPendingWallpaperAnimations.get(i); - adapter.getLeashFinishedCallback().onAnimationFinished(adapter); + adapter.getLeashFinishedCallback().onAnimationFinished( + adapter.getLastAnimationType(), adapter); mPendingWallpaperAnimations.remove(i); ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\twallpaper=%s", adapter.getToken()); } @@ -393,6 +399,7 @@ class RemoteAnimationController implements DeathRecipient { private final RemoteAnimationRecord mRecord; SurfaceControl mCapturedLeash; private OnAnimationFinishedCallback mCapturedFinishCallback; + private @AnimationType int mAnimationType; final Point mPosition = new Point(); final Rect mStackBounds = new Rect(); @@ -410,7 +417,7 @@ class RemoteAnimationController implements DeathRecipient { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, OnAnimationFinishedCallback finishCallback) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation"); // Restore z-layering, position and stack crop until client has a chance to modify it. @@ -425,6 +432,7 @@ class RemoteAnimationController implements DeathRecipient { } mCapturedLeash = animationLeash; mCapturedFinishCallback = finishCallback; + mAnimationType = type; } @Override diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index e6fd512df72c..a13399bcb69c 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1012,6 +1012,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mWmService.scheduleAnimationLocked(); + // Send any pending task-info changes that were queued-up during a layout deferment + mWmService.mAtmService.mTaskOrganizerController.dispatchPendingTaskInfoChanges(); + if (DEBUG_WINDOW_TRACE) Slog.e(TAG, "performSurfacePlacementInner exit: animating=" + mWmService.mAnimator.isAnimating()); @@ -2959,6 +2962,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents(); case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant(); } + // TODO(task-hierarchy): Find another way to differentiate tile from normal stack once it is + // part of the hierarchy + if (stack instanceof TaskTile) { + // Don't launch directly into tiles. + return false; + } // There is a 1-to-1 relationship between stack and task when not in // primary split-windowing mode. if (stack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index e7aca898b19d..f6cdac5d4565 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -24,6 +24,7 @@ import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA; import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA; import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING; import static com.android.server.wm.ScreenRotationAnimationProto.STARTED; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; @@ -48,6 +49,8 @@ import android.view.animation.Transformation; import com.android.internal.R; import com.android.server.protolog.common.ProtoLog; +import com.android.server.wm.SurfaceAnimator.AnimationType; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import com.android.server.wm.utils.RotationAnimationUtils; import java.io.PrintWriter; @@ -670,33 +673,35 @@ class ScreenRotationAnimation { * Start an animation defined by animationSpec on a new {@link SurfaceAnimator}. * * @param animatable The animatable used for the animation. - * @param animationSpec The spec of the animation. - * @param animationFinishedCallback Callback passed to the {@link SurfaceAnimator} and - * called when the animation finishes. + * @param animationSpec The spec of the animation. + * @param animationFinishedCallback Callback passed to the {@link SurfaceAnimator} + * and called when the animation finishes. * @return The newly created {@link SurfaceAnimator} that as been started. */ private SurfaceAnimator startAnimation( SurfaceAnimator.Animatable animatable, LocalAnimationAdapter.AnimationSpec animationSpec, - Runnable animationFinishedCallback) { + OnAnimationFinishedCallback animationFinishedCallback) { SurfaceAnimator animator = new SurfaceAnimator( animatable, animationFinishedCallback, mService); LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter( animationSpec, mService.mSurfaceAnimationRunner); animator.startAnimation(mDisplayContent.getPendingTransaction(), - localAnimationAdapter, false); + localAnimationAdapter, false, ANIMATION_TYPE_SCREEN_ROTATION); return animator; } - private void onAnimationEnd() { + private void onAnimationEnd(@AnimationType int type, AnimationAdapter anim) { synchronized (mService.mGlobalLock) { if (isAnimating()) { ProtoLog.v(WM_DEBUG_ORIENTATION, - "ScreenRotation sill animating: mDisplayAnimator: %s\n" - + "mEnterBlackFrameAnimator: " - + "%s\nmRotateScreenAnimator: %s\n" + "ScreenRotation still animating: type: %d\n" + + "mDisplayAnimator: %s\n" + + "mEnterBlackFrameAnimator: %s\n" + + "mRotateScreenAnimator: %s\n" + "mScreenshotRotationAnimator: %s", + type, mDisplayAnimator != null ? mDisplayAnimator.isAnimating() : null, mEnterBlackFrameAnimator != null diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index de7f9e41cac0..ab1f34adbeeb 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -238,16 +238,12 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { @Override public void setInTouchMode(boolean mode) { - synchronized (mService.mGlobalLock) { - mService.mInTouchMode = mode; - } + mService.setInTouchMode(mode); } @Override public boolean getInTouchMode() { - synchronized (mService.mGlobalLock) { - return mService.mInTouchMode; - } + return mService.getInTouchMode(); } @Override diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index cb1676f3a7b1..7164cd85230b 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -23,6 +23,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Slog; @@ -33,6 +34,8 @@ import android.view.SurfaceControl.Transaction; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * A class that can run animations on objects that have a set of child surfaces. We do this by @@ -47,6 +50,7 @@ class SurfaceAnimator { private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM; private final WindowManagerService mService; private AnimationAdapter mAnimation; + private @AnimationType int mAnimationType; @VisibleForTesting SurfaceControl mLeash; @@ -55,30 +59,32 @@ class SurfaceAnimator { private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback; @VisibleForTesting @Nullable - final Runnable mStaticAnimationFinishedCallback; + final OnAnimationFinishedCallback mStaticAnimationFinishedCallback; @Nullable - private Runnable mAnimationFinishedCallback; + private OnAnimationFinishedCallback mAnimationFinishedCallback; private boolean mAnimationStartDelayed; /** * @param animatable The object to animate. - * @param animationFinishedCallback Callback to invoke when an animation has finished running. + * @param staticAnimationFinishedCallback Callback to invoke when an animation has finished + * running. */ - SurfaceAnimator(Animatable animatable, @Nullable Runnable animationFinishedCallback, + SurfaceAnimator(Animatable animatable, + @Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback, WindowManagerService service) { mAnimatable = animatable; mService = service; - mStaticAnimationFinishedCallback = animationFinishedCallback; - mInnerAnimationFinishedCallback = getFinishedCallback(animationFinishedCallback); + mStaticAnimationFinishedCallback = staticAnimationFinishedCallback; + mInnerAnimationFinishedCallback = getFinishedCallback(staticAnimationFinishedCallback); } private OnAnimationFinishedCallback getFinishedCallback( - @Nullable Runnable animationFinishedCallback) { - return anim -> { + @Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback) { + return (type, anim) -> { synchronized (mService.mGlobalLock) { final SurfaceAnimator target = mService.mAnimationTransferMap.remove(anim); if (target != null) { - target.mInnerAnimationFinishedCallback.onAnimationFinished(anim); + target.mInnerAnimationFinishedCallback.onAnimationFinished(type, anim); return; } @@ -91,13 +97,14 @@ class SurfaceAnimator { if (anim != mAnimation) { return; } - final Runnable animationFinishCallback = mAnimationFinishedCallback; + final OnAnimationFinishedCallback animationFinishCallback = + mAnimationFinishedCallback; reset(mAnimatable.getPendingTransaction(), true /* destroyLeash */); - if (animationFinishedCallback != null) { - animationFinishedCallback.run(); + if (staticAnimationFinishedCallback != null) { + staticAnimationFinishedCallback.onAnimationFinished(type, anim); } if (animationFinishCallback != null) { - animationFinishCallback.run(); + animationFinishCallback.onAnimationFinished(type, anim); } }; if (!mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)) { @@ -120,9 +127,11 @@ class SurfaceAnimator { * @param animationFinishedCallback The callback being triggered when the animation finishes. */ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, - @Nullable Runnable animationFinishedCallback) { + @AnimationType int type, + @Nullable OnAnimationFinishedCallback animationFinishedCallback) { cancelAnimation(t, true /* restarting */, true /* forwardCancel */); mAnimation = anim; + mAnimationType = type; mAnimationFinishedCallback = animationFinishedCallback; final SurfaceControl surface = mAnimatable.getSurfaceControl(); if (surface == null) { @@ -137,11 +146,12 @@ class SurfaceAnimator { if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed"); return; } - mAnimation.startAnimation(mLeash, t, mInnerAnimationFinishedCallback); + mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback); } - void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) { - startAnimation(t, anim, hidden, null /* animationFinishedCallback */); + void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, + @AnimationType int type) { + startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */); } /** @@ -165,7 +175,7 @@ class SurfaceAnimator { mAnimationStartDelayed = false; if (delayed && mAnimation != null) { mAnimation.startAnimation(mLeash, mAnimatable.getPendingTransaction(), - mInnerAnimationFinishedCallback); + mAnimationType, mInnerAnimationFinishedCallback); mAnimatable.commitPendingTransaction(); } } @@ -245,6 +255,7 @@ class SurfaceAnimator { cancelAnimation(t, true /* restarting */, true /* forwardCancel */); mLeash = from.mLeash; mAnimation = from.mAnimation; + mAnimationType = from.mAnimationType; mAnimationFinishedCallback = from.mAnimationFinishedCallback; // Cancel source animation, but don't let animation runner cancel the animation. @@ -272,7 +283,8 @@ class SurfaceAnimator { if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting); final SurfaceControl leash = mLeash; final AnimationAdapter animation = mAnimation; - final Runnable animationFinishedCallback = mAnimationFinishedCallback; + final @AnimationType int animationType = mAnimationType; + final OnAnimationFinishedCallback animationFinishedCallback = mAnimationFinishedCallback; reset(t, false); if (animation != null) { if (!mAnimationStartDelayed && forwardCancel) { @@ -280,10 +292,10 @@ class SurfaceAnimator { } if (!restarting) { if (mStaticAnimationFinishedCallback != null) { - mStaticAnimationFinishedCallback.run(); + mStaticAnimationFinishedCallback.onAnimationFinished(animationType, animation); } if (animationFinishedCallback != null) { - animationFinishedCallback.run(); + animationFinishedCallback.onAnimationFinished(animationType, animation); } } } @@ -325,6 +337,7 @@ class SurfaceAnimator { mLeash = null; mAnimation = null; mAnimationFinishedCallback = null; + mAnimationType = ANIMATION_TYPE_NONE; if (reparent) { // Make sure to inform the animatable after the surface was reparented (or reparent @@ -392,12 +405,72 @@ class SurfaceAnimator { } } + + /** + * No animation is specified. + * @hide + */ + static final int ANIMATION_TYPE_NONE = 0; + + /** + * Animation for an app transition. + * @hide + */ + static final int ANIMATION_TYPE_APP_TRANSITION = 1; + + /** + * Animation for screen rotation. + * @hide + */ + static final int ANIMATION_TYPE_SCREEN_ROTATION = 2; + + /** + * Animation for dimming. + * @hide + */ + static final int ANIMATION_TYPE_DIMMER = 3; + + /** + * Animation for recent apps. + * @hide + */ + static final int ANIMATION_TYPE_RECENTS = 4; + + /** + * Animation for a {@link WindowState} without animating the activity. + * @hide + */ + static final int ANIMATION_TYPE_WINDOW_ANIMATION = 5; + + /** + * Animation to control insets. This is actually not an animation, but is used to give the + * client a leash over the system window causing insets. + * @hide + */ + static final int ANIMATION_TYPE_INSETS_CONTROL = 6; + + /** + * The type of the animation. + * @hide + */ + @IntDef(flag = true, prefix = { "ANIMATION_TYPE_" }, value = { + ANIMATION_TYPE_NONE, + ANIMATION_TYPE_APP_TRANSITION, + ANIMATION_TYPE_SCREEN_ROTATION, + ANIMATION_TYPE_DIMMER, + ANIMATION_TYPE_RECENTS, + ANIMATION_TYPE_WINDOW_ANIMATION, + ANIMATION_TYPE_INSETS_CONTROL + }) + @Retention(RetentionPolicy.SOURCE) + @interface AnimationType {} + /** * Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the * component that is running the animation when the animation is finished. */ interface OnAnimationFinishedCallback { - void onAnimationFinished(AnimationAdapter anim); + void onAnimationFinished(@AnimationType int type, AnimationAdapter anim); } /** diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 36cae1fb9b36..28dc2a42da2a 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1257,7 +1257,7 @@ class Task extends WindowContainer<WindowContainer> { if (affinityIntent != null) return affinityIntent; // Probably a task that contains other tasks, so return the intent for the top task? final Task topTask = getTopMostTask(); - return topTask != null ? topTask.getBaseIntent() : null; + return (topTask != this && topTask != null) ? topTask.getBaseIntent() : null; } /** Returns the first non-finishing activity from the bottom. */ @@ -3214,7 +3214,10 @@ class Task extends WindowContainer<WindowContainer> { info.taskId = mTaskId; info.displayId = getDisplayId(); info.isRunning = getTopNonFinishingActivity() != null; - info.baseIntent = new Intent(getBaseIntent()); + final Intent baseIntent = getBaseIntent(); + // Make a copy of base intent because this is like a snapshot info. + // Besides, {@link RecentTasks#getRecentTasksImpl} may modify it. + info.baseIntent = baseIntent == null ? new Intent() : new Intent(baseIntent); info.baseActivity = mReuseActivitiesReport.base != null ? mReuseActivitiesReport.base.intent.getComponent() : null; @@ -3229,6 +3232,10 @@ class Task extends WindowContainer<WindowContainer> { info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode(); info.resizeMode = mResizeMode; info.configuration.setTo(getConfiguration()); + info.token = mRemoteToken; + // Get's the first non-undefined activity type among this and children. Can't use + // configuration.windowConfiguration because that would only be this level. + info.topActivityType = getActivityType(); } /** @@ -3375,7 +3382,7 @@ class Task extends WindowContainer<WindowContainer> { if (affinity != null) { sb.append(" A="); sb.append(affinity); - } else if (intent != null) { + } else if (intent != null && intent.getComponent() != null) { sb.append(" I="); sb.append(intent.getComponent().flattenToShortString()); } else if (affinityIntent != null && affinityIntent.getComponent() != null) { @@ -3865,7 +3872,12 @@ class Task extends WindowContainer<WindowContainer> { boolean isControlledByTaskOrganizer() { final Task rootTask = getRootTask(); - return rootTask == this && rootTask.mTaskOrganizer != null; + return rootTask == this && rootTask.mTaskOrganizer != null + // TODO(task-hierarchy): Figure out how to control nested tasks. + // For now, if this is in a tile let WM drive. + && !(rootTask instanceof TaskTile) + && !(rootTask instanceof ActivityStack + && ((ActivityStack) rootTask).getTile() != null); } @Override @@ -3893,6 +3905,9 @@ class Task extends WindowContainer<WindowContainer> { } void setTaskOrganizer(ITaskOrganizer organizer) { + if (mTaskOrganizer == organizer) { + return; + } // Let the old organizer know it has lost control. if (mTaskOrganizer != null) { sendTaskVanished(); @@ -3918,8 +3933,6 @@ class Task extends WindowContainer<WindowContainer> { public void updateSurfacePosition() { // Avoid fighting with the TaskOrganizer over Surface position. if (isControlledByTaskOrganizer()) { - getPendingTransaction().setPosition(mSurfaceControl, 0, 0); - scheduleAnimation(); return; } else { super.updateSurfacePosition(); diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 66c65e226bce..44a6fc936961 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -16,26 +16,51 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + +import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; +import android.annotation.Nullable; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ITaskOrganizerController; +import android.app.WindowConfiguration; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArraySet; import android.util.Slog; import android.view.ITaskOrganizer; -import android.view.SurfaceControl; +import android.view.IWindowContainer; +import android.view.WindowContainerTransaction; + +import com.android.internal.util.function.pooled.PooledConsumer; +import com.android.internal.util.function.pooled.PooledLambda; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.WeakHashMap; /** * Stores the TaskOrganizers associated with a given windowing mode and * their associated state. */ -class TaskOrganizerController { +class TaskOrganizerController extends ITaskOrganizerController.Stub { private static final String TAG = "TaskOrganizerController"; - private WindowManagerGlobalLock mGlobalLock; + /** Flag indicating that an applied transaction may have effected lifecycle */ + private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1; + private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1; + + private final WindowManagerGlobalLock mGlobalLock; private class DeathRecipient implements IBinder.DeathRecipient { int mWindowingMode; @@ -87,11 +112,20 @@ class TaskOrganizerController { final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap(); + private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>(); + private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>(); + final ActivityTaskManagerService mService; - TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) { + RunningTaskInfo mTmpTaskInfo; + + TaskOrganizerController(ActivityTaskManagerService atm) { mService = atm; - mGlobalLock = lock; + mGlobalLock = atm.mGlobalLock; + } + + private void enforceStackPermission(String func) { + mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func); } private void clearIfNeeded(int windowingMode) { @@ -106,26 +140,35 @@ class TaskOrganizerController { * If there was already a TaskOrganizer for this windowing mode it will be evicted * and receive taskVanished callbacks in the process. */ - void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { - if (windowingMode != WINDOWING_MODE_PINNED && - windowingMode != WINDOWING_MODE_MULTI_WINDOW) { - throw new UnsupportedOperationException( - "As of now only Pinned and Multiwindow windowing modes are" - + " supported for registerTaskOrganizer"); - + @Override + public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { + if (windowingMode != WINDOWING_MODE_PINNED + && windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + && windowingMode != WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + && windowingMode != WINDOWING_MODE_MULTI_WINDOW) { + throw new UnsupportedOperationException("As of now only Pinned/Split/Multiwindow" + + " windowing modes are supported for registerTaskOrganizer"); } - clearIfNeeded(windowingMode); - DeathRecipient dr = new DeathRecipient(organizer, windowingMode); + enforceStackPermission("registerTaskOrganizer()"); + final long origId = Binder.clearCallingIdentity(); try { - organizer.asBinder().linkToDeath(dr, 0); - } catch (RemoteException e) { - Slog.e(TAG, "TaskOrganizer failed to register death recipient"); - } + synchronized (mGlobalLock) { + clearIfNeeded(windowingMode); + DeathRecipient dr = new DeathRecipient(organizer, windowingMode); + try { + organizer.asBinder().linkToDeath(dr, 0); + } catch (RemoteException e) { + Slog.e(TAG, "TaskOrganizer failed to register death recipient"); + } - final TaskOrganizerState state = new TaskOrganizerState(organizer, dr); - mTaskOrganizersForWindowingMode.put(windowingMode, state); + final TaskOrganizerState state = new TaskOrganizerState(organizer, dr); + mTaskOrganizersForWindowingMode.put(windowingMode, state); - mTaskOrganizerStates.put(organizer, state); + mTaskOrganizerStates.put(organizer, state); + } + } finally { + Binder.restoreCallingIdentity(origId); + } } ITaskOrganizer getTaskOrganizer(int windowingMode) { @@ -138,7 +181,7 @@ class TaskOrganizerController { private void sendTaskAppeared(ITaskOrganizer organizer, Task task) { try { - organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo()); + organizer.taskAppeared(task.getTaskInfo()); } catch (Exception e) { Slog.e(TAG, "Exception sending taskAppeared callback" + e); } @@ -167,4 +210,254 @@ class TaskOrganizerController { // we do this AFTER sending taskVanished. state.removeTask(task); } + + @Override + public RunningTaskInfo createRootTask(int displayId, int windowingMode) { + enforceStackPermission("createRootTask()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId); + if (display == null) { + return null; + } + final int nextId = display.getNextStackId(); + TaskTile tile = new TaskTile(mService, nextId, windowingMode); + display.addTile(tile); + RunningTaskInfo out = new RunningTaskInfo(); + tile.fillTaskInfo(out); + mLastSentTaskInfos.put(tile, out); + return out; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override + public boolean deleteRootTask(IWindowContainer token) { + enforceStackPermission("deleteRootTask()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + TaskTile tile = TaskTile.forToken(token.asBinder()); + if (tile == null) { + return false; + } + tile.removeImmediately(); + return true; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + void dispatchPendingTaskInfoChanges() { + if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) { + return; + } + for (int i = 0, n = mPendingTaskInfoChanges.size(); i < n; ++i) { + dispatchTaskInfoChanged(mPendingTaskInfoChanges.get(i), false /* force */); + } + mPendingTaskInfoChanges.clear(); + } + + void dispatchTaskInfoChanged(Task task, boolean force) { + if (!force && mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) { + // Defer task info reporting while layout is deferred. This is because layout defer + // blocks tend to do lots of re-ordering which can mess up animations in receivers. + mPendingTaskInfoChanges.remove(task); + mPendingTaskInfoChanges.add(task); + return; + } + RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task); + if (mTmpTaskInfo == null) { + mTmpTaskInfo = new RunningTaskInfo(); + } + task.fillTaskInfo(mTmpTaskInfo); + boolean changed = lastInfo == null + || mTmpTaskInfo.topActivityType != lastInfo.topActivityType + || mTmpTaskInfo.isResizable() != lastInfo.isResizable(); + if (!(changed || force)) { + return; + } + final RunningTaskInfo newInfo = mTmpTaskInfo; + mLastSentTaskInfos.put(task, mTmpTaskInfo); + // Since we've stored this, clean up the reference so a new one will be created next time. + // Transferring it this way means we only have to construct new RunningTaskInfos when they + // change. + mTmpTaskInfo = null; + + if (task.mTaskOrganizer != null) { + try { + task.mTaskOrganizer.onTaskInfoChanged(newInfo); + } catch (RemoteException e) { + } + } + } + + @Override + public IWindowContainer getImeTarget(int displayId) { + enforceStackPermission("getImeTarget()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + DisplayContent dc = mService.mWindowManager.mRoot + .getDisplayContent(displayId); + if (dc == null || dc.mInputMethodTarget == null) { + return null; + } + // Avoid WindowState#getRootTask() so we don't attribute system windows to a task. + final Task task = dc.mInputMethodTarget.getTask(); + if (task == null) { + return null; + } + ActivityStack rootTask = (ActivityStack) task.getRootTask(); + final TaskTile tile = rootTask.getTile(); + if (tile != null) { + rootTask = tile; + } + return rootTask.mRemoteToken; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override + public void setLaunchRoot(int displayId, @Nullable IWindowContainer tile) { + enforceStackPermission("setLaunchRoot()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId); + if (display == null) { + return; + } + TaskTile taskTile = tile == null ? null : TaskTile.forToken(tile.asBinder()); + if (taskTile == null) { + display.mLaunchTile = null; + return; + } + if (taskTile.getDisplay() != display) { + throw new RuntimeException("Can't set launch root for display " + displayId + + " to task on display " + taskTile.getDisplay().getDisplayId()); + } + display.mLaunchTile = taskTile; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + private int sanitizeAndApplyChange(WindowContainer container, + WindowContainerTransaction.Change change) { + if (!(container instanceof Task)) { + throw new RuntimeException("Invalid token in task transaction"); + } + // The "client"-facing API should prevent bad changes; however, just in case, sanitize + // masks here. + int configMask = change.getConfigSetMask(); + int windowMask = change.getWindowSetMask(); + configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION + | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; + windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS; + int effects = 0; + if (configMask != 0) { + Configuration c = new Configuration(container.getRequestedOverrideConfiguration()); + c.setTo(change.getConfiguration(), configMask, windowMask); + container.onRequestedOverrideConfigurationChanged(c); + // TODO(b/145675353): remove the following once we could apply new bounds to the + // pinned stack together with its children. + resizePinnedStackIfNeeded(container, configMask, windowMask, c); + effects |= TRANSACT_EFFECTS_CLIENT_CONFIG; + } + if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) { + if (container.setFocusable(change.getFocusable())) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + } + return effects; + } + + private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask, + int windowMask, Configuration config) { + if ((container instanceof ActivityStack) + && ((configMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0) + && ((windowMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)) { + final ActivityStack stack = (ActivityStack) container; + if (stack.inPinnedWindowingMode()) { + stack.resize(config.windowConfiguration.getBounds(), + null /* tempTaskBounds */, null /* tempTaskInsetBounds */, + PRESERVE_WINDOWS, true /* deferResume */); + } + } + } + + private int applyWindowContainerChange(WindowContainer wc, + WindowContainerTransaction.Change c) { + int effects = sanitizeAndApplyChange(wc, c); + + Rect enterPipBounds = c.getEnterPipBounds(); + if (enterPipBounds != null) { + Task tr = (Task) wc; + mService.mStackSupervisor.updatePictureInPictureMode(tr, + enterPipBounds, true); + } + return effects; + } + + @Override + public void applyContainerTransaction(WindowContainerTransaction t) { + enforceStackPermission("applyContainerTransaction()"); + if (t == null) { + return; + } + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + int effects = 0; + mService.deferWindowLayout(); + try { + ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>(); + Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = + t.getChanges().entrySet().iterator(); + while (entries.hasNext()) { + final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = + entries.next(); + final WindowContainer wc = WindowContainer.RemoteToken.fromBinder( + entry.getKey()).getContainer(); + int containerEffect = applyWindowContainerChange(wc, entry.getValue()); + effects |= containerEffect; + // Lifecycle changes will trigger ensureConfig for everything. + if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0 + && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { + haveConfigChanges.add(wc); + } + } + if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { + // Already calls ensureActivityConfig + mService.mRootWindowContainer.ensureActivitiesVisible( + null, 0, PRESERVE_WINDOWS); + } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { + final PooledConsumer f = PooledLambda.obtainConsumer( + ActivityRecord::ensureActivityConfiguration, + PooledLambda.__(ActivityRecord.class), 0, + false /* preserveWindow */); + try { + for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { + haveConfigChanges.valueAt(i).forAllActivities(f); + } + } finally { + f.recycle(); + } + } + } finally { + mService.continueWindowLayout(); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } } diff --git a/services/core/java/com/android/server/wm/TaskTile.java b/services/core/java/com/android/server/wm/TaskTile.java new file mode 100644 index 000000000000..add11d69cbaa --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskTile.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.app.WindowConfiguration; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.Slog; +import android.view.SurfaceControl; + +import java.util.ArrayList; +import java.util.Comparator; + +/** + * A Tile. Right now this acts as a proxy for manipulating non-child stacks. Eventually, this + * can become an actual parent. + */ +// TODO(task-hierarchy): Remove when tasks can nest >2 or when single tasks can handle their +// own lifecycles. +public class TaskTile extends ActivityStack { + private static final String TAG = "TaskTile"; + final ArrayList<WindowContainer> mChildren = new ArrayList<>(); + + private static ActivityInfo createEmptyActivityInfo() { + ActivityInfo info = new ActivityInfo(); + info.applicationInfo = new ApplicationInfo(); + return info; + } + + TaskTile(ActivityTaskManagerService atmService, int id, int windowingMode) { + super(atmService, id, new Intent() /*intent*/, null /*affinityIntent*/, null /*affinity*/, + null /*rootAffinity*/, null /*realActivity*/, null /*origActivity*/, + false /*rootWasReset*/, false /*autoRemoveRecents*/, false /*askedCompatMode*/, + 0 /*userId*/, 0 /*effectiveUid*/, null /*lastDescription*/, + System.currentTimeMillis(), true /*neverRelinquishIdentity*/, + new ActivityManager.TaskDescription(), id, INVALID_TASK_ID, INVALID_TASK_ID, + 0 /*taskAffiliationColor*/, 0 /*callingUid*/, "" /*callingPackage*/, + RESIZE_MODE_RESIZEABLE, false /*supportsPictureInPicture*/, + false /*_realActivitySuspended*/, false /*userSetupComplete*/, INVALID_MIN_SIZE, + INVALID_MIN_SIZE, createEmptyActivityInfo(), null /*voiceSession*/, + null /*voiceInteractor*/, null /*stack*/); + getRequestedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); + } + + @Override + void onDisplayChanged(DisplayContent dc) { + mDisplayContent = null; + if (dc != null) { + dc.getPendingTransaction().merge(getPendingTransaction()); + } + mDisplayContent = dc; + // Virtual parent, so don't notify children. + } + + /** + * If there is a disconnection, this will clean up any vestigial surfaces left on the tile + * leash by moving known children to a new surfacecontrol and then removing the old one. + */ + void cleanupSurfaces() { + if (mSurfaceControl == null) { + return; + } + SurfaceControl oldSurface = mSurfaceControl; + WindowContainer parentWin = getParent(); + if (parentWin == null) { + return; + } + mSurfaceControl = parentWin.makeChildSurface(null).setName("TaskTile " + mTaskId + " - " + + getRequestedOverrideWindowingMode()).setContainerLayer().build(); + SurfaceControl.Transaction t = parentWin.getPendingTransaction(); + t.show(mSurfaceControl); + for (int i = 0; i < mChildren.size(); ++i) { + if (mChildren.get(i).getSurfaceControl() == null) { + continue; + } + mChildren.get(i).reparentSurfaceControl(t, mSurfaceControl); + } + t.remove(oldSurface); + } + + @Override + protected void addChild(WindowContainer child, Comparator<WindowContainer> comparator) { + throw new RuntimeException("Improper use of addChild() on Tile"); + } + + @Override + void addChild(WindowContainer child, int index) { + mChildren.add(child); + if (child instanceof ActivityStack) { + ((ActivityStack) child).setTile(this); + } + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + this, false /* force */); + } + + @Override + void removeChild(WindowContainer child) { + if (child instanceof ActivityStack) { + ((ActivityStack) child).setTile(null); + } + mChildren.remove(child); + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + this, false /* force */); + } + + void removeAllChildren() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + if (child instanceof ActivityStack) { + ((ActivityStack) child).setTile(null); + } + } + mChildren.clear(); + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + this, false /* force */); + } + + @Override + protected int getChildCount() { + // Currently 0 as this isn't a proper hierarchy member yet. + return 0; + } + + @Override + public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) { + Configuration c = new Configuration(getRequestedOverrideConfiguration()); + c.windowConfiguration.setWindowingMode(windowingMode); + onRequestedOverrideConfigurationChanged(c); + } + + @Override + public void onConfigurationChanged(Configuration newParentConfig) { + super.onConfigurationChanged(newParentConfig); + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + child.onConfigurationChanged(child.getParent().getConfiguration()); + } + } + + /** + * Until this can be part of the hierarchy, the Stack level can use this utility during + * resolveOverrideConfig to simulate inheritance. + */ + void updateResolvedConfig(Configuration inOutResolvedConfig) { + Rect resolveBounds = inOutResolvedConfig.windowConfiguration.getBounds(); + if (resolveBounds == null || resolveBounds.isEmpty()) { + resolveBounds.set(getRequestedOverrideBounds()); + } + int stackMode = inOutResolvedConfig.windowConfiguration.getWindowingMode(); + if (stackMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED + || stackMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) { + // Also replace FULLSCREEN because we interpret FULLSCREEN as "fill parent" + inOutResolvedConfig.windowConfiguration.setWindowingMode( + getRequestedOverrideWindowingMode()); + } + if (inOutResolvedConfig.smallestScreenWidthDp + == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { + inOutResolvedConfig.smallestScreenWidthDp = + getRequestedOverrideConfiguration().smallestScreenWidthDp; + } + } + + @Override + void fillTaskInfo(TaskInfo info) { + super.fillTaskInfo(info); + WindowContainer top = null; + // Check mChildren.isEmpty directly because hasChild() -> getChildCount() always returns 0 + if (!mChildren.isEmpty()) { + // Find the top-most root task which is a virtual child of this Tile. Because this is a + // virtual parent, the mChildren order here isn't changed during hierarchy operations. + WindowContainer parent = mChildren.get(0).getParent(); + for (int i = parent.getChildCount() - 1; i >= 0; --i) { + if (mChildren.contains(parent.getChildAt(i))) { + top = parent.getChildAt(i); + break; + } + } + } + final Task topTask = top == null ? null : top.getTopMostTask(); + boolean isResizable = topTask == null || topTask.isResizeable(); + info.resizeMode = isResizable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE; + info.topActivityType = top == null ? ACTIVITY_TYPE_UNDEFINED : top.getActivityType(); + info.configuration.setTo(getRequestedOverrideConfiguration()); + } + + @Override + void removeImmediately() { + removeAllChildren(); + super.removeImmediately(); + } + + static TaskTile forToken(IBinder token) { + try { + return (TaskTile) ((TaskToken) token).getContainer(); + } catch (ClassCastException e) { + Slog.w(TAG, "Bad tile token: " + token, e); + return null; + } + } +} diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java index 801e5217098f..bd705998f49a 100644 --- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static com.android.server.wm.AnimationAdapterProto.REMOTE; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS; import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; import android.graphics.Point; import android.os.SystemClock; @@ -26,6 +27,7 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import com.android.server.protolog.common.ProtoLog; +import com.android.server.wm.SurfaceAnimator.AnimationType; import java.io.PrintWriter; import java.util.ArrayList; @@ -40,6 +42,7 @@ class WallpaperAnimationAdapter implements AnimationAdapter { private final WallpaperWindowToken mWallpaperToken; private SurfaceControl mCapturedLeash; private SurfaceAnimator.OnAnimationFinishedCallback mCapturedLeashFinishCallback; + private @AnimationType int mLastAnimationType; private long mDurationHint; private long mStatusBarTransitionDelay; @@ -77,7 +80,7 @@ class WallpaperAnimationAdapter implements AnimationAdapter { wallpaperWindow, durationHint, statusBarTransitionDelay, animationCanceledRunnable); wallpaperWindow.startAnimation(wallpaperWindow.getPendingTransaction(), - wallpaperAdapter, false /* hidden */); + wallpaperAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION); targets.add(wallpaperAdapter.createRemoteAnimationTarget()); adaptersOut.add(wallpaperAdapter); }); @@ -110,6 +113,13 @@ class WallpaperAnimationAdapter implements AnimationAdapter { } /** + * @return the type of animation. + */ + @AnimationType int getLastAnimationType() { + return mLastAnimationType; + } + + /** * @return the wallpaper window */ WallpaperWindowToken getToken() { @@ -124,13 +134,14 @@ class WallpaperAnimationAdapter implements AnimationAdapter { @Override public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, - SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation"); // Restore z-layering until client has a chance to modify it. t.setLayer(animationLeash, mWallpaperToken.getPrefixOrderIndex()); mCapturedLeash = animationLeash; mCapturedLeashFinishCallback = finishCallback; + mLastAnimationType = type; } @Override diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index d828ca6d7a96..86723153c5f6 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -31,6 +31,7 @@ import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_W import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; @@ -74,6 +75,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ToBooleanFunction; import com.android.server.protolog.common.ProtoLog; import com.android.server.wm.SurfaceAnimator.Animatable; +import com.android.server.wm.SurfaceAnimator.AnimationType; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -1851,19 +1854,24 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @param anim The animation to run. * @param hidden Whether our container is currently hidden. TODO This should use isVisible at * some point but the meaning is too weird to work for all containers. + * @param type The type of animation defined as {@link AnimationType}. * @param animationFinishedCallback The callback being triggered when the animation finishes. */ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, - @Nullable Runnable animationFinishedCallback) { - if (DEBUG_ANIM) Slog.v(TAG, "Starting animation on " + this + ": " + anim); + @AnimationType int type, + @Nullable OnAnimationFinishedCallback animationFinishedCallback) { + if (DEBUG_ANIM) { + Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim); + } // TODO: This should use isVisible() but because isVisible has a really weird meaning at // the moment this doesn't work for all animatable window containers. - mSurfaceAnimator.startAnimation(t, anim, hidden, animationFinishedCallback); + mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback); } - void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) { - startAnimation(t, anim, hidden, null /* animationFinishedCallback */); + void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, + @AnimationType int type) { + startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */); } void transferAnimation(WindowContainer from) { @@ -1916,7 +1924,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @see #getAnimationAdapter */ boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - boolean isVoiceInteraction, @Nullable Runnable animationFinishedCallback) { + boolean isVoiceInteraction, + @Nullable OnAnimationFinishedCallback animationFinishedCallback) { if (mWmService.mDisableTransitionAnimation) { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: transition animation is disabled or skipped. " @@ -1937,7 +1946,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< AnimationAdapter thumbnailAdapter = adapters.second; if (adapter != null) { startAnimation(getPendingTransaction(), adapter, !isVisible(), - animationFinishedCallback); + ANIMATION_TYPE_APP_TRANSITION, animationFinishedCallback); if (adapter.getShowWallpaper()) { getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } @@ -2128,7 +2137,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< /** * Called when an animation has finished running. */ - protected void onAnimationFinished() { + protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) { mWmService.onAnimationFinished(); } diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java index 8948f6fc2ab1..90e3be74c743 100644 --- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java +++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java @@ -20,6 +20,7 @@ import static android.view.SurfaceControl.METADATA_OWNER_UID; import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT; import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR; import static com.android.server.wm.WindowContainerThumbnailProto.WIDTH; @@ -40,6 +41,7 @@ import android.view.animation.Animation; import com.android.server.protolog.common.ProtoLog; import com.android.server.wm.SurfaceAnimator.Animatable; +import com.android.server.wm.SurfaceAnimator.AnimationType; import java.util.function.Supplier; @@ -85,7 +87,7 @@ class WindowContainerThumbnail implements Animatable { // We can't use a delegating constructor since we need to // reference this::onAnimationFinished mSurfaceAnimator = - new SurfaceAnimator(this, null /* animationFinishedCallback */, + new SurfaceAnimator(this, this::onAnimationFinished /* animationFinishedCallback */, container.mWmService); } mWidth = thumbnailHeader.getWidth(); @@ -130,14 +132,19 @@ class WindowContainerThumbnail implements Animatable { new WindowAnimationSpec(anim, position, mWindowContainer.getDisplayContent().mAppTransition.canSkipFirstFrame(), mWindowContainer.getDisplayContent().getWindowCornerRadius()), - mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */); + mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */, + ANIMATION_TYPE_RECENTS, null /* animationFinishedCallback */); } /** * Start animation with existing adapter. */ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) { - mSurfaceAnimator.startAnimation(t, anim, hidden); + mSurfaceAnimator.startAnimation(t, anim, hidden, ANIMATION_TYPE_RECENTS, + null /* animationFinishedCallback */); + } + + private void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) { } void setShowing(Transaction pendingTransaction, boolean show) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7f845671d3d1..563bb26a280f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -397,7 +397,7 @@ public class WindowManagerService extends IWindowManager.Stub * @see #HIERARCHICAL_ANIMATIONS_PROPERTY */ static boolean sHierarchicalAnimations = - SystemProperties.getBoolean(HIERARCHICAL_ANIMATIONS_PROPERTY, false); + SystemProperties.getBoolean(HIERARCHICAL_ANIMATIONS_PROPERTY, true); // Enums for animation scale update types. @Retention(RetentionPolicy.SOURCE) @@ -938,7 +938,7 @@ public class WindowManagerService extends IWindowManager.Stub * Whether the UI is currently running in touch mode (not showing * navigational focus because the user is directly pressing the screen). */ - boolean mInTouchMode; + private boolean mInTouchMode; private ViewServer mViewServer; final ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<>(); @@ -3461,6 +3461,12 @@ public class WindowManagerService extends IWindowManager.Stub mInputManager.setInTouchMode(mode); } + boolean getInTouchMode() { + synchronized (mGlobalLock) { + return mInTouchMode; + } + } + public void showEmulatorDisplayOverlayIfNeeded() { if (mContext.getResources().getBoolean( com.android.internal.R.bool.config_windowEnableCircularEmulatorDisplayOverlay) diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index fc04126714d1..1faf48fadad7 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -115,6 +115,7 @@ import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RESIZE; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; @@ -231,6 +232,7 @@ import com.android.internal.util.ToBooleanFunction; import com.android.server.policy.WindowManagerPolicy; import com.android.server.protolog.common.ProtoLog; import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; +import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.utils.WmDisplayCutout; import java.io.PrintWriter; @@ -5027,12 +5029,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } private void startAnimation(Transaction t, AnimationAdapter adapter) { - startAnimation(t, adapter, mWinAnimator.mLastHidden, null /* animationFinishedCallback */); + startAnimation(t, adapter, mWinAnimator.mLastHidden, ANIMATION_TYPE_WINDOW_ANIMATION, + null /* animationFinishedCallback */); } @Override - protected void onAnimationFinished() { - super.onAnimationFinished(); + protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) { + super.onAnimationFinished(type, anim); mWinAnimator.onAnimationFinished(); } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 4a2636ee6ca4..390068e1fa75 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -22,6 +22,8 @@ cc_library_static { "BroadcastRadio/TunerCallback.cpp", "BroadcastRadio/convert.cpp", "BroadcastRadio/regions.cpp", + "stats/PowerStatsPuller.cpp", + "stats/SubsystemSleepStatePuller.cpp", "com_android_server_am_BatteryStatsService.cpp", "com_android_server_connectivity_Vpn.cpp", "com_android_server_ConsumerIrService.cpp", @@ -37,6 +39,7 @@ cc_library_static { "com_android_server_security_VerityUtils.cpp", "com_android_server_SerialService.cpp", "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp", + "com_android_server_stats_pull_StatsPullAtomService.cpp", "com_android_server_storage_AppFuseBridge.cpp", "com_android_server_SystemServer.cpp", "com_android_server_TestNetworkService.cpp", diff --git a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp new file mode 100644 index 000000000000..f5b778e85e5c --- /dev/null +++ b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "StatsPullAtomService" + +#include <jni.h> +#include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <stats_event.h> +#include <stats_pull_atom_callback.h> +#include <statslog.h> + +#include "stats/PowerStatsPuller.h" +#include "stats/SubsystemSleepStatePuller.h" + +namespace android { + +static server::stats::PowerStatsPuller gPowerStatsPuller; +static server::stats::SubsystemSleepStatePuller gSubsystemSleepStatePuller; + +static status_pull_atom_return_t onDevicePowerMeasurementCallback(int32_t atom_tag, + pulled_stats_event_list* data, + void* cookie) { + return gPowerStatsPuller.Pull(atom_tag, data); +} + +static status_pull_atom_return_t subsystemSleepStateCallback(int32_t atom_tag, + pulled_stats_event_list* data, + void* cookie) { + return gSubsystemSleepStatePuller.Pull(atom_tag, data); +} + +static void nativeInit(JNIEnv* env, jobject javaObject) { + // on device power measurement + gPowerStatsPuller = server::stats::PowerStatsPuller(); + register_stats_pull_atom_callback(android::util::ON_DEVICE_POWER_MEASUREMENT, + onDevicePowerMeasurementCallback, + /* metadata= */ nullptr, + /* cookie= */ nullptr); + + // subsystem sleep state + gSubsystemSleepStatePuller = server::stats::SubsystemSleepStatePuller(); + register_stats_pull_atom_callback(android::util::SUBSYSTEM_SLEEP_STATE, + subsystemSleepStateCallback, + /* metadata= */ nullptr, + /* cookie= */ nullptr); +} + +static const JNINativeMethod sMethods[] = {{"nativeInit", "()V", (void*)nativeInit}}; + +int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/stats/pull/StatsPullAtomService", + sMethods, NELEM(sMethods)); + if (res < 0) { + ALOGE("failed to register native methods"); + } + return res; +} + +} // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 19fa062bd9f9..1202ad33996d 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -59,6 +59,7 @@ int register_android_server_am_LowMemDetector(JNIEnv* env); int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl( JNIEnv* env); int register_android_server_incremental_IncrementalManagerService(JNIEnv* env); +int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env); }; using namespace android; @@ -111,5 +112,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl( env); register_android_server_incremental_IncrementalManagerService(env); + register_android_server_stats_pull_StatsPullAtomService(env); return JNI_VERSION_1_4; } diff --git a/services/core/jni/stats/OWNERS b/services/core/jni/stats/OWNERS new file mode 100644 index 000000000000..a61babf32e58 --- /dev/null +++ b/services/core/jni/stats/OWNERS @@ -0,0 +1,9 @@ +jeffreyhuang@google.com +joeo@google.com +jtnguyen@google.com +muhammadq@google.com +ruchirr@google.com +singhtejinder@google.com +tsaichristine@google.com +yaochen@google.com +yro@google.com diff --git a/cmds/statsd/src/external/PowerStatsPuller.cpp b/services/core/jni/stats/PowerStatsPuller.cpp index dc69b78f0329..e80b5cfc4a71 100644 --- a/cmds/statsd/src/external/PowerStatsPuller.cpp +++ b/services/core/jni/stats/PowerStatsPuller.cpp @@ -14,31 +14,28 @@ * limitations under the License. */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" +#define DEBUG false // STOPSHIP if true +#define LOG_TAG "PowerStatsPuller" #include <android/hardware/power/stats/1.0/IPowerStats.h> +#include <log/log.h> +#include <statslog.h> #include <vector> #include "PowerStatsPuller.h" -#include "statslog.h" -#include "stats_log_util.h" using android::hardware::hidl_vec; -using android::hardware::power::stats::V1_0::IPowerStats; +using android::hardware::Return; +using android::hardware::Void; using android::hardware::power::stats::V1_0::EnergyData; +using android::hardware::power::stats::V1_0::IPowerStats; using android::hardware::power::stats::V1_0::RailInfo; using android::hardware::power::stats::V1_0::Status; -using android::hardware::Return; -using android::hardware::Void; - -using std::make_shared; -using std::shared_ptr; namespace android { -namespace os { -namespace statsd { +namespace server { +namespace stats { static sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHal = nullptr; static std::mutex gPowerStatsHalMutex; @@ -47,7 +44,7 @@ static std::vector<RailInfo> gRailInfo; struct PowerStatsPullerDeathRecipient : virtual public hardware::hidl_death_recipient { virtual void serviceDied(uint64_t cookie, - const wp<android::hidl::base::V1_0::IBase>& who) override { + const wp<android::hidl::base::V1_0::IBase>& who) override { // The HAL just died. Reset all handles to HAL services. std::lock_guard<std::mutex> lock(gPowerStatsHalMutex); gPowerStatsHal = nullptr; @@ -67,7 +64,7 @@ static bool getPowerStatsHalLocked() { hardware::Return<bool> linked = gPowerStatsHal->linkToDeath(gDeathRecipient, 0); if (!linked.isOk()) { ALOGE("Transaction error in linking to power.stats HAL death: %s", - linked.description().c_str()); + linked.description().c_str()); gPowerStatsHal = nullptr; return false; } else if (!linked) { @@ -79,29 +76,22 @@ static bool getPowerStatsHalLocked() { return gPowerStatsHal != nullptr; } -PowerStatsPuller::PowerStatsPuller() : StatsPuller(android::util::ON_DEVICE_POWER_MEASUREMENT) { -} +PowerStatsPuller::PowerStatsPuller() {} -bool PowerStatsPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { +status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_event_list* data) { std::lock_guard<std::mutex> lock(gPowerStatsHalMutex); if (!getPowerStatsHalLocked()) { - return false; + return STATS_PULL_SKIP; } - int64_t wallClockTimestampNs = getWallClockNs(); - int64_t elapsedTimestampNs = getElapsedRealtimeNs(); - - data->clear(); - // Pull getRailInfo if necessary if (gRailInfo.empty()) { bool resultSuccess = true; Return<void> ret = gPowerStatsHal->getRailInfo( - [&resultSuccess](const hidl_vec<RailInfo> &list, Status status) { + [&resultSuccess](const hidl_vec<RailInfo>& list, Status status) { resultSuccess = (status == Status::SUCCESS || status == Status::NOT_SUPPORTED); if (status != Status::SUCCESS) return; - gRailInfo.reserve(list.size()); for (size_t i = 0; i < list.size(); ++i) { gRailInfo.push_back(list[i]); @@ -110,61 +100,64 @@ bool PowerStatsPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { if (!resultSuccess || !ret.isOk()) { ALOGE("power.stats getRailInfo() failed. Description: %s", ret.description().c_str()); gPowerStatsHal = nullptr; - return false; + return STATS_PULL_SKIP; } // If SUCCESS but empty, or if NOT_SUPPORTED, then never try again. if (gRailInfo.empty()) { ALOGE("power.stats has no rail information"); gPowerStatsExist = false; // No rail info, so never try again. gPowerStatsHal = nullptr; - return false; + return STATS_PULL_SKIP; } } // Pull getEnergyData and write the data out const hidl_vec<uint32_t> desiredRailIndices; // Empty vector indicates we want all. bool resultSuccess = true; - Return<void> ret = gPowerStatsHal->getEnergyData(desiredRailIndices, - [&data, wallClockTimestampNs, elapsedTimestampNs, &resultSuccess] - (hidl_vec<EnergyData> energyDataList, Status status) { - resultSuccess = (status == Status::SUCCESS); - if (!resultSuccess) return; - - for (size_t i = 0; i < energyDataList.size(); i++) { - const EnergyData& energyData = energyDataList[i]; - - if (energyData.index >= gRailInfo.size()) { - ALOGE("power.stats getEnergyData() returned an invalid rail index %u.", - energyData.index); - resultSuccess = false; - return; - } - const RailInfo& rail = gRailInfo[energyData.index]; - - auto ptr = make_shared<LogEvent>(android::util::ON_DEVICE_POWER_MEASUREMENT, - wallClockTimestampNs, elapsedTimestampNs); - ptr->write(rail.subsysName); - ptr->write(rail.railName); - ptr->write(energyData.timestamp); - ptr->write(energyData.energy); - ptr->init(); - data->push_back(ptr); - - VLOG("power.stat: %s.%s: %llu, %llu", - rail.subsysName.c_str(), - rail.railName.c_str(), - (unsigned long long)energyData.timestamp, - (unsigned long long)energyData.energy); - } - }); + Return<void> ret = + gPowerStatsHal + ->getEnergyData(desiredRailIndices, + [&data, &resultSuccess](hidl_vec<EnergyData> energyDataList, + Status status) { + resultSuccess = (status == Status::SUCCESS); + if (!resultSuccess) return; + + for (size_t i = 0; i < energyDataList.size(); i++) { + const EnergyData& energyData = energyDataList[i]; + + if (energyData.index >= gRailInfo.size()) { + ALOGE("power.stats getEnergyData() returned an " + "invalid rail index %u.", + energyData.index); + resultSuccess = false; + return; + } + const RailInfo& rail = gRailInfo[energyData.index]; + + stats_event* event = add_stats_event_to_pull_data(data); + stats_event_set_atom_id(event, + android::util::ON_DEVICE_POWER_MEASUREMENT); + stats_event_write_string8(event, + rail.subsysName.c_str()); + stats_event_write_string8(event, rail.railName.c_str()); + stats_event_write_int64(event, energyData.timestamp); + stats_event_write_int64(event, energyData.energy); + stats_event_build(event); + + ALOGV("power.stat: %s.%s: %llu, %llu", + rail.subsysName.c_str(), rail.railName.c_str(), + (unsigned long long)energyData.timestamp, + (unsigned long long)energyData.energy); + } + }); if (!resultSuccess || !ret.isOk()) { ALOGE("power.stats getEnergyData() failed. Description: %s", ret.description().c_str()); gPowerStatsHal = nullptr; - return false; + return STATS_PULL_SKIP; } - return true; + return STATS_PULL_SUCCESS; } -} // namespace statsd -} // namespace os -} // namespace android +} // namespace stats +} // namespace server +} // namespace android diff --git a/cmds/statsd/src/external/PowerStatsPuller.h b/services/core/jni/stats/PowerStatsPuller.h index 6f15bd68fa94..048dbb933f52 100644 --- a/cmds/statsd/src/external/PowerStatsPuller.h +++ b/services/core/jni/stats/PowerStatsPuller.h @@ -16,23 +16,22 @@ #pragma once -#include "StatsPuller.h" +#include <stats_event.h> +#include <stats_pull_atom_callback.h> namespace android { -namespace os { -namespace statsd { +namespace server { +namespace stats { /** * Reads hal for power.stats */ -class PowerStatsPuller : public StatsPuller { +class PowerStatsPuller { public: PowerStatsPuller(); - -private: - bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override; + status_pull_atom_return_t Pull(int32_t atomTag, pulled_stats_event_list* data); }; -} // namespace statsd -} // namespace os -} // namespace android +} // namespace stats +} // namespace server +} // namespace android diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp b/services/core/jni/stats/SubsystemSleepStatePuller.cpp index f6a4aeaa3f9e..c6a836cfb6d5 100644 --- a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp +++ b/services/core/jni/stats/SubsystemSleepStatePuller.cpp @@ -15,7 +15,10 @@ */ #define DEBUG false // STOPSHIP if true -#include "Log.h" +#define LOG_TAG "SubsystemSleepStatePuller" + +#include <log/log.h> +#include <statslog.h> #include <android/hardware/power/1.0/IPower.h> #include <android/hardware/power/1.1/IPower.h> @@ -32,13 +35,8 @@ #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> -#include "external/SubsystemSleepStatePuller.h" -#include "external/StatsPuller.h" - +#include <unordered_map> #include "SubsystemSleepStatePuller.h" -#include "logd/LogEvent.h" -#include "statslog.h" -#include "stats_log_util.h" using android::hardware::hidl_vec; using android::hardware::power::V1_0::IPower; @@ -53,14 +51,12 @@ using android::hardware::power::stats::V1_0::PowerEntityStateSpace; using android::hardware::Return; using android::hardware::Void; -using std::make_shared; -using std::shared_ptr; - namespace android { -namespace os { -namespace statsd { +namespace server { +namespace stats { -static std::function<bool(vector<shared_ptr<LogEvent>>* data)> gPuller = {}; +static std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list* data)> + gPuller = {}; static sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr; static sp<android::hardware::power::V1_1::IPower> gPowerHalV1_1 = nullptr; @@ -91,9 +87,7 @@ struct SubsystemSleepStatePullerDeathRecipient : virtual public hardware::hidl_d static sp<SubsystemSleepStatePullerDeathRecipient> gDeathRecipient = new SubsystemSleepStatePullerDeathRecipient(); -SubsystemSleepStatePuller::SubsystemSleepStatePuller() : - StatsPuller(android::util::SUBSYSTEM_SLEEP_STATE) { -} +SubsystemSleepStatePuller::SubsystemSleepStatePuller() {} // The caller must be holding gPowerHalMutex. static bool checkResultLocked(const Return<void> &ret, const char* function) { @@ -182,48 +176,46 @@ static bool getPowerStatsHalLocked() { } // The caller must be holding gPowerHalMutex. -static bool getIPowerStatsDataLocked(vector<shared_ptr<LogEvent>>* data) { +static status_pull_atom_return_t getIPowerStatsDataLocked(int32_t atomTag, + pulled_stats_event_list* data) { using android::hardware::power::stats::V1_0::Status; if(!getPowerStatsHalLocked()) { - return false; + return STATS_PULL_SKIP; } - - int64_t wallClockTimestampNs = getWallClockNs(); - int64_t elapsedTimestampNs = getElapsedRealtimeNs(); - // Get power entity state residency data bool success = false; - Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({}, - [&data, &success, wallClockTimestampNs, elapsedTimestampNs] - (auto results, auto status) { - if (status == Status::NOT_SUPPORTED) { - ALOGW("getPowerEntityStateResidencyData is not supported"); - success = false; - return; - } - - for(auto result : results) { - for(auto stateResidency : result.stateResidencyData) { - auto statePtr = make_shared<LogEvent>( - android::util::SUBSYSTEM_SLEEP_STATE, - wallClockTimestampNs, elapsedTimestampNs); - statePtr->write(gEntityNames.at(result.powerEntityId)); - statePtr->write(gStateNames.at(result.powerEntityId) - .at(stateResidency.powerEntityStateId)); - statePtr->write(stateResidency.totalStateEntryCount); - statePtr->write(stateResidency.totalTimeInStateMs); - statePtr->init(); - data->emplace_back(statePtr); - } - } - success = true; - }); + Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData( + {}, [&data, &success](auto results, auto status) { + if (status == Status::NOT_SUPPORTED) { + ALOGW("getPowerEntityStateResidencyData is not supported"); + success = false; + return; + } + for (auto result : results) { + for (auto stateResidency : result.stateResidencyData) { + stats_event* event = add_stats_event_to_pull_data(data); + stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE); + stats_event_write_string8(event, + gEntityNames.at(result.powerEntityId).c_str()); + stats_event_write_string8(event, + gStateNames.at(result.powerEntityId) + .at(stateResidency.powerEntityStateId) + .c_str()); + stats_event_write_int64(event, stateResidency.totalStateEntryCount); + stats_event_write_int64(event, stateResidency.totalTimeInStateMs); + stats_event_build(event); + } + } + success = true; + }); // Intentionally not returning early here. // bool success determines if this succeeded or not. checkResultLocked(ret, __func__); - - return success; + if (!success) { + return STATS_PULL_SKIP; + } + return STATS_PULL_SUCCESS; } // The caller must be holding gPowerHalMutex. @@ -252,56 +244,51 @@ static bool getPowerHalLocked() { } // The caller must be holding gPowerHalMutex. -static bool getIPowerDataLocked(vector<shared_ptr<LogEvent>>* data) { +static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, + pulled_stats_event_list* data) { using android::hardware::power::V1_0::Status; if(!getPowerHalLocked()) { - return false; + return STATS_PULL_SKIP; } - int64_t wallClockTimestampNs = getWallClockNs(); - int64_t elapsedTimestampNs = getElapsedRealtimeNs(); Return<void> ret; ret = gPowerHalV1_0->getPlatformLowPowerStats( - [&data, wallClockTimestampNs, elapsedTimestampNs] - (hidl_vec<PowerStatePlatformSleepState> states, Status status) { + [&data](hidl_vec<PowerStatePlatformSleepState> states, Status status) { if (status != Status::SUCCESS) return; for (size_t i = 0; i < states.size(); i++) { const PowerStatePlatformSleepState& state = states[i]; - - auto statePtr = make_shared<LogEvent>( - android::util::SUBSYSTEM_SLEEP_STATE, - wallClockTimestampNs, elapsedTimestampNs); - statePtr->write(state.name); - statePtr->write(""); - statePtr->write(state.totalTransitions); - statePtr->write(state.residencyInMsecSinceBoot); - statePtr->init(); - data->push_back(statePtr); - VLOG("powerstate: %s, %lld, %lld, %d", state.name.c_str(), - (long long)state.residencyInMsecSinceBoot, - (long long)state.totalTransitions, - state.supportedOnlyInSuspend ? 1 : 0); + stats_event* event = add_stats_event_to_pull_data(data); + stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE); + stats_event_write_string8(event, state.name.c_str()); + stats_event_write_string8(event, ""); + stats_event_write_int64(event, state.totalTransitions); + stats_event_write_int64(event, state.residencyInMsecSinceBoot); + stats_event_build(event); + + ALOGV("powerstate: %s, %lld, %lld, %d", state.name.c_str(), + (long long)state.residencyInMsecSinceBoot, + (long long)state.totalTransitions, + state.supportedOnlyInSuspend ? 1 : 0); for (const auto& voter : state.voters) { - auto voterPtr = make_shared<LogEvent>( - android::util::SUBSYSTEM_SLEEP_STATE, - wallClockTimestampNs, elapsedTimestampNs); - voterPtr->write(state.name); - voterPtr->write(voter.name); - voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot); - voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot); - voterPtr->init(); - data->push_back(voterPtr); - VLOG("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(), - voter.name.c_str(), - (long long)voter.totalTimeInMsecVotedForSinceBoot, - (long long)voter.totalNumberOfTimesVotedSinceBoot); + stats_event* event = add_stats_event_to_pull_data(data); + stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE); + stats_event_write_string8(event, state.name.c_str()); + stats_event_write_string8(event, voter.name.c_str()); + stats_event_write_int64(event, voter.totalNumberOfTimesVotedSinceBoot); + stats_event_write_int64(event, voter.totalTimeInMsecVotedForSinceBoot); + stats_event_build(event); + + ALOGV("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(), + voter.name.c_str(), + (long long)voter.totalTimeInMsecVotedForSinceBoot, + (long long)voter.totalNumberOfTimesVotedSinceBoot); } } }); if (!checkResultLocked(ret, __func__)) { - return false; + return STATS_PULL_SKIP; } // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1 @@ -309,41 +296,42 @@ static bool getIPowerDataLocked(vector<shared_ptr<LogEvent>>* data) { android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0); if (gPowerHal_1_1 != nullptr) { ret = gPowerHal_1_1->getSubsystemLowPowerStats( - [&data, wallClockTimestampNs, elapsedTimestampNs] - (hidl_vec<PowerStateSubsystem> subsystems, Status status) { - if (status != Status::SUCCESS) return; - - if (subsystems.size() > 0) { - for (size_t i = 0; i < subsystems.size(); i++) { - const PowerStateSubsystem& subsystem = subsystems[i]; - for (size_t j = 0; j < subsystem.states.size(); j++) { - const PowerStateSubsystemSleepState& state = - subsystem.states[j]; - auto subsystemStatePtr = make_shared<LogEvent>( - android::util::SUBSYSTEM_SLEEP_STATE, - wallClockTimestampNs, elapsedTimestampNs); - subsystemStatePtr->write(subsystem.name); - subsystemStatePtr->write(state.name); - subsystemStatePtr->write(state.totalTransitions); - subsystemStatePtr->write(state.residencyInMsecSinceBoot); - subsystemStatePtr->init(); - data->push_back(subsystemStatePtr); - VLOG("subsystemstate: %s, %s, %lld, %lld, %lld", - subsystem.name.c_str(), state.name.c_str(), - (long long)state.residencyInMsecSinceBoot, - (long long)state.totalTransitions, - (long long)state.lastEntryTimestampMs); + [&data](hidl_vec<PowerStateSubsystem> subsystems, Status status) { + if (status != Status::SUCCESS) return; + + if (subsystems.size() > 0) { + for (size_t i = 0; i < subsystems.size(); i++) { + const PowerStateSubsystem& subsystem = subsystems[i]; + for (size_t j = 0; j < subsystem.states.size(); j++) { + const PowerStateSubsystemSleepState& state = + subsystem.states[j]; + stats_event* event = add_stats_event_to_pull_data(data); + stats_event_set_atom_id(event, + android::util::SUBSYSTEM_SLEEP_STATE); + stats_event_write_string8(event, subsystem.name.c_str()); + stats_event_write_string8(event, state.name.c_str()); + stats_event_write_int64(event, state.totalTransitions); + stats_event_write_int64(event, state.residencyInMsecSinceBoot); + stats_event_build(event); + + ALOGV("subsystemstate: %s, %s, %lld, %lld, %lld", + subsystem.name.c_str(), state.name.c_str(), + (long long)state.residencyInMsecSinceBoot, + (long long)state.totalTransitions, + (long long)state.lastEntryTimestampMs); + } + } } - } - } - }); + }); } - return true; + return STATS_PULL_SUCCESS; } // The caller must be holding gPowerHalMutex. -std::function<bool(vector<shared_ptr<LogEvent>>* data)> getPullerLocked() { - std::function<bool(vector<shared_ptr<LogEvent>>* data)> ret = {}; +std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list* data)> +getPullerLocked() { + std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list * data)> ret = + {}; // First see if power.stats HAL is available. Fall back to power HAL if // power.stats HAL is unavailable. @@ -358,7 +346,8 @@ std::function<bool(vector<shared_ptr<LogEvent>>* data)> getPullerLocked() { return ret; } -bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { +status_pull_atom_return_t SubsystemSleepStatePuller::Pull(int32_t atomTag, + pulled_stats_event_list* data) { std::lock_guard<std::mutex> lock(gPowerHalMutex); if(!gPuller) { @@ -366,13 +355,13 @@ bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) } if(gPuller) { - return gPuller(data); + return gPuller(atomTag, data); } ALOGE("Unable to load Power Hal or power.stats HAL"); - return false; + return STATS_PULL_SKIP; } -} // namespace statsd -} // namespace os +} // namespace stats +} // namespace server } // namespace android diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.h b/services/core/jni/stats/SubsystemSleepStatePuller.h index 87f5f02614a9..59dbbd258401 100644 --- a/cmds/statsd/src/external/SubsystemSleepStatePuller.h +++ b/services/core/jni/stats/SubsystemSleepStatePuller.h @@ -16,24 +16,22 @@ #pragma once -#include <utils/String16.h> -#include "StatsPuller.h" +#include <stats_event.h> +#include <stats_pull_atom_callback.h> namespace android { -namespace os { -namespace statsd { +namespace server { +namespace stats { /** * Reads hal for sleep states */ -class SubsystemSleepStatePuller : public StatsPuller { +class SubsystemSleepStatePuller { public: SubsystemSleepStatePuller(); - -private: - bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override; + status_pull_atom_return_t Pull(int32_t atomTag, pulled_stats_event_list* data); }; -} // namespace statsd -} // namespace os -} // namespace android +} // namespace stats +} // namespace server +} // namespace android diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index ec3ef7807c3a..65cabadaa3d8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -15077,9 +15077,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return packages; } - private List<String> getDefaultCrossProfilePackages() { - return Arrays.asList(mContext.getResources() + @Override + public List<String> getDefaultCrossProfilePackages() { + Set<String> crossProfilePackages = new HashSet<>(); + + Collections.addAll(crossProfilePackages, mContext.getResources() .getStringArray(R.array.cross_profile_apps)); + Collections.addAll(crossProfilePackages, mContext.getResources() + .getStringArray(R.array.vendor_cross_profile_apps)); + + return new ArrayList<>(crossProfilePackages); } private List<ActiveAdmin> getProfileOwnerAdminsForCurrentProfileGroup() { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 2a6b0294a031..6ba4330f3916 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -342,6 +342,7 @@ public final class SystemServer { private static final String START_SENSOR_SERVICE = "StartSensorService"; private static final String START_HIDL_SERVICES = "StartHidlServices"; + private static final String START_BLOB_STORE_SERVICE = "startBlobStoreManagerService"; private static final String SYSPROP_START_COUNT = "sys.system_server.start_count"; private static final String SYSPROP_START_ELAPSED = "sys.system_server.start_elapsed"; @@ -349,6 +350,7 @@ public final class SystemServer { private Future<?> mSensorServiceStart; private Future<?> mZygotePreload; + private Future<?> mBlobStoreServiceStart; /** * Start the sensor service. This is a blocking call and can take time. @@ -1795,6 +1797,13 @@ public final class SystemServer { t.traceEnd(); } + mBlobStoreServiceStart = SystemServerInitThreadPool.submit(() -> { + final TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog(); + traceLog.traceBegin(START_BLOB_STORE_SERVICE); + mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS); + traceLog.traceEnd(); + }, START_BLOB_STORE_SERVICE); + // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode) t.traceBegin("StartDreamManager"); mSystemServiceManager.startService(DreamManagerService.class); @@ -2039,10 +2048,6 @@ public final class SystemServer { mSystemServiceManager.startService(ClipboardService.class); t.traceEnd(); - t.traceBegin("StartBlobStoreManagerService"); - mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS); - t.traceEnd(); - t.traceBegin("AppServiceManager"); mSystemServiceManager.startService(AppBindingService.Lifecycle.class); t.traceEnd(); @@ -2160,6 +2165,9 @@ public final class SystemServer { mSystemServiceManager.startService(APP_SEARCH_MANAGER_SERVICE_CLASS); t.traceEnd(); + ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart, + START_BLOB_STORE_SERVICE); + // These are needed to propagate to the runnable below. final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index f99081024494..8381205fa48e 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -46,6 +46,7 @@ android_test { "service-appsearch", "service-jobscheduler", "service-permission", + "service-blobstore", // TODO: remove once Android migrates to JUnit 4.12, // which provides assertThrows "testng", diff --git a/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java new file mode 100644 index 000000000000..ff728e7a4017 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java @@ -0,0 +1,189 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.blob; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.blob.BlobHandle; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; +import android.util.LongSparseArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.blob.BlobStoreManagerService.Injector; +import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class BlobStoreManagerServiceTest { + private Context mContext; + private Handler mHandler; + private BlobStoreManagerService mService; + + private LongSparseArray<BlobStoreSession> mUserSessions; + private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs; + + private SessionStateChangeListener mStateChangeListener; + + private static final String TEST_PKG1 = "com.example1"; + private static final String TEST_PKG2 = "com.example2"; + private static final String TEST_PKG3 = "com.example3"; + + private static final int TEST_UID1 = 10001; + private static final int TEST_UID2 = 10002; + private static final int TEST_UID3 = 10003; + + @Before + public void setUp() { + // Share classloader to allow package private access. + System.setProperty("dexmaker.share_classloader", "true"); + + mContext = InstrumentationRegistry.getTargetContext(); + mHandler = new TestHandler(Looper.getMainLooper()); + mService = new BlobStoreManagerService(mContext, new TestInjector()); + mUserSessions = new LongSparseArray<>(); + mUserBlobs = new ArrayMap<>(); + + mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId()); + mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId()); + + mStateChangeListener = mService.new SessionStateChangeListener(); + } + + @Test + public void testHandlePackageRemoved() throws Exception { + // Setup sessions + final File sessionFile1 = mock(File.class); + final long sessionId1 = 11; + final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, + sessionId1, sessionFile1); + mUserSessions.append(sessionId1, session1); + + final File sessionFile2 = mock(File.class); + final long sessionId2 = 25; + final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2, + sessionId2, sessionFile2); + mUserSessions.append(sessionId2, session2); + + final File sessionFile3 = mock(File.class); + final long sessionId3 = 37; + final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3, + sessionId3, sessionFile3); + mUserSessions.append(sessionId3, session3); + + final File sessionFile4 = mock(File.class); + final long sessionId4 = 48; + final BlobStoreSession session4 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, + sessionId4, sessionFile4); + mUserSessions.append(sessionId4, session4); + + // Setup blobs + final File blobFile1 = mock(File.class); + final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), + "label1", System.currentTimeMillis(), "tag1"); + final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobFile1, true); + mUserBlobs.put(blobHandle1, blobMetadata1); + + final File blobFile2 = mock(File.class); + final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), + "label2", System.currentTimeMillis(), "tag2"); + final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobFile2, false); + mUserBlobs.put(blobHandle2, blobMetadata2); + + // Invoke test method + mService.handlePackageRemoved(TEST_PKG1, TEST_UID1); + + // Verify sessions are removed + verify(sessionFile1).delete(); + verify(sessionFile2, never()).delete(); + verify(sessionFile3, never()).delete(); + verify(sessionFile4).delete(); + + assertThat(mUserSessions.size()).isEqualTo(2); + assertThat(mUserSessions.get(sessionId1)).isNull(); + assertThat(mUserSessions.get(sessionId2)).isNotNull(); + assertThat(mUserSessions.get(sessionId3)).isNotNull(); + assertThat(mUserSessions.get(sessionId4)).isNull(); + + // Verify blobs are removed + verify(blobMetadata1).removeCommitter(TEST_PKG1, TEST_UID1); + verify(blobMetadata1).removeLeasee(TEST_PKG1, TEST_UID1); + verify(blobMetadata2).removeCommitter(TEST_PKG1, TEST_UID1); + verify(blobMetadata2).removeLeasee(TEST_PKG1, TEST_UID1); + + verify(blobFile1, never()).delete(); + verify(blobFile2).delete(); + + assertThat(mUserBlobs.size()).isEqualTo(1); + assertThat(mUserBlobs.get(blobHandle1)).isNotNull(); + assertThat(mUserBlobs.get(blobHandle2)).isNull(); + } + + private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid, + long sessionId, File sessionFile) { + final BlobStoreSession session = mock(BlobStoreSession.class); + when(session.getOwnerPackageName()).thenReturn(ownerPackageName); + when(session.getOwnerUid()).thenReturn(ownerUid); + when(session.getSessionId()).thenReturn(sessionId); + when(session.getSessionFile()).thenReturn(sessionFile); + return session; + } + + private BlobMetadata createBlobMetadataMock(File blobFile, boolean hasLeases) { + final BlobMetadata blobMetadata = mock(BlobMetadata.class); + when(blobMetadata.getBlobFile()).thenReturn(blobFile); + when(blobMetadata.hasLeases()).thenReturn(hasLeases); + return blobMetadata; + } + + private class TestHandler extends Handler { + TestHandler(Looper looper) { + super(looper); + } + + @Override + public void dispatchMessage(Message msg) { + // Ignore all messages + } + } + + private class TestInjector extends Injector { + @Override + public Handler initializeMessageHandler() { + return mHandler; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index f7a9e5456156..b193a34952f4 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -5817,6 +5817,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setCrossProfileAppsList(); + setVendorCrossProfileAppsList(); assertTrue(dpm.getAllCrossProfilePackages().isEmpty()); } @@ -5827,6 +5828,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.packageName = admin1.getPackageName(); setCrossProfileAppsList(); + setVendorCrossProfileAppsList(); initializeDpms(); assertTrue(dpm.getAllCrossProfilePackages().isEmpty()); @@ -5839,9 +5841,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.setCrossProfilePackages(admin1, packages); setCrossProfileAppsList("TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE"); + setVendorCrossProfileAppsList("TEST_VENDOR_DEFAULT_PACKAGE"); assertEquals(Sets.newSet( - "TEST_PACKAGE", "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE"), + "TEST_PACKAGE", "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE", + "TEST_VENDOR_DEFAULT_PACKAGE"), dpm.getAllCrossProfilePackages()); } @@ -5854,13 +5858,31 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.setCrossProfilePackages(admin1, packages); setCrossProfileAppsList("TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE"); + setVendorCrossProfileAppsList("TEST_VENDOR_DEFAULT_PACKAGE"); initializeDpms(); assertEquals(Sets.newSet( - "TEST_PACKAGE", "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE"), + "TEST_PACKAGE", "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE", + "TEST_VENDOR_DEFAULT_PACKAGE"), dpm.getAllCrossProfilePackages()); } + public void testGetDefaultCrossProfilePackages_noPackagesSet_returnsEmpty() { + setCrossProfileAppsList(); + setVendorCrossProfileAppsList(); + + assertThat(dpm.getDefaultCrossProfilePackages()).isEmpty(); + } + + public void testGetDefaultCrossProfilePackages_packagesSet_returnsCombinedSet() { + setCrossProfileAppsList("TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE"); + setVendorCrossProfileAppsList("TEST_VENDOR_DEFAULT_PACKAGE"); + + assertThat(dpm.getDefaultCrossProfilePackages()).isEqualTo(Sets.newSet( + "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE", "TEST_VENDOR_DEFAULT_PACKAGE" + )); + } + public void testSetCommonCriteriaMode_asDeviceOwner() throws Exception { setDeviceOwner(); @@ -5892,6 +5914,12 @@ public class DevicePolicyManagerTest extends DpmTestBase { .thenReturn(packages); } + private void setVendorCrossProfileAppsList(String... packages) { + when(mContext.getResources() + .getStringArray(eq(R.array.vendor_cross_profile_apps))) + .thenReturn(packages); + } + // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one. private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception { writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM, diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index d22502db69e4..4532400e3b34 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -16,9 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; - import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -27,17 +24,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import android.app.Activity; -import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.EnterPipRequestedItem; @@ -46,7 +40,6 @@ import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.view.IDisplayWindowListener; -import android.view.WindowContainerTransaction; import androidx.test.filters.MediumTest; @@ -126,47 +119,6 @@ public class ActivityTaskManagerServiceTests extends ActivityTestsBase { } @Test - public void testTaskTransaction() { - removeGlobalMinSizeRestriction(); - final ActivityStack stack = new StackBuilder(mRootWindowContainer) - .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - final Task task = stack.getTopMostTask(); - WindowContainerTransaction t = new WindowContainerTransaction(); - Rect newBounds = new Rect(10, 10, 100, 100); - t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100)); - mService.applyContainerTransaction(t); - assertEquals(newBounds, task.getBounds()); - } - - @Test - public void testStackTransaction() { - removeGlobalMinSizeRestriction(); - final ActivityStack stack = new StackBuilder(mRootWindowContainer) - .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - ActivityManager.StackInfo info = - mService.getStackInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); - WindowContainerTransaction t = new WindowContainerTransaction(); - assertEquals(stack.mRemoteToken, info.stackToken); - Rect newBounds = new Rect(10, 10, 100, 100); - t.setBounds(info.stackToken, new Rect(10, 10, 100, 100)); - mService.applyContainerTransaction(t); - assertEquals(newBounds, stack.getBounds()); - } - - @Test - public void testContainerChanges() { - removeGlobalMinSizeRestriction(); - final ActivityStack stack = new StackBuilder(mRootWindowContainer) - .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - final Task task = stack.getTopMostTask(); - WindowContainerTransaction t = new WindowContainerTransaction(); - assertTrue(task.isFocusable()); - t.setFocusable(stack.mRemoteToken, false); - mService.applyContainerTransaction(t); - assertFalse(task.isFocusable()); - } - - @Test public void testDisplayWindowListener() { final ArrayList<Integer> added = new ArrayList<>(); final ArrayList<Integer> changed = new ArrayList<>(); diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java index 71390dbbe4a3..0f5489575ee0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static org.junit.Assert.assertFalse; @@ -69,8 +70,10 @@ public class AnimatingActivityRegistryTest extends WindowTestsBase { final AnimatingActivityRegistry registry = activity1.getStack().getAnimatingActivityRegistry(); - activity1.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */); - activity2.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */); + activity1.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); + activity2.startAnimation(activity1.getPendingTransaction(), mAdapter, false /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); assertTrue(activity1.isAnimating(TRANSITION)); assertTrue(activity2.isAnimating(TRANSITION)); @@ -91,8 +94,10 @@ public class AnimatingActivityRegistryTest extends WindowTestsBase { final AnimatingActivityRegistry registry = window1.getStack().getAnimatingActivityRegistry(); - window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); - window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); + window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); + window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); assertTrue(window1.isAnimating(TRANSITION)); assertTrue(window2.isAnimating(TRANSITION)); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java index b0f3b42271de..6e78a271458a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.google.common.truth.Truth.assertThat; @@ -70,7 +71,8 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { public void clipAfterAnim_boundsLayerIsCreated() { mActivity.mNeedsAnimationBoundsLayer = true; - mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); verify(mTransaction).reparent(eq(mActivity.getSurfaceControl()), eq(mActivity.mSurfaceAnimator.mLeash)); verify(mTransaction).reparent(eq(mActivity.mSurfaceAnimator.mLeash), @@ -82,7 +84,8 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { mActivity.mNeedsAnimationBoundsLayer = true; mActivity.mNeedsZBoost = true; - mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); verify(mTransaction).setLayer(eq(mActivity.mAnimationBoundsLayer), intThat(layer -> layer >= ActivityRecord.Z_BOOST_BASE)); } @@ -91,15 +94,18 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { @FlakyTest(bugId = 131005232) public void clipAfterAnim_boundsLayerIsDestroyed() { mActivity.mNeedsAnimationBoundsLayer = true; - mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); final SurfaceControl leash = mActivity.mSurfaceAnimator.mLeash; final SurfaceControl animationBoundsLayer = mActivity.mAnimationBoundsLayer; final ArgumentCaptor<SurfaceAnimator.OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( SurfaceAnimator.OnAnimationFinishedCallback.class); - verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION), + callbackCaptor.capture()); - callbackCaptor.getValue().onAnimationFinished(mSpec); + callbackCaptor.getValue().onAnimationFinished( + ANIMATION_TYPE_APP_TRANSITION, mSpec); verify(mTransaction).remove(eq(leash)); verify(mTransaction).remove(eq(animationBoundsLayer)); assertThat(mActivity.mNeedsAnimationBoundsLayer).isFalse(); @@ -108,7 +114,8 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { @Test public void clipAfterAnimCancelled_boundsLayerIsDestroyed() { mActivity.mNeedsAnimationBoundsLayer = true; - mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); final SurfaceControl leash = mActivity.mSurfaceAnimator.mLeash; final SurfaceControl animationBoundsLayer = mActivity.mAnimationBoundsLayer; @@ -123,7 +130,8 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { public void clipNoneAnim_boundsLayerIsNotCreated() { mActivity.mNeedsAnimationBoundsLayer = false; - mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); verify(mTransaction).reparent(eq(mActivity.getSurfaceControl()), eq(mActivity.mSurfaceAnimator.mLeash)); assertThat(mActivity.mAnimationBoundsLayer).isNull(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index 7344fa42016c..77ceeedae1ac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -22,11 +22,13 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; @@ -37,6 +39,9 @@ import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.view.SurfaceSession; +import com.android.server.wm.SurfaceAnimator.AnimationType; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -114,11 +119,11 @@ public class DimmerTests extends WindowTestsBase { private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter { @Override public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, - AnimationAdapter anim, boolean hidden, - @Nullable Runnable animationFinishedCallback) { - surfaceAnimator.mStaticAnimationFinishedCallback.run(); + AnimationAdapter anim, boolean hidden, @AnimationType int type, + @Nullable OnAnimationFinishedCallback animationFinishedCallback) { + surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim); if (animationFinishedCallback != null) { - animationFinishedCallback.run(); + animationFinishedCallback.onAnimationFinished(type, anim); } } } @@ -224,7 +229,7 @@ public class DimmerTests extends WindowTestsBase { mDimmer.updateDims(mTransaction, new Rect()); verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any( SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), - isNull()); + eq(ANIMATION_TYPE_DIMMER), isNull()); verify(mHost.getPendingTransaction()).remove(dimLayer); } @@ -282,7 +287,7 @@ public class DimmerTests extends WindowTestsBase { mDimmer.updateDims(mTransaction, new Rect()); verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any( SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), - isNull()); + eq(ANIMATION_TYPE_DIMMER), isNull()); verify(mTransaction).remove(dimLayer); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java new file mode 100644 index 000000000000..618e6086b582 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; +import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; + +import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS; +import static com.android.server.wm.DisplayArea.Type.ANY; +import static com.android.server.wm.DisplayArea.Type.BELOW_TASKS; +import static com.android.server.wm.DisplayArea.Type.checkChild; +import static com.android.server.wm.DisplayArea.Type.checkSiblings; +import static com.android.server.wm.DisplayArea.Type.typeOf; +import static com.android.server.wm.testing.Assert.assertThrows; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import android.os.Binder; +import android.platform.test.annotations.Presubmit; +import android.view.SurfaceControl; + +import org.junit.Rule; +import org.junit.Test; + +@Presubmit +public class DisplayAreaTest { + + @Rule + public SystemServicesTestRule mWmsRule = new SystemServicesTestRule(); + + @Test + public void testDisplayArea_positionChanged_throwsIfIncompatibleChild() { + WindowManagerService wms = mWmsRule.getWindowManagerService(); + DisplayArea<WindowContainer> parent = new DisplayArea<>(wms, BELOW_TASKS, "Parent"); + DisplayArea<WindowContainer> child = new DisplayArea<>(wms, ANY, "Child"); + + assertThrows(IllegalStateException.class, () -> parent.addChild(child, 0)); + } + + @Test + public void testDisplayArea_positionChanged_throwsIfIncompatibleSibling() { + WindowManagerService wms = mWmsRule.getWindowManagerService(); + DisplayArea<WindowContainer> parent = new SurfacelessDisplayArea<>(wms, ANY, "Parent"); + DisplayArea<WindowContainer> child1 = new DisplayArea<>(wms, ANY, "Child1"); + DisplayArea<WindowContainer> child2 = new DisplayArea<>(wms, ANY, "Child2"); + + parent.addChild(child1, 0); + assertThrows(IllegalStateException.class, () -> parent.addChild(child2, 0)); + } + + @Test + public void testType_typeOf() { + WindowManagerService wms = mWmsRule.getWindowManagerService(); + + assertEquals(ABOVE_TASKS, typeOf(new DisplayArea<>(wms, ABOVE_TASKS, "test"))); + assertEquals(ANY, typeOf(new DisplayArea<>(wms, ANY, "test"))); + assertEquals(BELOW_TASKS, typeOf(new DisplayArea<>(wms, BELOW_TASKS, "test"))); + + assertEquals(ABOVE_TASKS, typeOf(createWindowToken(TYPE_APPLICATION_OVERLAY))); + assertEquals(ABOVE_TASKS, typeOf(createWindowToken(TYPE_PRESENTATION))); + assertEquals(BELOW_TASKS, typeOf(createWindowToken(TYPE_WALLPAPER))); + + assertThrows(IllegalArgumentException.class, () -> typeOf(mock(ActivityRecord.class))); + assertThrows(IllegalArgumentException.class, () -> typeOf(mock(WindowContainer.class))); + } + + @Test + public void testType_checkSiblings() { + checkSiblings(BELOW_TASKS, BELOW_TASKS); + checkSiblings(BELOW_TASKS, ANY); + checkSiblings(BELOW_TASKS, ABOVE_TASKS); + checkSiblings(ANY, ABOVE_TASKS); + checkSiblings(ABOVE_TASKS, ABOVE_TASKS); + + assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, BELOW_TASKS)); + assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, ANY)); + assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, ANY)); + assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, BELOW_TASKS)); + } + + @Test + public void testType_checkChild() { + checkChild(ANY, ANY); + checkChild(ANY, ABOVE_TASKS); + checkChild(ANY, BELOW_TASKS); + checkChild(ABOVE_TASKS, ABOVE_TASKS); + checkChild(BELOW_TASKS, BELOW_TASKS); + + assertThrows(IllegalStateException.class, () -> checkChild(ABOVE_TASKS, BELOW_TASKS)); + assertThrows(IllegalStateException.class, () -> checkChild(ABOVE_TASKS, ANY)); + assertThrows(IllegalStateException.class, () -> checkChild(BELOW_TASKS, ABOVE_TASKS)); + assertThrows(IllegalStateException.class, () -> checkChild(BELOW_TASKS, ANY)); + } + + private WindowToken createWindowToken(int type) { + return new WindowToken(mWmsRule.getWindowManagerService(), new Binder(), + type, false /* persist */, null /* displayContent */, + false /* canManageTokens */); + } + + private static class SurfacelessDisplayArea<T extends WindowContainer> extends DisplayArea<T> { + + SurfacelessDisplayArea(WindowManagerService wms, Type type, String name) { + super(wms, type, name); + } + + @Override + SurfaceControl.Builder makeChildSurface(WindowContainer child) { + return new MockSurfaceControlBuilder(); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 1637370df0f7..2ea00cec3f2e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -139,11 +139,11 @@ public class DisplayContentTests extends WindowTestsBase { mAppWindow, mChildAppWindowAbove, mDockedDividerWindow, + mImeWindow, + mImeDialogWindow, mStatusBarWindow, mNotificationShadeWindow, - mNavBarWindow, - mImeWindow, - mImeDialogWindow)); + mNavBarWindow)); } @Test @@ -232,11 +232,11 @@ public class DisplayContentTests extends WindowTestsBase { mChildAppWindowAbove, mDockedDividerWindow, voiceInteractionWindow, + mImeWindow, + mImeDialogWindow, mStatusBarWindow, mNotificationShadeWindow, - mNavBarWindow, - mImeWindow, - mImeDialogWindow)); + mNavBarWindow)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 945928d1b967..7753a32d8ca0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -37,6 +37,7 @@ import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -103,14 +104,15 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); AnimationAdapter adapter = mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */); - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS, + mFinishedCallback); // Remove the app window so that the animation target can not be created activity.removeImmediately(); mController.startAnimation(); // Verify that the finish callback to reparent the leash is called - verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), eq(adapter)); // Verify the animation canceled callback to the app was made verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */); verifyNoMoreInteractionsExceptAsBinder(mMockRunner); @@ -122,7 +124,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); AnimationAdapter adapter = mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */); - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS, + mFinishedCallback); // Remove the app window so that the animation target can not be created activity.removeImmediately(); diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java index a23425f2510c..be2559719438 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java @@ -18,6 +18,8 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -108,7 +110,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase { overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID; overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation( overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class), - false /* hidden */); + false /* hidden */, ANIMATION_TYPE_APP_TRANSITION); mPolicy.addNonHighRefreshRatePackage("com.android.test"); assertEquals(0, mPolicy.getPreferredModeId(overrideWindow)); } @@ -124,7 +126,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase { cameraUsingWindow.mActivityRecord.mSurfaceAnimator.startAnimation( cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class), - false /* hidden */); + false /* hidden */, ANIMATION_TYPE_APP_TRANSITION); assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 1a575962b961..3a724a140ffb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -25,6 +25,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -97,7 +99,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { try { final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); mController.goodToGo(); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = @@ -122,7 +125,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { verify(mMockTransaction).setWindowCrop(mMockLeash, 100, 50); finishedCaptor.getValue().onAnimationFinished(); - verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), + eq(adapter)); } finally { mDisplayContent.mOpeningApps.clear(); } @@ -133,7 +137,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); mController.goodToGo(); adapter.onAnimationCancelled(mMockLeash); @@ -146,14 +151,16 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); mController.goodToGo(); mClock.fastForward(2500); mHandler.timeAdvance(); verify(mMockRunner).onAnimationCancelled(); - verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), + eq(adapter)); } @Test @@ -164,7 +171,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { "testWin"); final AnimationAdapter adapter = mController.createRemoteAnimationRecord( win.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); mController.goodToGo(); mClock.fastForward(2500); @@ -176,7 +184,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mHandler.timeAdvance(); verify(mMockRunner).onAnimationCancelled(); - verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), + eq(adapter)); } finally { mWm.setAnimationScale(2, 1.0f); } @@ -205,7 +214,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { new Point(50, 100), new Rect(50, 100, 150, 150), null); final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win2.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); mController.goodToGo(); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = @@ -225,11 +235,13 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); win.mActivityRecord.removeImmediately(); mController.goodToGo(); verifyNoMoreInteractionsExceptAsBinder(mMockRunner); - verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), + eq(adapter)); } @Test @@ -242,9 +254,10 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { new Rect(0, 0, 200, 200)); assertNotNull(record.mThumbnailAdapter); ((AnimationAdapter) record.mAdapter) - .startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + .startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, + mFinishedCallback); ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, - mMockTransaction, mThumbnailFinishedCallback); + mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); mController.goodToGo(); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = @@ -272,8 +285,10 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0); finishedCaptor.getValue().onAnimationFinished(); - verify(mFinishedCallback).onAnimationFinished(eq(record.mAdapter)); - verify(mThumbnailFinishedCallback).onAnimationFinished(eq(record.mThumbnailAdapter)); + verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION), + eq(record.mAdapter)); + verify(mThumbnailFinishedCallback).onAnimationFinished( + eq(ANIMATION_TYPE_WINDOW_ANIMATION), eq(record.mThumbnailAdapter)); } finally { mDisplayContent.mChangingApps.clear(); } @@ -290,7 +305,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { try { final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); mController.goodToGo(); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = @@ -318,7 +334,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { try { final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter; - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); mController.goodToGo(); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java index 2894241356f7..552c476613b2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java @@ -22,6 +22,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -39,6 +41,7 @@ import android.view.SurfaceSession; import androidx.test.filters.SmallTest; import com.android.server.wm.SurfaceAnimator.Animatable; +import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import org.junit.After; @@ -89,25 +92,30 @@ public class SurfaceAnimatorTest extends WindowTestsBase { @Test public void testRunAnimation() { - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_RECENTS); final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( OnAnimationFinishedCallback.class); assertAnimating(mAnimatable); verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash)); - verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_RECENTS), + callbackCaptor.capture()); - callbackCaptor.getValue().onAnimationFinished(mSpec); + callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_RECENTS, mSpec); assertNotAnimating(mAnimatable); assertTrue(mAnimatable.mFinishedCallbackCalled); + assertEquals(ANIMATION_TYPE_RECENTS, mAnimatable.mFinishedAnimationType); verify(mTransaction).remove(eq(mAnimatable.mLeash)); // TODO: Verify reparenting once we use mPendingTransaction to reparent it back } @Test public void testOverrideAnimation() { - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); final SurfaceControl firstLeash = mAnimatable.mLeash; - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2, true /* hidden */); + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); verify(mTransaction).remove(eq(firstLeash)); assertFalse(mAnimatable.mFinishedCallbackCalled); @@ -115,34 +123,40 @@ public class SurfaceAnimatorTest extends WindowTestsBase { final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( OnAnimationFinishedCallback.class); assertAnimating(mAnimatable); - verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION), + callbackCaptor.capture()); // First animation was finished, but this shouldn't cancel the second animation - callbackCaptor.getValue().onAnimationFinished(mSpec); + callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec); assertTrue(mAnimatable.mSurfaceAnimator.isAnimating()); // Second animation was finished - verify(mSpec2).startAnimation(any(), any(), callbackCaptor.capture()); - callbackCaptor.getValue().onAnimationFinished(mSpec2); + verify(mSpec2).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION), + callbackCaptor.capture()); + callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec2); assertNotAnimating(mAnimatable); assertTrue(mAnimatable.mFinishedCallbackCalled); + assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType); } @Test public void testCancelAnimation() { - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); assertAnimating(mAnimatable); mAnimatable.mSurfaceAnimator.cancelAnimation(); assertNotAnimating(mAnimatable); verify(mSpec).onAnimationCancelled(any()); assertTrue(mAnimatable.mFinishedCallbackCalled); + assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType); verify(mTransaction).remove(eq(mAnimatable.mLeash)); } @Test public void testCancelWithNullFinishCallbackAnimation() { SurfaceAnimator animator = new SurfaceAnimator(mAnimatable, null, mWm); - animator.startAnimation(mTransaction, mSpec, true /* hidden */); + animator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); assertTrue(animator.isAnimating()); assertNotNull(animator.getAnimation()); animator.cancelAnimation(); @@ -155,32 +169,37 @@ public class SurfaceAnimatorTest extends WindowTestsBase { @Test public void testDelayingAnimationStart() { mAnimatable.mSurfaceAnimator.startDelayingAnimationStart(); - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); verifyZeroInteractions(mSpec); assertAnimating(mAnimatable); assertTrue(mAnimatable.mSurfaceAnimator.isAnimationStartDelayed()); mAnimatable.mSurfaceAnimator.endDelayingAnimationStart(); - verify(mSpec).startAnimation(any(), any(), any()); + verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION), any()); } @Test public void testDelayingAnimationStartAndCancelled() { mAnimatable.mSurfaceAnimator.startDelayingAnimationStart(); - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); mAnimatable.mSurfaceAnimator.cancelAnimation(); verifyZeroInteractions(mSpec); assertNotAnimating(mAnimatable); assertTrue(mAnimatable.mFinishedCallbackCalled); + assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType); verify(mTransaction).remove(eq(mAnimatable.mLeash)); } @Test public void testTransferAnimation() { - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( OnAnimationFinishedCallback.class); - verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION), + callbackCaptor.capture()); final SurfaceControl leash = mAnimatable.mLeash; mAnimatable2.mSurfaceAnimator.transferAnimation(mAnimatable.mSurfaceAnimator); @@ -188,15 +207,17 @@ public class SurfaceAnimatorTest extends WindowTestsBase { assertAnimating(mAnimatable2); assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash); verify(mTransaction, never()).remove(eq(leash)); - callbackCaptor.getValue().onAnimationFinished(mSpec); + callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec); assertNotAnimating(mAnimatable2); assertTrue(mAnimatable2.mFinishedCallbackCalled); + assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType); verify(mTransaction).remove(eq(leash)); } @Test public void testOnAnimationLeashLostWhenAnimatableParentSurfaceControlNull() { - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); spyOn(mAnimatable); // Verify onAnimationLeashLost will be called even animatable's parent surface control lost. @@ -215,13 +236,14 @@ public class SurfaceAnimatorTest extends WindowTestsBase { final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec); // Finish the animation but then make sure we are deferring. - onFinishedCallback.onAnimationFinished(mSpec); + onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec); assertAnimating(mDeferFinishAnimatable); // Now end defer finishing. mDeferFinishAnimatable.mEndDeferFinishCallback.run(); assertNotAnimating(mAnimatable2); assertTrue(mDeferFinishAnimatable.mFinishedCallbackCalled); + assertEquals(ANIMATION_TYPE_APP_TRANSITION, mDeferFinishAnimatable.mFinishedAnimationType); verify(mTransaction).remove(eq(mDeferFinishAnimatable.mLeash)); } @@ -229,7 +251,7 @@ public class SurfaceAnimatorTest extends WindowTestsBase { public void testDeferFinishDoNotFinishNextAnimation() { // Start the first animation. final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec); - onFinishedCallback.onAnimationFinished(mSpec); + onFinishedCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec); // The callback is the resetAndInvokeFinish in {@link SurfaceAnimator#getFinishedCallback}. final Runnable firstDeferFinishCallback = mDeferFinishAnimatable.mEndDeferFinishCallback; @@ -247,11 +269,12 @@ public class SurfaceAnimatorTest extends WindowTestsBase { private OnAnimationFinishedCallback startDeferFinishAnimatable(AnimationAdapter anim) { mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, anim, - true /* hidden */); + true /* hidden */, ANIMATION_TYPE_APP_TRANSITION); final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( OnAnimationFinishedCallback.class); assertAnimating(mDeferFinishAnimatable); - verify(anim).startAnimation(any(), any(), callbackCaptor.capture()); + verify(anim).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION), + callbackCaptor.capture()); return callbackCaptor.getValue(); } @@ -274,6 +297,7 @@ public class SurfaceAnimatorTest extends WindowTestsBase { final SurfaceAnimator mSurfaceAnimator; SurfaceControl mLeash; boolean mFinishedCallbackCalled; + @AnimationType int mFinishedAnimationType; MyAnimatable(WindowManagerService wm, SurfaceSession session, Transaction transaction) { mSession = session; @@ -343,7 +367,11 @@ public class SurfaceAnimatorTest extends WindowTestsBase { return 1; } - private final Runnable mFinishedCallback = () -> mFinishedCallbackCalled = true; + private final SurfaceAnimator.OnAnimationFinishedCallback mFinishedCallback = ( + type, anim) -> { + mFinishedCallbackCalled = true; + mFinishedAnimationType = type; + }; } private static class DeferFinishAnimatable extends MyAnimatable { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index 9e80cf223271..078347e96a07 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,45 +16,50 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +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 com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; -import android.graphics.Point; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityManager.StackInfo; +import android.content.res.Configuration; import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; import android.os.Binder; import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.view.Display; import android.view.ITaskOrganizer; +import android.view.IWindowContainer; import android.view.SurfaceControl; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import android.view.WindowContainerTransaction; import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; + /** - * Test class for {@link TaskOrganizer}. + * Test class for {@link ITaskOrganizer} and {@link android.app.ITaskOrganizerController}. * * Build/Install/Run: * atest WmTests:TaskOrganizerTests @@ -67,7 +72,8 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); when(organizer.asBinder()).thenReturn(new Binder()); - mWm.mAtmService.registerTaskOrganizer(organizer, windowingMode); + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer( + organizer, windowingMode); return organizer; } @@ -83,7 +89,7 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer = registerMockOrganizer(); task.setTaskOrganizer(organizer); - verify(organizer).taskAppeared(any(), any()); + verify(organizer).taskAppeared(any()); task.removeImmediately(); verify(organizer).taskVanished(any()); @@ -97,10 +103,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED); task.setTaskOrganizer(organizer); - verify(organizer).taskAppeared(any(), any()); + verify(organizer).taskAppeared(any()); task.setTaskOrganizer(organizer2); verify(organizer).taskVanished(any()); - verify(organizer2).taskAppeared(any(), any()); + verify(organizer2).taskAppeared(any()); } @Test @@ -111,10 +117,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED); stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - verify(organizer).taskAppeared(any(), any()); + verify(organizer).taskAppeared(any()); stack.setWindowingMode(WINDOWING_MODE_PINNED); verify(organizer).taskVanished(any()); - verify(organizer2).taskAppeared(any(), any()); + verify(organizer2).taskAppeared(any()); } @Test @@ -124,7 +130,7 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer = registerMockOrganizer(); stack.setTaskOrganizer(organizer); - verify(organizer).taskAppeared(any(), any()); + verify(organizer).taskAppeared(any()); assertTrue(stack.isControlledByTaskOrganizer()); stack.setTaskOrganizer(null); @@ -140,9 +146,176 @@ public class TaskOrganizerTests extends WindowTestsBase { final Task task = createTaskInStack(stack, 0 /* userId */); final Task task2 = createTaskInStack(stack, 0 /* userId */); stack.setWindowingMode(WINDOWING_MODE_PINNED); - verify(organizer, times(1)).taskAppeared(any(), any()); + verify(organizer, times(1)).taskAppeared(any()); stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); verify(organizer, times(1)).taskVanished(any()); } + + @Test + public void testTaskTransaction() { + removeGlobalMinSizeRestriction(); + final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + final Task task = stack.getTopMostTask(); + WindowContainerTransaction t = new WindowContainerTransaction(); + Rect newBounds = new Rect(10, 10, 100, 100); + t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100)); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + assertEquals(newBounds, task.getBounds()); + } + + @Test + public void testStackTransaction() { + removeGlobalMinSizeRestriction(); + final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + StackInfo info = + mWm.mAtmService.getStackInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); + WindowContainerTransaction t = new WindowContainerTransaction(); + assertEquals(stack.mRemoteToken, info.stackToken); + Rect newBounds = new Rect(10, 10, 100, 100); + t.setBounds(info.stackToken, new Rect(10, 10, 100, 100)); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + assertEquals(newBounds, stack.getBounds()); + } + + @Test + public void testContainerChanges() { + removeGlobalMinSizeRestriction(); + final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + final Task task = stack.getTopMostTask(); + WindowContainerTransaction t = new WindowContainerTransaction(); + assertTrue(task.isFocusable()); + t.setFocusable(stack.mRemoteToken, false); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + assertFalse(task.isFocusable()); + } + + @Test + public void testCreateDeleteRootTasks() { + RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + Display.DEFAULT_DISPLAY, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, + info1.configuration.windowConfiguration.getWindowingMode()); + assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); + + RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + Display.DEFAULT_DISPLAY, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, + info2.configuration.windowConfiguration.getWindowingMode()); + assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType); + + DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); + List<TaskTile> infos = getTaskTiles(dc); + assertEquals(2, infos.size()); + + assertTrue(mWm.mAtmService.mTaskOrganizerController.deleteRootTask(info1.token)); + infos = getTaskTiles(dc); + assertEquals(1, infos.size()); + assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, infos.get(0).getWindowingMode()); + } + + @Test + public void testTileAddRemoveChild() { + RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + + final ActivityStack stack = createTaskStackOnDisplay( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); + assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); + TaskTile tile1 = TaskTile.forToken(info1.token.asBinder()); + tile1.addChild(stack, 0 /* index */); + assertEquals(info1.configuration.windowConfiguration.getWindowingMode(), + stack.getWindowingMode()); + + // Info should reflect new membership + List<TaskTile> tiles = getTaskTiles(mDisplayContent); + info1 = new RunningTaskInfo(); + tiles.get(0).fillTaskInfo(info1); + assertEquals(ACTIVITY_TYPE_STANDARD, info1.topActivityType); + + // Children inherit configuration + Rect newSize = new Rect(10, 10, 300, 300); + Configuration c = new Configuration(tile1.getRequestedOverrideConfiguration()); + c.windowConfiguration.setBounds(newSize); + tile1.onRequestedOverrideConfigurationChanged(c); + assertEquals(newSize, stack.getBounds()); + + tile1.removeChild(stack); + assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); + info1 = new RunningTaskInfo(); + tiles = getTaskTiles(mDisplayContent); + tiles.get(0).fillTaskInfo(info1); + assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); + } + + @Test + public void testTaskInfoCallback() { + final ArrayList<RunningTaskInfo> lastReportedTiles = new ArrayList<>(); + final boolean[] called = {false}; + ITaskOrganizer listener = new ITaskOrganizer.Stub() { + @Override + public void taskAppeared(RunningTaskInfo taskInfo) { } + + @Override + public void taskVanished(IWindowContainer container) { } + + @Override + public void transactionReady(int id, SurfaceControl.Transaction t) { } + + @Override + public void onTaskInfoChanged(RunningTaskInfo info) throws RemoteException { + lastReportedTiles.add(info); + called[0] = true; + } + }; + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + lastReportedTiles.clear(); + called[0] = false; + + final ActivityStack stack = createTaskStackOnDisplay( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); + TaskTile tile1 = TaskTile.forToken(info1.token.asBinder()); + tile1.addChild(stack, 0 /* index */); + assertTrue(called[0]); + assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); + + lastReportedTiles.clear(); + called[0] = false; + final ActivityStack stack2 = createTaskStackOnDisplay( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); + tile1.addChild(stack2, 0 /* index */); + assertTrue(called[0]); + assertEquals(ACTIVITY_TYPE_HOME, lastReportedTiles.get(0).topActivityType); + + lastReportedTiles.clear(); + called[0] = false; + mDisplayContent.positionStackAtTop(stack, false /* includingParents */); + assertTrue(called[0]); + assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); + + lastReportedTiles.clear(); + called[0] = false; + tile1.removeAllChildren(); + assertTrue(called[0]); + assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType); + } + + private List<TaskTile> getTaskTiles(DisplayContent dc) { + ArrayList<TaskTile> out = new ArrayList<>(); + for (int i = dc.getStackCount() - 1; i >= 0; --i) { + final Task t = dc.getStackAt(i); + if (t instanceof TaskTile) { + out.add((TaskTile) t); + } + } + return out; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 05d048d360ea..4a87701e3c4b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -34,6 +34,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; @@ -61,6 +62,8 @@ import android.view.SurfaceSession; import androidx.test.filters.SmallTest; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -829,7 +832,8 @@ public class WindowContainerTests extends WindowTestsBase { wc.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(adapter); spyOn(wc); doReturn(true).when(wc).okToAnimate(); - final Runnable onAnimationFinishedCallback = mock(Runnable.class); + final OnAnimationFinishedCallback onAnimationFinishedCallback = + mock(OnAnimationFinishedCallback.class); // Make sure animating state is as expected after applied animation. assertTrue(wc.applyAnimation(null, TRANSIT_TASK_OPEN, true, false, @@ -837,16 +841,18 @@ public class WindowContainerTests extends WindowTestsBase { assertEquals(wc.getTopMostActivity(), act); assertTrue(wc.isAnimating()); assertTrue(act.isAnimating(PARENTS)); - verify(onAnimationFinishedCallback, times(0)).run(); + verify(onAnimationFinishedCallback, times(0)).onAnimationFinished( + eq(ANIMATION_TYPE_APP_TRANSITION), any()); // Make sure animation finish callback will be received and reset animating state after // animation finish. wc.getDisplayContent().mAppTransition.goodToGo(TRANSIT_TASK_OPEN, act, mDisplayContent.mOpeningApps); - verify(wc).onAnimationFinished(); + verify(wc).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), any()); assertFalse(wc.isAnimating()); assertFalse(act.isAnimating(PARENTS)); - verify(onAnimationFinishedCallback, times(1)).run(); + verify(onAnimationFinishedCallback, times(1)).onAnimationFinished( + eq(ANIMATION_TYPE_APP_TRANSITION), any()); } /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 8e362ae4c59a..1ca2e318b0d7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -40,6 +40,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.mockito.Mockito.mock; import android.content.Context; +import android.content.Intent; import android.util.Log; import android.view.Display; import android.view.DisplayInfo; @@ -340,6 +341,7 @@ class WindowTestsBase extends SystemServiceTestsBase { .setWindowingMode(windowingMode) .setActivityType(activityType) .setCreateActivity(false) + .setIntent(new Intent()) .build(); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/testing/Assert.java b/services/tests/wmtests/src/com/android/server/wm/testing/Assert.java new file mode 100644 index 000000000000..1e98277f0f96 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/testing/Assert.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.testing; + +/** + * Assertions for WM tests. + */ +public class Assert { + + /** + * Runs {@code r} and asserts that an exception of type {@code expectedThrowable} is thrown. + * @param expectedThrowable the type of throwable that is expected to be thrown + * @param r the {@link Runnable} which is run and expected to throw. + * @throws AssertionError if {@code r} does not throw, or throws a runnable that is not an + * instance of {@code expectedThrowable}. + */ + // TODO: remove once Android migrates to JUnit 4.13, which provides assertThrows + public static void assertThrows(Class<? extends Throwable> expectedThrowable, Runnable r) { + try { + r.run(); + } catch (Throwable t) { + if (expectedThrowable.isInstance(t)) { + return; + } else if (t instanceof Exception) { + throw new AssertionError("Expected " + expectedThrowable + + ", but got " + t.getClass(), t); + } else { + // Re-throw Errors and other non-Exception throwables. + throw t; + } + } + throw new AssertionError("Expected " + expectedThrowable + ", but nothing was thrown"); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/testing/AssertTest.java b/services/tests/wmtests/src/com/android/server/wm/testing/AssertTest.java new file mode 100644 index 000000000000..df1276123c5c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/testing/AssertTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.testing; + +import static com.android.server.wm.testing.Assert.assertThrows; + +import static org.junit.Assert.assertTrue; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class AssertTest { + + @Rule + public ExpectedException mExpectedException = ExpectedException.none(); + + @Test + public void assertThrows_runsRunnable() { + boolean[] ran = new boolean[] { false }; + assertThrows(TestException.class, () -> { + ran[0] = true; + throw new TestException(); + }); + assertTrue(ran[0]); + } + + @Test + public void assertThrows_failsIfNothingThrown() { + mExpectedException.expect(AssertionError.class); + assertThrows(TestException.class, () -> { + }); + } + + @Test + public void assertThrows_failsIfWrongExceptionThrown() { + mExpectedException.expect(AssertionError.class); + assertThrows(TestException.class, () -> { + throw new RuntimeException(); + }); + } + + @Test + public void assertThrows_succeedsIfGivenExceptionThrown() { + assertThrows(TestException.class, () -> { + throw new TestException(); + }); + } + + @Test + public void assertThrows_succeedsIfSubExceptionThrown() { + assertThrows(RuntimeException.class, () -> { + throw new TestException(); + }); + } + + @Test + public void assertThrows_rethrowsUnexpectedErrors() { + mExpectedException.expect(TestError.class); + assertThrows(TestException.class, () -> { + throw new TestError(); + }); + } + + static class TestException extends RuntimeException { + } + + static class TestError extends Error { + } + +} diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java index 5419c3c3d5c4..9baa66fcd87e 100644 --- a/telephony/java/android/telephony/BarringInfo.java +++ b/telephony/java/android/telephony/BarringInfo.java @@ -238,6 +238,12 @@ public final class BarringInfo implements Parcelable { } } + private static final BarringServiceInfo BARRING_SERVICE_INFO_UNKNOWN = + new BarringServiceInfo(BarringServiceInfo.BARRING_TYPE_UNKNOWN); + + private static final BarringServiceInfo BARRING_SERVICE_INFO_UNBARRED = + new BarringServiceInfo(BarringServiceInfo.BARRING_TYPE_NONE); + private CellIdentity mCellIdentity; // A SparseArray potentially mapping each BarringService type to a BarringServiceInfo config @@ -298,17 +304,6 @@ public final class BarringInfo implements Parcelable { } /** - * Return whether a service is currently barred based on the BarringInfo - * - * @param service the service to be checked. - * @return true if the service is currently being barred, otherwise false - */ - public boolean isServiceBarred(@BarringServiceType int service) { - BarringServiceInfo bsi = mBarringServiceInfos.get(service); - return bsi != null && (bsi.isBarred()); - } - - /** * Get the BarringServiceInfo for a specified service. * * @return a BarringServiceInfo struct describing the current barring status for a service @@ -319,9 +314,8 @@ public final class BarringInfo implements Parcelable { // type as UNKNOWN; if the modem reports barring info but doesn't report for a particular // service then we can safely assume that the service isn't barred (for instance because // that particular service isn't applicable to the current RAN). - return (bsi != null) ? bsi : new BarringServiceInfo( - mBarringServiceInfos.size() > 0 ? BarringServiceInfo.BARRING_TYPE_NONE : - BarringServiceInfo.BARRING_TYPE_UNKNOWN); + return (bsi != null) ? bsi : mBarringServiceInfos.size() > 0 + ? BARRING_SERVICE_INFO_UNBARRED : BARRING_SERVICE_INFO_UNKNOWN; } /** @hide */ diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 5b09cd9e6caa..a5a1ebc10139 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -1925,6 +1925,8 @@ public class ServiceState implements Parcelable { /** * Get the network registration state for the transport type and network domain. + * If multiple domains are in the input bitmask, only the first one from + * networkRegistrationInfo.getDomain() will be returned. * * @param domain The network {@link NetworkRegistrationInfo.Domain domain} * @param transportType The transport type @@ -2072,11 +2074,18 @@ public class ServiceState implements Parcelable { public boolean isIwlanPreferred() { return mIsIwlanPreferred; } - /** - * @return {@code true}Returns True whenever the modem is searching for service. - * To check both CS and PS domain - */ + /** + * This indicates whether the device is searching for service. + * + * This API reports the modem searching status for + * {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN} (cellular) service in either + * {@link NetworkRegistrationInfo#DOMAIN_CS} or {@link NetworkRegistrationInfo#DOMAIN_PS}. + * This API will not report searching status for + * {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN}. + * + * @return {@code true} whenever the modem is searching for service. + */ public boolean isSearching() { NetworkRegistrationInfo psRegState = getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java index bc8d3c349f9d..3fb0050d0c55 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.os.Bundle; +import android.os.RemoteException; import android.view.ITaskOrganizer; import android.view.IWindowContainer; import android.view.SurfaceControl; @@ -47,13 +48,16 @@ public class TaskOrganizerMultiWindowTest extends Activity { class Organizer extends ITaskOrganizer.Stub { @Override - public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) { - mView.reparentTask(wc); + public void taskAppeared(ActivityManager.RunningTaskInfo ti) { + mView.reparentTask(ti.token); } public void taskVanished(IWindowContainer wc) { } public void transactionReady(int id, SurfaceControl.Transaction t) { } + @Override + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { + } } Organizer mOrganizer = new Organizer(); diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java index fc1be28d2b8f..8f3cb3442f5a 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -21,18 +21,14 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.Service; -import android.app.WindowConfiguration; -import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.os.IBinder; import android.view.ITaskOrganizer; import android.view.IWindowContainer; -import android.view.WindowContainerTransaction; import android.view.SurfaceControl; -import android.view.SurfaceHolder; -import android.view.SurfaceView; import android.view.ViewGroup; +import android.view.WindowContainerTransaction; import android.view.WindowManager; import android.widget.FrameLayout; @@ -43,13 +39,13 @@ public class TaskOrganizerPipTest extends Service { TaskView mTaskView; class Organizer extends ITaskOrganizer.Stub { - public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) { - mTaskView.reparentTask(wc); + public void taskAppeared(ActivityManager.RunningTaskInfo ti) { + mTaskView.reparentTask(ti.token); final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.scheduleFinishEnterPip(wc, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); + wct.scheduleFinishEnterPip(ti.token, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); try { - ActivityTaskManager.getService().applyContainerTransaction(wct); + ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct); } catch (Exception e) { } } @@ -57,6 +53,8 @@ public class TaskOrganizerPipTest extends Service { } public void transactionReady(int id, SurfaceControl.Transaction t) { } + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { + } } Organizer mOrganizer = new Organizer(); diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java index ff73340fc947..9f32bb8a0bf7 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java @@ -44,7 +44,7 @@ class TaskView extends SurfaceView implements SurfaceHolder.Callback { @Override public void surfaceCreated(SurfaceHolder holder) { try { - ActivityTaskManager.getService().registerTaskOrganizer(mTaskOrganizer, + ActivityTaskManager.getTaskOrganizerController().registerTaskOrganizer(mTaskOrganizer, mWindowingMode); } catch (Exception e) { } diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java index 7ab4b56fae80..2d5df4f47e00 100644 --- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java +++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -27,12 +27,18 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.content.Context; import android.os.PersistableBundle; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,15 +58,27 @@ public class ConnectivityDiagnosticsManagerTest { private static final Executor INLINE_EXECUTOR = x -> x.run(); + @Mock private Context mContext; + @Mock private IConnectivityManager mService; @Mock private ConnectivityDiagnosticsCallback mCb; private ConnectivityDiagnosticsBinder mBinder; + private ConnectivityDiagnosticsManager mManager; @Before public void setUp() { + mContext = mock(Context.class); + mService = mock(IConnectivityManager.class); mCb = mock(ConnectivityDiagnosticsCallback.class); mBinder = new ConnectivityDiagnosticsBinder(mCb, INLINE_EXECUTOR); + mManager = new ConnectivityDiagnosticsManager(mContext, mService); + } + + @After + public void tearDown() { + // clear ConnectivityDiagnosticsManager callbacks map + ConnectivityDiagnosticsManager.sCallbacks.clear(); } private ConnectivityReport createSampleConnectivityReport() { @@ -245,4 +263,53 @@ public class ConnectivityDiagnosticsManagerTest { // latch without waiting. verify(mCb).onNetworkConnectivityReported(eq(n), eq(connectivity)); } + + @Test + public void testRegisterConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + + verify(mService).registerConnectivityDiagnosticsCallback( + any(ConnectivityDiagnosticsBinder.class), eq(request)); + assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); + } + + @Test + public void testRegisterDuplicateConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + + try { + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + fail("Duplicate callback registration should fail"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testUnregisterConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + + mManager.unregisterConnectivityDiagnosticsCallback(mCb); + + verify(mService).unregisterConnectivityDiagnosticsCallback( + any(ConnectivityDiagnosticsBinder.class)); + assertFalse(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); + + // verify that re-registering is successful + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + verify(mService, times(2)).registerConnectivityDiagnosticsCallback( + any(ConnectivityDiagnosticsBinder.class), eq(request)); + assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); + } + + @Test + public void testUnregisterUnknownConnectivityDiagnosticsCallback() throws Exception { + mManager.unregisterConnectivityDiagnosticsCallback(mCb); + + verifyNoMoreInteractions(mService); + } } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index a0a1352a6330..5592cd7c2f9f 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -139,6 +139,7 @@ import android.net.ConnectivityManager.PacketKeepalive; import android.net.ConnectivityManager.PacketKeepaliveCallback; import android.net.ConnectivityManager.TooManyRequestsException; import android.net.ConnectivityThread; +import android.net.IConnectivityDiagnosticsCallback; import android.net.IDnsResolver; import android.net.IIpConnectivityMetrics; import android.net.INetd; @@ -180,6 +181,7 @@ import android.os.Bundle; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Parcel; @@ -210,6 +212,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.WakeupMessage; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo; import com.android.server.connectivity.ConnectivityConstants; import com.android.server.connectivity.DefaultNetworkMetrics; import com.android.server.connectivity.IpConnectivityMetrics; @@ -322,6 +325,8 @@ public class ConnectivityServiceTest { @Mock UserManager mUserManager; @Mock NotificationManager mNotificationManager; @Mock AlarmManager mAlarmManager; + @Mock IConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback; + @Mock IBinder mIBinder; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); @@ -6355,4 +6360,70 @@ public class ConnectivityServiceTest { UserHandle.getAppId(uid)); return packageInfo; } + + @Test + public void testRegisterConnectivityDiagnosticsCallbackInvalidRequest() throws Exception { + final NetworkRequest request = + new NetworkRequest( + new NetworkCapabilities(), TYPE_ETHERNET, 0, NetworkRequest.Type.NONE); + try { + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, request); + fail("registerConnectivityDiagnosticsCallback should throw on invalid NetworkRequest"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testRegisterUnregisterConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest wifiRequest = + new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(); + + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, wifiRequest); + + verify(mIBinder, timeout(TIMEOUT_MS)) + .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + assertTrue( + mService.mConnectivityDiagnosticsCallbacks.containsKey( + mConnectivityDiagnosticsCallback)); + + mService.unregisterConnectivityDiagnosticsCallback(mConnectivityDiagnosticsCallback); + verify(mIBinder, timeout(TIMEOUT_MS)) + .unlinkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + assertFalse( + mService.mConnectivityDiagnosticsCallbacks.containsKey( + mConnectivityDiagnosticsCallback)); + verify(mConnectivityDiagnosticsCallback, atLeastOnce()).asBinder(); + } + + @Test + public void testRegisterDuplicateConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest wifiRequest = + new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, wifiRequest); + + verify(mIBinder, timeout(TIMEOUT_MS)) + .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + verify(mConnectivityDiagnosticsCallback).asBinder(); + assertTrue( + mService.mConnectivityDiagnosticsCallbacks.containsKey( + mConnectivityDiagnosticsCallback)); + + // Register the same callback again + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, wifiRequest); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + assertTrue( + mService.mConnectivityDiagnosticsCallbacks.containsKey( + mConnectivityDiagnosticsCallback)); + } } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 0ef224a5f8ee..8d95cb010856 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -373,6 +373,7 @@ public class WifiConfiguration implements Parcelable { * ECDHE_ECDSA * ECDHE_RSA * </pre> + * @hide */ public static class SuiteBCipher { private SuiteBCipher() { } @@ -715,8 +716,8 @@ public class WifiConfiguration implements Parcelable { public BitSet allowedGroupManagementCiphers; /** * The set of SuiteB ciphers supported by this configuration. - * To be used for WPA3-Enterprise mode. - * See {@link SuiteBCipher} for descriptions of the values. + * To be used for WPA3-Enterprise mode. Set automatically by the framework based on the + * certificate type that is used in this configuration. */ @NonNull public BitSet allowedSuiteBCiphers; @@ -1929,54 +1930,38 @@ public class WifiConfiguration implements Parcelable { private NetworkSelectionStatus mNetworkSelectionStatus = new NetworkSelectionStatus(); /** - * @hide * This class is intended to store extra failure reason information for the most recent * connection attempt, so that it may be surfaced to the settings UI + * @hide */ - @SystemApi + // TODO(b/148626966): called by SUW via reflection, remove once SUW is updated public static class RecentFailure { private RecentFailure() {} - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {NONE, STATUS_AP_UNABLE_TO_HANDLE_NEW_STA}) - public @interface AssociationStatus {} - - /** - * No recent failure, or no specific reason given for the recent connection failure - */ - public static final int NONE = 0; - /** - * Connection to this network recently failed due to Association Rejection Status 17 - * (AP is full) - */ - public static final int STATUS_AP_UNABLE_TO_HANDLE_NEW_STA = 17; /** * Association Rejection Status code (NONE for success/non-association-rejection-fail) */ - @AssociationStatus - private int mAssociationStatus = NONE; + @RecentFailureReason + private int mAssociationStatus = RECENT_FAILURE_NONE; /** * @param status the association status code for the recent failure - * @hide */ - public void setAssociationStatus(@AssociationStatus int status) { + public void setAssociationStatus(@RecentFailureReason int status) { mAssociationStatus = status; } /** * Sets the RecentFailure to NONE - * @hide */ public void clear() { - mAssociationStatus = NONE; + mAssociationStatus = RECENT_FAILURE_NONE; } /** - * Get the recent failure code. One of {@link #NONE} or - * {@link #STATUS_AP_UNABLE_TO_HANDLE_NEW_STA}. + * Get the recent failure code. One of {@link #RECENT_FAILURE_NONE} or + * {@link #RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA}. */ - @AssociationStatus + @RecentFailureReason public int getAssociationStatus() { return mAssociationStatus; } @@ -1986,10 +1971,47 @@ public class WifiConfiguration implements Parcelable { * RecentFailure member * @hide */ + // TODO(b/148626966): called by SUW via reflection, once SUW is updated, make private and + // rename to mRecentFailure @NonNull - @SystemApi public final RecentFailure recentFailure = new RecentFailure(); + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "RECENT_FAILURE_", value = { + RECENT_FAILURE_NONE, + RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA}) + public @interface RecentFailureReason {} + + /** + * No recent failure, or no specific reason given for the recent connection failure + * @hide + */ + @SystemApi + public static final int RECENT_FAILURE_NONE = 0; + /** + * Connection to this network recently failed due to Association Rejection Status 17 + * (AP is full) + * @hide + */ + @SystemApi + public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; + + /** + * Get the failure reason for the most recent connection attempt, or + * {@link #RECENT_FAILURE_NONE} if there was no failure. + * + * Failure reasons include: + * {@link #RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA} + * + * @hide + */ + @RecentFailureReason + @SystemApi + public int getRecentFailureReason() { + return recentFailure.getAssociationStatus(); + } + /** * Get the network selection status. * @hide diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 6f01350d8af4..8250a95c97a9 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -836,6 +836,8 @@ public class WifiScanner { * Enable/Disable wifi scanning. * * @param enable set to true to enable scanning, set to false to disable all types of scanning. + * + * @see WifiManager#ACTION_WIFI_SCAN_AVAILABLE * {@hide} */ @SystemApi diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java index 3a0d080594c8..756d679b8f21 100644 --- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java +++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java @@ -19,6 +19,7 @@ package android.net.wifi.hotspot2; import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE; import static android.net.wifi.WifiConfiguration.MeteredOverride; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.wifi.hotspot2.pps.Credential; @@ -895,4 +896,18 @@ public final class PasspointConfiguration implements Parcelable { public boolean isOsuProvisioned() { return getUpdateIdentifier() != Integer.MIN_VALUE; } + + /** + * Get a unique identifier for a PasspointConfiguration object. + * + * @return A unique identifier + * @throws IllegalStateException if Credential or HomeSP nodes are not initialized + */ + public @NonNull String getUniqueId() throws IllegalStateException { + if (mCredential == null || mHomeSp == null || TextUtils.isEmpty(mHomeSp.getFqdn())) { + throw new IllegalStateException("Credential or HomeSP are not initialized"); + } + + return mHomeSp.getFqdn(); + } } diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java index 9562f95ac162..36c7213b3799 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java @@ -137,12 +137,12 @@ public class WifiP2pConfig implements Parcelable { /** @hide */ @UnsupportedAppUsage - public int netId = WifiP2pGroup.PERSISTENT_NET_ID; + public int netId = WifiP2pGroup.NETWORK_ID_PERSISTENT; /** * Get the network ID of this P2P configuration. - * @return either a non-negative network ID, or one of {@link WifiP2pGroup#PERSISTENT_NET_ID} or - * {@link WifiP2pGroup#TEMPORARY_NET_ID}. + * @return either a non-negative network ID, or one of + * {@link WifiP2pGroup#NETWORK_ID_PERSISTENT} or {@link WifiP2pGroup#NETWORK_ID_TEMPORARY}. */ public int getNetworkId() { return netId; @@ -280,7 +280,7 @@ public class WifiP2pConfig implements Parcelable { private String mPassphrase = ""; private int mGroupOperatingBand = GROUP_OWNER_BAND_AUTO; private int mGroupOperatingFrequency = GROUP_OWNER_BAND_AUTO; - private int mNetId = WifiP2pGroup.TEMPORARY_NET_ID; + private int mNetId = WifiP2pGroup.NETWORK_ID_TEMPORARY; /** * Specify the peer's MAC address. If not set, the device will @@ -460,9 +460,9 @@ public class WifiP2pConfig implements Parcelable { */ public @NonNull Builder enablePersistentMode(boolean persistent) { if (persistent) { - mNetId = WifiP2pGroup.PERSISTENT_NET_ID; + mNetId = WifiP2pGroup.NETWORK_ID_PERSISTENT; } else { - mNetId = WifiP2pGroup.TEMPORARY_NET_ID; + mNetId = WifiP2pGroup.NETWORK_ID_TEMPORARY; } return this; } diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java index 21f6704be0bb..e497b22d7769 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java @@ -41,7 +41,15 @@ public class WifiP2pGroup implements Parcelable { * The temporary network id. * @see #getNetworkId() */ - public static final int TEMPORARY_NET_ID = -1; + public static final int NETWORK_ID_TEMPORARY = -1; + + /** + * The temporary network id. + * + * @hide + */ + @UnsupportedAppUsage + public static final int TEMPORARY_NET_ID = NETWORK_ID_TEMPORARY; /** * The persistent network id. @@ -49,7 +57,7 @@ public class WifiP2pGroup implements Parcelable { * Otherwise, create a new persistent profile. * @see #getNetworkId() */ - public static final int PERSISTENT_NET_ID = -2; + public static final int NETWORK_ID_PERSISTENT = -2; /** The network name */ private String mNetworkName; @@ -130,13 +138,13 @@ public class WifiP2pGroup implements Parcelable { mPassphrase = match.group(4); mOwner = new WifiP2pDevice(match.group(5)); if (match.group(6) != null) { - mNetId = PERSISTENT_NET_ID; + mNetId = NETWORK_ID_PERSISTENT; } else { - mNetId = TEMPORARY_NET_ID; + mNetId = NETWORK_ID_TEMPORARY; } } else if (tokens[0].equals("P2P-INVITATION-RECEIVED")) { String sa = null; - mNetId = PERSISTENT_NET_ID; + mNetId = NETWORK_ID_PERSISTENT; for (String token : tokens) { String[] nameValue = token.split("="); if (nameValue.length != 2) continue; diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java index 3459c9496595..0fe06756a969 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java @@ -1293,7 +1293,7 @@ public class WifiP2pManager { @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(Channel c, ActionListener listener) { checkChannel(c); - c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.PERSISTENT_NET_ID, + c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.NETWORK_ID_PERSISTENT, c.putListener(listener)); } diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java index 654154d77b0d..e78c5bf992f3 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java @@ -18,6 +18,7 @@ package android.net.wifi.hotspot2; import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -364,4 +365,54 @@ public class PasspointConfigurationTest { assertTrue(config.validateForR2()); assertTrue(config.isOsuProvisioned()); } + + /** + * Verify that the unique identifier generated is correct. + * + * @throws Exception + */ + @Test + public void validateUniqueId() throws Exception { + PasspointConfiguration config = PasspointTestUtils.createConfig(); + String uniqueId; + uniqueId = config.getUniqueId(); + assertEquals(uniqueId, config.getHomeSp().getFqdn()); + } + + /** + * Verify that the unique identifier API generates an exception if HomeSP is not initialized. + * + * @throws Exception + */ + @Test + public void validateUniqueIdExceptionWithEmptyHomeSp() throws Exception { + PasspointConfiguration config = PasspointTestUtils.createConfig(); + config.setHomeSp(null); + boolean exceptionCaught = false; + try { + String uniqueId = config.getUniqueId(); + } catch (IllegalStateException e) { + exceptionCaught = true; + } + assertTrue(exceptionCaught); + } + + /** + * Verify that the unique identifier API generates an exception if Credential is not + * initialized. + * + * @throws Exception + */ + @Test + public void validateUniqueIdExceptionWithEmptyCredential() throws Exception { + PasspointConfiguration config = PasspointTestUtils.createConfig(); + config.setCredential(null); + boolean exceptionCaught = false; + try { + String uniqueId = config.getUniqueId(); + } catch (IllegalStateException e) { + exceptionCaught = true; + } + assertTrue(exceptionCaught); + } } |