diff options
52 files changed, 758 insertions, 455 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index a1fd0dbd8df0..7e25382f06d9 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6061,7 +6061,7 @@ package android.net { method public void release(); } - public class InvalidPacketException extends java.lang.Exception { + public final class InvalidPacketException extends java.lang.Exception { ctor public InvalidPacketException(int); method public int getError(); field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb @@ -6255,7 +6255,7 @@ package android.net { field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 } - public static class NetworkCapabilities.Builder { + public static final class NetworkCapabilities.Builder { ctor public NetworkCapabilities.Builder(); ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities); method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int); diff --git a/api/test-current.txt b/api/test-current.txt index 0bd8a195edff..ba6feedc65ba 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -535,6 +535,7 @@ package android.app { method public void setWindowingMode(int); method public void writeToParcel(android.os.Parcel, int); field public static final int ACTIVITY_TYPE_ASSISTANT = 4; // 0x4 + field public static final int ACTIVITY_TYPE_DREAM = 5; // 0x5 field public static final int ACTIVITY_TYPE_HOME = 2; // 0x2 field public static final int ACTIVITY_TYPE_RECENTS = 3; // 0x3 field public static final int ACTIVITY_TYPE_STANDARD = 1; // 0x1 @@ -1833,7 +1834,7 @@ package android.net { field public static final int TRANSPORT_TEST = 7; // 0x7 } - public static class NetworkCapabilities.Builder { + public static final class NetworkCapabilities.Builder { ctor public NetworkCapabilities.Builder(); ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities); method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bd3fee2d1fc7..21b56d3e337f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4974,10 +4974,8 @@ public final class ActivityThread extends ClientTransactionHandler { ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); - Class<? extends Activity> activityClass = null; if (localLOGV) Slog.v(TAG, "Performing finish of " + r); if (r != null) { - activityClass = r.activity.getClass(); r.activity.mConfigChangeFlags |= configChanges; if (finishing) { r.activity.mFinished = true; @@ -5030,7 +5028,6 @@ public final class ActivityThread extends ClientTransactionHandler { synchronized (mResourcesManager) { mActivities.remove(token); } - StrictMode.decrementExpectedActivityCount(activityClass); return r; } @@ -5050,6 +5047,7 @@ public final class ActivityThread extends ClientTransactionHandler { ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance, reason); if (r != null) { + Class<? extends Activity> activityClass = r.activity.getClass(); cleanUpPendingRemoveWindows(r, finishing); WindowManager wm = r.activity.getWindowManager(); View v = r.activity.mDecor; @@ -5074,14 +5072,14 @@ public final class ActivityThread extends ClientTransactionHandler { } if (wtoken != null && r.mPendingRemoveWindow == null) { WindowManagerGlobal.getInstance().closeAll(wtoken, - r.activity.getClass().getName(), "Activity"); + activityClass.getName(), "Activity"); } else if (r.mPendingRemoveWindow != null) { // We're preserving only one window, others should be closed so app views // will be detached before the final tear down. It should be done now because // some components (e.g. WebView) rely on detach callbacks to perform receiver // unregister and other cleanup. WindowManagerGlobal.getInstance().closeAllExceptView(token, v, - r.activity.getClass().getName(), "Activity"); + activityClass.getName(), "Activity"); } r.activity.mDecor = null; } @@ -5093,18 +5091,23 @@ public final class ActivityThread extends ClientTransactionHandler { // about leaking windows, because that is a bug, so if they are // using this recreate facility then they get to live with leaks. WindowManagerGlobal.getInstance().closeAll(token, - r.activity.getClass().getName(), "Activity"); + activityClass.getName(), "Activity"); } // Mocked out contexts won't be participating in the normal // process lifecycle, but if we're running with a proper // ApplicationContext we need to have it tear down things // cleanly. - Context c = r.activity.getBaseContext(); - if (c instanceof ContextImpl) { - ((ContextImpl) c).scheduleFinalCleanup( - r.activity.getClass().getName(), "Activity"); + final ContextImpl impl = ContextImpl.getImpl(r.activity); + if (impl != null) { + impl.scheduleFinalCleanup(activityClass.getName(), "Activity"); } + + r.activity = null; + r.window = null; + r.hideForNow = false; + r.nextIdle = null; + StrictMode.decrementExpectedActivityCount(activityClass); } if (finishing) { try { @@ -5334,10 +5337,6 @@ public final class ActivityThread extends ClientTransactionHandler { handleDestroyActivity(r.token, false, configChanges, true, reason); - r.activity = null; - r.window = null; - r.hideForNow = false; - r.nextIdle = null; // Merge any pending results and pending intents; don't just replace them if (pendingResults != null) { if (r.pendingResults == null) { diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index f590eb914aac..78d3581a7012 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -48,8 +48,6 @@ interface INotificationManager void clearData(String pkg, int uid, boolean fromApp); void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @nullable ITransientNotificationCallback callback); void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId); - // TODO(b/144152069): Remove this after assessing impact on dogfood. - void enqueueTextOrCustomToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId, boolean isCustom); void cancelToast(String pkg, IBinder token); void finishToken(String pkg, IBinder token); diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 6b40890e17c7..37e07de9809a 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -141,6 +141,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu public static final int ACTIVITY_TYPE_RECENTS = 3; /** Assistant activity type. */ public static final int ACTIVITY_TYPE_ASSISTANT = 4; + /** Dream activity type. */ + public static final int ACTIVITY_TYPE_DREAM = 5; /** @hide */ @IntDef(prefix = { "ACTIVITY_TYPE_" }, value = { @@ -149,6 +151,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS, ACTIVITY_TYPE_ASSISTANT, + ACTIVITY_TYPE_DREAM, }) public @interface ActivityType {} @@ -746,9 +749,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu * @hide */ public boolean isAlwaysOnTop() { - return mWindowingMode == WINDOWING_MODE_PINNED || (mAlwaysOnTop == ALWAYS_ON_TOP_ON - && (mWindowingMode == WINDOWING_MODE_FREEFORM - || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW)); + if (mWindowingMode == WINDOWING_MODE_PINNED) return true; + if (mActivityType == ACTIVITY_TYPE_DREAM) return true; + if (mAlwaysOnTop != ALWAYS_ON_TOP_ON) return false; + return mWindowingMode == WINDOWING_MODE_FREEFORM + || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW; } /** @@ -798,7 +803,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu /** @hide */ public static boolean supportSplitScreenWindowingMode(int activityType) { - return activityType != ACTIVITY_TYPE_ASSISTANT; + return activityType != ACTIVITY_TYPE_ASSISTANT && activityType != ACTIVITY_TYPE_DREAM; } /** @hide */ @@ -823,6 +828,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu case ACTIVITY_TYPE_HOME: return "home"; case ACTIVITY_TYPE_RECENTS: return "recents"; case ACTIVITY_TYPE_ASSISTANT: return "assistant"; + case ACTIVITY_TYPE_DREAM: return "dream"; } return String.valueOf(applicationType); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 95385ee9b79c..b1d6c830d3b7 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -11215,6 +11215,17 @@ public class Intent implements Parcelable, Cloneable { && hasWebURI(); } + private boolean isImageCaptureIntent() { + return (MediaStore.ACTION_IMAGE_CAPTURE.equals(mAction) + || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(mAction) + || MediaStore.ACTION_VIDEO_CAPTURE.equals(mAction)); + } + + /** @hide */ + public boolean isImplicitImageCaptureIntent() { + return mPackage == null && mComponent == null && isImageCaptureIntent(); + } + /** * @hide */ @@ -11241,9 +11252,7 @@ public class Intent implements Parcelable, Cloneable { } putParcelableArrayListExtra(EXTRA_STREAM, newStreams); } - } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action) - || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action) - || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) { + } else if (isImageCaptureIntent()) { final Uri output = getParcelableExtra(MediaStore.EXTRA_OUTPUT); if (output != null) { putExtra(MediaStore.EXTRA_OUTPUT, maybeAddUserId(output, contentUserHint)); @@ -11349,9 +11358,7 @@ public class Intent implements Parcelable, Cloneable { } } catch (ClassCastException e) { } - } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action) - || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action) - || MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) { + } else if (isImageCaptureIntent()) { final Uri output; try { output = getParcelableExtra(MediaStore.EXTRA_OUTPUT); diff --git a/core/java/android/inputmethodservice/InlineSuggestionSession.java b/core/java/android/inputmethodservice/InlineSuggestionSession.java index c31cb4e61b6e..9b3e8c9c137d 100644 --- a/core/java/android/inputmethodservice/InlineSuggestionSession.java +++ b/core/java/android/inputmethodservice/InlineSuggestionSession.java @@ -166,9 +166,14 @@ class InlineSuggestionSession { } return; } - + // The IME doesn't have information about the virtual view id for the child views in the + // web view, so we are only comparing the parent view id here. This means that for cases + // where there are two input fields in the web view, they will have the same view id + // (although different virtual child id), and we will not be able to distinguish them. + final AutofillId imeClientFieldId = mClientAutofillIdSupplier.get(); if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get()) - || !fieldId.equalsIgnoreSession(mClientAutofillIdSupplier.get())) { + || imeClientFieldId == null + || fieldId.getViewId() != imeClientFieldId.getViewId()) { if (DEBUG) { Log.d(TAG, "handleOnInlineSuggestionsResponse() called on the wrong package/field " diff --git a/core/java/android/net/InvalidPacketException.java b/core/java/android/net/InvalidPacketException.java index b3b0f11a776b..1873d778c0f2 100644 --- a/core/java/android/net/InvalidPacketException.java +++ b/core/java/android/net/InvalidPacketException.java @@ -27,7 +27,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi -public class InvalidPacketException extends Exception { +public final class InvalidPacketException extends Exception { private final int mError; // Must match SocketKeepalive#ERROR_INVALID_IP_ADDRESS. diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index fcfcebdf0862..05d7860ff891 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -2000,7 +2000,7 @@ public final class NetworkCapabilities implements Parcelable { */ @SystemApi @TestApi - public static class Builder { + public static final class Builder { private final NetworkCapabilities mCaps; /** diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 71f931da1a92..5cc73caa4f60 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -72,16 +72,16 @@ public class V4Signature { * V4 signature data. */ public static class SigningInfo { - public final byte[] v3Digest; // used to match with the corresponding APK + public final byte[] apkDigest; // used to match with the corresponding APK public final byte[] certificate; // ASN.1 DER form public final byte[] additionalData; // a free-form binary data blob public final byte[] publicKey; // ASN.1 DER, must match the certificate public final int signatureAlgorithmId; // see the APK v2 doc for the list public final byte[] signature; - SigningInfo(byte[] v3Digest, byte[] certificate, byte[] additionalData, + SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData, byte[] publicKey, int signatureAlgorithmId, byte[] signature) { - this.v3Digest = v3Digest; + this.apkDigest = apkDigest; this.certificate = certificate; this.additionalData = additionalData; this.publicKey = publicKey; @@ -94,13 +94,13 @@ public class V4Signature { */ public static SigningInfo fromByteArray(byte[] bytes) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); - byte[] v3Digest = readBytes(buffer); + byte[] apkDigest = readBytes(buffer); byte[] certificate = readBytes(buffer); byte[] additionalData = readBytes(buffer); byte[] publicKey = readBytes(buffer); int signatureAlgorithmId = buffer.getInt(); byte[] signature = readBytes(buffer); - return new SigningInfo(v3Digest, certificate, additionalData, publicKey, + return new SigningInfo(apkDigest, certificate, additionalData, publicKey, signatureAlgorithmId, signature); } } @@ -150,7 +150,7 @@ public class V4Signature { final int size = 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize( hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize( - signingInfo.v3Digest) + bytesSize(signingInfo.certificate) + bytesSize( + signingInfo.apkDigest) + bytesSize(signingInfo.certificate) + bytesSize( signingInfo.additionalData); ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(size); @@ -159,7 +159,7 @@ public class V4Signature { buffer.put(hashingInfo.log2BlockSize); writeBytes(buffer, hashingInfo.salt); writeBytes(buffer, hashingInfo.rawRootHash); - writeBytes(buffer, signingInfo.v3Digest); + writeBytes(buffer, signingInfo.apkDigest); writeBytes(buffer, signingInfo.certificate); writeBytes(buffer, signingInfo.additionalData); return buffer.array(); diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 04be71f2babc..346fe293d7ae 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -24,6 +24,7 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContent import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4; import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; import android.util.ArrayMap; @@ -117,7 +118,10 @@ public class ApkSignatureSchemeV2Verifier { return vSigner.certs; } - private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) + /** + * Same as above returns the full signer object, containing additional info e.g. digest. + */ + public static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) throws SignatureNotFoundException, SecurityException, IOException { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { return verify(apk, verifyIntegrity); @@ -209,9 +213,11 @@ public class ApkSignatureSchemeV2Verifier { verityDigest, apk.length(), signatureInfo); } + byte[] digest = pickBestDigestForV4(contentDigests); + return new VerifiedSigner( signerCerts.toArray(new X509Certificate[signerCerts.size()][]), - verityRootHash); + verityRootHash, digest); } private static X509Certificate[] verifySigner( @@ -426,11 +432,14 @@ public class ApkSignatureSchemeV2Verifier { */ public static class VerifiedSigner { public final X509Certificate[][] certs; + public final byte[] verityRootHash; + public final byte[] digest; - public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash) { + public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash, byte[] digest) { this.certs = certs; this.verityRootHash = verityRootHash; + this.digest = digest; } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index 2437af26770b..4ab541b616ed 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -16,8 +16,6 @@ package android.util.apk; -import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256; -import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; @@ -26,6 +24,7 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContent import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4; import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; import android.os.Build; @@ -213,24 +212,11 @@ public class ApkSignatureSchemeV3Verifier { verityDigest, apk.length(), signatureInfo); } - result.digest = pickBestV3DigestForV4(contentDigests); + result.digest = pickBestDigestForV4(contentDigests); return result; } - // Keep in sync with pickBestV3DigestForV4 in apksigner.V3SchemeVerifier. - private static byte[] pickBestV3DigestForV4(Map<Integer, byte[]> contentDigests) { - final int[] orderedContentDigestTypes = - {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256, - CONTENT_DIGEST_CHUNKED_SHA256}; - for (int contentDigestType : orderedContentDigestTypes) { - if (contentDigests.containsKey(contentDigestType)) { - return contentDigests.get(contentDigestType); - } - } - return null; - } - private static VerifiedSigner verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java index 8c240d99f590..d40efce0b3b3 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java @@ -145,7 +145,7 @@ public class ApkSignatureSchemeV4Verifier { "Public key mismatch between certificate and signature record"); } - return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.v3Digest); + return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.apkDigest); } /** @@ -155,11 +155,11 @@ public class ApkSignatureSchemeV4Verifier { */ public static class VerifiedSigner { public final Certificate[] certs; - public byte[] v3Digest; + public byte[] apkDigest; - public VerifiedSigner(Certificate[] certs, byte[] v3Digest) { + public VerifiedSigner(Certificate[] certs, byte[] apkDigest) { this.certs = certs; - this.v3Digest = v3Digest; + this.apkDigest = apkDigest; } } diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index c1cee48cc663..ab8f80d3d1a5 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -184,27 +184,45 @@ public class ApkSignatureVerifier { Signature[] signerSigs = convertToSignatures(signerCerts); if (verifyFull) { - // v4 is an add-on and requires v3 signature to validate against its certificates - ApkSignatureSchemeV3Verifier.VerifiedSigner nonstreaming = - ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); - Certificate[][] nonstreamingCerts = new Certificate[][]{nonstreaming.certs}; - Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); + byte[] nonstreamingDigest = null; + Certificate[][] nonstreamingCerts = null; + + try { + // v4 is an add-on and requires v2 or v3 signature to validate against its + // certificate and digest + ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer = + ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); + nonstreamingDigest = v3Signer.digest; + nonstreamingCerts = new Certificate[][]{v3Signer.certs}; + } catch (SignatureNotFoundException e) { + try { + ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer = + ApkSignatureSchemeV2Verifier.verify(apkPath, false); + nonstreamingDigest = v2Signer.digest; + nonstreamingCerts = v2Signer.certs; + } catch (SignatureNotFoundException ee) { + throw new SecurityException( + "V4 verification failed to collect V2/V3 certificates from : " + + apkPath, ee); + } + } + Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); if (nonstreamingSigs.length != signerSigs.length) { throw new SecurityException( - "Invalid number of certificates: " + nonstreaming.certs.length); + "Invalid number of certificates: " + nonstreamingSigs.length); } for (int i = 0, size = signerSigs.length; i < size; ++i) { if (!nonstreamingSigs[i].equals(signerSigs[i])) { - throw new SecurityException("V4 signature certificate does not match V3"); + throw new SecurityException( + "V4 signature certificate does not match V2/V3"); } } - // TODO(b/151240006): add support for v2 digest and make it mandatory. - if (!ArrayUtils.isEmpty(vSigner.v3Digest) && !ArrayUtils.equals(vSigner.v3Digest, - nonstreaming.digest, vSigner.v3Digest.length)) { - throw new SecurityException("V3 digest in V4 signature does not match V3"); + if (!ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest, + vSigner.apkDigest.length)) { + throw new SecurityException("APK digest in V4 signature does not match V2/V3"); } } diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index 4fe8515143a0..2a4b65d23e64 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -421,6 +421,10 @@ final class ApkSigningBlockUtils { static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3; + private static final int[] V4_CONTENT_DIGEST_ALGORITHMS = + {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256, + CONTENT_DIGEST_CHUNKED_SHA256}; + static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) { int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); @@ -572,6 +576,21 @@ final class ApkSigningBlockUtils { } /** + * Returns the best digest from the map of available digests. + * similarly to compareContentDigestAlgorithm. + * + * Keep in sync with pickBestDigestForV4 in apksigner's ApkSigningBlockUtils. + */ + static byte[] pickBestDigestForV4(Map<Integer, byte[]> contentDigests) { + for (int algo : V4_CONTENT_DIGEST_ALGORITHMS) { + if (contentDigests.containsKey(algo)) { + return contentDigests.get(algo); + } + } + return null; + } + + /** * Returns new byte buffer whose content is a shared subsequence of this buffer's content * between the specified start (inclusive) and end (exclusive) positions. As opposed to * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 4aeea10eee68..62dd192a6d67 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -405,6 +405,13 @@ public class Editor { // The actual zoom value may changes based on this initial zoom value. private float mInitialZoom = 1f; + // For calculating the line change slops while moving cursor/selection. + // The slop max/min value include line height and the slop on the upper/lower line. + private static final int LINE_CHANGE_SLOP_MAX_DP = 45; + private static final int LINE_CHANGE_SLOP_MIN_DP = 12; + private int mLineChangeSlopMax; + private int mLineChangeSlopMin; + Editor(TextView textView) { mTextView = textView; // Synchronize the filter list, which places the undo input filter at the end. @@ -430,6 +437,14 @@ public class Editor { logCursor("Editor", "New magnifier is %s.", mNewMagnifierEnabled ? "enabled" : "disabled"); } + + mLineChangeSlopMax = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, LINE_CHANGE_SLOP_MAX_DP, + mTextView.getContext().getResources().getDisplayMetrics()); + mLineChangeSlopMin = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, LINE_CHANGE_SLOP_MIN_DP, + mTextView.getContext().getResources().getDisplayMetrics()); + } @VisibleForTesting @@ -6018,7 +6033,14 @@ public class Editor { } } - private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { + @VisibleForTesting + public void setLineChangeSlopMinMaxForTesting(final int min, final int max) { + mLineChangeSlopMin = min; + mLineChangeSlopMax = max; + } + + @VisibleForTesting + public int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { final int trueLine = mTextView.getLineAtCoordinate(y); if (layout == null || prevLine > layout.getLineCount() || layout.getLineCount() <= 0 || prevLine < 0) { @@ -6031,28 +6053,21 @@ public class Editor { return trueLine; } + final int lineHeight = layout.getLineBottom(prevLine) - layout.getLineTop(prevLine); + int slop = (int)(LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS + * (layout.getLineBottom(trueLine) - layout.getLineTop(trueLine))); + slop = Math.max(mLineChangeSlopMin, + Math.min(mLineChangeSlopMax, lineHeight + slop)) - lineHeight; + slop = Math.max(0, slop); + final float verticalOffset = mTextView.viewportToContentVerticalOffset(); - final int lineCount = layout.getLineCount(); - final float slop = mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS; - - final float firstLineTop = layout.getLineTop(0) + verticalOffset; - final float prevLineTop = layout.getLineTop(prevLine) + verticalOffset; - final float yTopBound = Math.max(prevLineTop - slop, firstLineTop + slop); - - final float lastLineBottom = layout.getLineBottom(lineCount - 1) + verticalOffset; - final float prevLineBottom = layout.getLineBottom(prevLine) + verticalOffset; - final float yBottomBound = Math.min(prevLineBottom + slop, lastLineBottom - slop); - - // Determine if we've moved lines based on y position and previous line. - int currLine; - if (y <= yTopBound) { - currLine = Math.max(prevLine - 1, 0); - } else if (y >= yBottomBound) { - currLine = Math.min(prevLine + 1, lineCount - 1); - } else { - currLine = prevLine; + if (trueLine > prevLine && y >= layout.getLineBottom(prevLine) + slop + verticalOffset) { + return trueLine; + } + if (trueLine < prevLine && y <= layout.getLineTop(prevLine) - slop + verticalOffset) { + return trueLine; } - return currLine; + return prevLine; } /** diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 8943da4a2fc0..4f14539dd976 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -144,9 +144,6 @@ public class Toast { @Nullable private CharSequence mText; - // TODO(b/144152069): Remove this after assessing impact on dogfood. - private boolean mIsCustomToast; - /** * Construct an empty Toast object. You must call {@link #setView} before you * can call {@link #show}. @@ -214,8 +211,7 @@ public class Toast { service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback); } } else { - service.enqueueTextOrCustomToast(pkg, mToken, tn, mDuration, displayId, - mIsCustomToast); + service.enqueueToast(pkg, mToken, tn, mDuration, displayId); } } catch (RemoteException e) { // Empty @@ -253,7 +249,6 @@ public class Toast { */ @Deprecated public void setView(View view) { - mIsCustomToast = true; mNextView = view; } diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index ed2c5b2b4930..ab57e3dcdc53 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2665,4 +2665,8 @@ enum PageId { // Open: Settings > Sound > Do Not Disturb > People > Messages // OS: R DND_MESSAGES = 1839; + + // Open: Settings > Sound > Do Not Disturb > Apps > <Choose App> + // OS: R + DND_APPS_BYPASSING = 1840; } diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java index ba27fac05a99..eae1bbc930d4 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java @@ -15,11 +15,12 @@ */ package android.view.contentcapture; +import static org.mockito.Mockito.mock; import static org.testng.Assert.assertThrows; +import android.content.ContentCaptureOptions; import android.content.Context; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -37,16 +38,20 @@ public class ContentCaptureManagerTest { @Mock private Context mMockContext; - private ContentCaptureManager mManager; - - @Before - public void before() { - mManager = new ContentCaptureManager(mMockContext, /* service= */ null, - /* options= */ null); + @Test + public void testConstructor_invalidParametersThrowsException() { + assertThrows(NullPointerException.class, + () -> new ContentCaptureManager(mMockContext, /* service= */ null, /* options= */ + null)); } @Test - public void testRemoveData_invalid() { - assertThrows(NullPointerException.class, () -> mManager.removeData(null)); + public void testRemoveData_invalidParametersThrowsException() { + final IContentCaptureManager mockService = mock(IContentCaptureManager.class); + final ContentCaptureOptions options = new ContentCaptureOptions(null); + final ContentCaptureManager manager = + new ContentCaptureManager(mMockContext, mockService, options); + + assertThrows(NullPointerException.class, () -> manager.removeData(null)); } } diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java index f81964c9cdf9..1b5ce8fd4ce5 100644 --- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java +++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java @@ -16,6 +16,7 @@ package android.widget; +import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE; import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; import static android.widget.espresso.TextViewActions.dragOnText; import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex; @@ -37,6 +38,9 @@ import android.app.Activity; import android.app.Instrumentation; import android.graphics.Rect; import android.text.Layout; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.AbsoluteSizeSpan; import android.util.ArraySet; import android.util.Log; import android.view.InputDevice; @@ -489,6 +493,38 @@ public class EditorCursorDragTest { } @Test + public void testLineChangeSlop() throws Throwable { + TextView tv = mActivity.findViewById(R.id.textview); + Spannable s = new SpannableString("a\nb\nc"); + s.setSpan(new AbsoluteSizeSpan(10), 2, 4, SPAN_INCLUSIVE_EXCLUSIVE); + s.setSpan(new AbsoluteSizeSpan(32), 4, 5, SPAN_INCLUSIVE_EXCLUSIVE); + mInstrumentation.runOnMainSync(() -> tv.setText(s)); + + Layout layout = tv.getLayout(); + Editor editor = tv.getEditorForTesting(); + final float verticalOffset = tv.getExtendedPaddingTop(); + editor.setLineChangeSlopMinMaxForTesting(30, 65); + // Hit top part of upper line, jump to upper line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 5 + verticalOffset)) + .isEqualTo(0); + // Hit bottom part of upper line, stay at current line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 40 + verticalOffset)) + .isEqualTo(1); + // Hit current line, stay at current line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 70 + verticalOffset)) + .isEqualTo(1); + // Hit top part of lower line, stay at current line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 85 + verticalOffset)) + .isEqualTo(1); + // Hit bottom part of lower line, jump to lower line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 1, 110 + verticalOffset)) + .isEqualTo(2); + // Hit lower line of lower line, jump to target line. + assertThat(editor.getCurrentLineAdjustedForSlop(layout, 0, 110 + verticalOffset)) + .isEqualTo(2); + } + + @Test public void testCursorDrag_snapDistance() throws Throwable { String text = "line1: This is the 1st line: A\n" + "line2: This is the 2nd line: B\n" diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index b1300a97a324..922caeb0a817 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -66,6 +66,7 @@ public class LocalMediaManager implements BluetoothCallback { private LocalBluetoothManager mLocalBluetoothManager; private InfoMediaManager mInfoMediaManager; private String mPackageName; + private MediaDevice mOnTransferBluetoothDevice; @VisibleForTesting List<MediaDevice> mMediaDevices = new ArrayList<>(); @@ -143,7 +144,7 @@ public class LocalMediaManager implements BluetoothCallback { final CachedBluetoothDevice cachedDevice = ((BluetoothMediaDevice) device).getCachedDevice(); if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) { - device.setState(MediaDeviceState.STATE_CONNECTING); + mOnTransferBluetoothDevice = connectDevice; cachedDevice.connect(); return; } @@ -389,6 +390,10 @@ public class LocalMediaManager implements BluetoothCallback { mCurrentConnectedDevice = infoMediaDevice != null ? infoMediaDevice : updateCurrentConnectedDevice(); dispatchDeviceListUpdate(); + if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) { + connectDevice(mOnTransferBluetoothDevice); + mOnTransferBluetoothDevice = null; + } } private List<MediaDevice> buildDisconnectedBluetoothDevice() { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 6b3a97f8c8de..4c61ef504090 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -107,8 +108,8 @@ public class LocalMediaManagerTest { when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile); - mInfoMediaDevice1 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1, - TEST_PACKAGE_NAME); + mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1, + TEST_PACKAGE_NAME)); mInfoMediaDevice2 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo2, TEST_PACKAGE_NAME); mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager, @@ -565,6 +566,34 @@ public class LocalMediaManagerTest { } @Test + public void onDeviceListAdded_transferToDisconnectedBluetooth_verifyConnectDevice() { + final List<MediaDevice> devices = new ArrayList<>(); + final MediaDevice currentDevice = mock(MediaDevice.class); + final MediaDevice device = mock(BluetoothMediaDevice.class); + final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + mLocalMediaManager.mMediaDevices.add(device); + mLocalMediaManager.mMediaDevices.add(currentDevice); + + when(device.getId()).thenReturn(TEST_DEVICE_ID_1); + when(currentDevice.getId()).thenReturn(TEST_CURRENT_DEVICE_ID); + when(((BluetoothMediaDevice) device).getCachedDevice()).thenReturn(cachedDevice); + when(cachedDevice.isConnected()).thenReturn(false); + when(cachedDevice.isBusy()).thenReturn(false); + + mLocalMediaManager.registerCallback(mCallback); + mLocalMediaManager.connectDevice(device); + + verify(cachedDevice).connect(); + when(device.isConnected()).thenReturn(true); + mLocalMediaManager.mCurrentConnectedDevice = currentDevice; + devices.add(mInfoMediaDevice1); + devices.add(currentDevice); + mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); + + verify(mInfoMediaDevice1).connect(); + } + + @Test public void onRequestFailed_shouldDispatchOnRequestFailed() { mLocalMediaManager.registerCallback(mCallback); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 09d7d26e4dfe..b3299916356c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -490,6 +490,9 @@ public class DozeTriggers implements DozeMachine.Part { public void onPowerSaveChanged(boolean active) { if (mDozeHost.isPowerSaveActive()) { mMachine.requestState(DozeMachine.State.DOZE); + } else if (mMachine.getState() == DozeMachine.State.DOZE + && mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) { + mMachine.requestState(DozeMachine.State.DOZE_AOD); } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java index 3cdc01d95f58..dea8c32dc88d 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java +++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java @@ -38,7 +38,7 @@ public class CurrentUserObservable { @Override protected void onInactive() { super.onInactive(); - mTracker.startTracking(); + mTracker.stopTracking(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index c6d84ff79bde..d2f781d2e19c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -651,7 +651,7 @@ public class NotificationEntryManager implements */ public void updateNotifications(String reason) { reapplyFilterAndSort(reason); - if (mPresenter != null) { + if (mPresenter != null && !mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mPresenter.updateNotificationViews(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt index 0437877d8330..cf670bd5a424 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt @@ -142,9 +142,11 @@ class NotifViewManager @Inject constructor( // To attach rows we can use _this one weird trick_: if the intended view to add does not // have a parent, then simply add it (and its children). entries.forEach { entry -> - val listItem = rowRegistry.requireView(entry) + // TODO: We should eventually map GroupEntry's themselves to views so that we don't + // depend on representativeEntry here which may actually be null in the future + val listItem = rowRegistry.requireView(entry.representativeEntry!!) - if (listItem.view.parent != null) { + if (listItem.view.parent == null) { listContainer.addListItem(listItem) stabilityManager.notifyViewAddition(listItem.view) } @@ -153,7 +155,8 @@ class NotifViewManager @Inject constructor( for ((idx, childEntry) in entry.children.withIndex()) { val childListItem = rowRegistry.requireView(childEntry) // Child hasn't been added yet. add it! - if (!listItem.notificationChildren.contains(childListItem)) { + if (listItem.notificationChildren == null || + !listItem.notificationChildren.contains(childListItem)) { // TODO: old code here just Log.wtf()'d here. This might wreak havoc if (childListItem.view.parent != null) { throw IllegalStateException("trying to add a notification child that " + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index 7e9e76096873..e9cbf32ee052 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -45,7 +45,8 @@ public class RankingCoordinator implements Coordinator { public void attach(NotifPipeline pipeline) { mStatusBarStateController.addCallback(mStatusBarStateCallback); - pipeline.addPreGroupFilter(mNotifFilter); + pipeline.addPreGroupFilter(mSuspendedFilter); + pipeline.addPreGroupFilter(mDozingFilter); } /** @@ -53,33 +54,30 @@ public class RankingCoordinator implements Coordinator { * NotifListBuilder invalidates the notification list each time the ranking is updated, * so we don't need to explicitly invalidate this filter on ranking update. */ - private final NotifFilter mNotifFilter = new NotifFilter(TAG) { + private final NotifFilter mSuspendedFilter = new NotifFilter("IsSuspendedFilter") { @Override public boolean shouldFilterOut(NotificationEntry entry, long now) { - // App suspended from Ranking - if (entry.getRanking().isSuspended()) { - return true; - } + return entry.getRanking().isSuspended(); + } + }; + private final NotifFilter mDozingFilter = new NotifFilter("IsDozingFilter") { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { // Dozing + DND Settings from Ranking object if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) { return true; } - if (!mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList()) { - return true; - } - - return false; + return !mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList(); } }; - private final StatusBarStateController.StateListener mStatusBarStateCallback = new StatusBarStateController.StateListener() { @Override public void onDozingChanged(boolean isDozing) { - mNotifFilter.invalidateList(); + mDozingFilter.invalidateList(); } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index be8af82f8baf..823b18660bed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -6478,7 +6478,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private boolean hasActiveNotifications() { if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - return mNotifPipeline.getShadeList().isEmpty(); + return !mNotifPipeline.getShadeList().isEmpty(); } else { return mEntryManager.hasActiveNotifications(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index e84f9cf352ed..85acbe6d440b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -19,9 +19,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -42,6 +41,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -51,20 +51,23 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private NotifPipeline mNotifPipeline; + + @Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor; + private NotificationEntry mEntry; - private RankingCoordinator mRankingCoordinator; - private NotifFilter mRankingFilter; + private NotifFilter mCapturedSuspendedFilter; + private NotifFilter mCapturedDozingFilter; @Before public void setup() { MockitoAnnotations.initMocks(this); - mRankingCoordinator = new RankingCoordinator(mStatusBarStateController); + RankingCoordinator rankingCoordinator = new RankingCoordinator(mStatusBarStateController); mEntry = new NotificationEntryBuilder().build(); - ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); - mRankingCoordinator.attach(mNotifPipeline); - verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture()); - mRankingFilter = filterCaptor.getValue(); + rankingCoordinator.attach(mNotifPipeline); + verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture()); + mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0); + mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1); } @Test @@ -73,7 +76,7 @@ public class RankingCoordinatorTest extends SysuiTestCase { mEntry.setRanking(getRankingForUnfilteredNotif().build()); // THEN don't filter out the notification - assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertFalse(mCapturedSuspendedFilter.shouldFilterOut(mEntry, 0)); } @Test @@ -84,7 +87,7 @@ public class RankingCoordinatorTest extends SysuiTestCase { .build()); // THEN filter out the notification - assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertTrue(mCapturedSuspendedFilter.shouldFilterOut(mEntry, 0)); } @Test @@ -98,13 +101,13 @@ public class RankingCoordinatorTest extends SysuiTestCase { when(mStatusBarStateController.isDozing()).thenReturn(true); // THEN filter out the notification - assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertTrue(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); // WHEN it's not dozing (showing the notification list) when(mStatusBarStateController.isDozing()).thenReturn(false); // THEN don't filter out the notification - assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertFalse(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); } @Test @@ -118,13 +121,13 @@ public class RankingCoordinatorTest extends SysuiTestCase { when(mStatusBarStateController.isDozing()).thenReturn(true); // THEN don't filter out the notification - assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertFalse(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); // WHEN it's not dozing (showing the notification list) when(mStatusBarStateController.isDozing()).thenReturn(false); // THEN filter out the notification - assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0)); + assertTrue(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); } private RankingBuilder getRankingForUnfilteredNotif() { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index e49c1ed47c93..c6a54fc3d206 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -50,6 +50,7 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; @@ -63,6 +64,7 @@ import android.util.LocalLog; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.TimeUtils; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.SmartSuggestionMode; @@ -151,6 +153,7 @@ public final class AutofillManagerService private final LocalLog mWtfHistory = new LocalLog(50); private final AutofillCompatState mAutofillCompatState = new AutofillCompatState(); + private final DisabledInfoCache mDisabledInfoCache = new DisabledInfoCache(); private final LocalService mLocalService = new LocalService(); private final ActivityManagerInternal mAm; @@ -302,14 +305,15 @@ public final class AutofillManagerService @Override // from AbstractMasterSystemService protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId, boolean disabled) { - return new AutofillManagerServiceImpl(this, mLock, mUiLatencyHistory, - mWtfHistory, resolvedUserId, mUi, mAutofillCompatState, disabled); + return new AutofillManagerServiceImpl(this, mLock, mUiLatencyHistory, mWtfHistory, + resolvedUserId, mUi, mAutofillCompatState, disabled, mDisabledInfoCache); } @Override // AbstractMasterSystemService protected void onServiceRemoved(@NonNull AutofillManagerServiceImpl service, @UserIdInt int userId) { service.destroyLocked(); + mDisabledInfoCache.remove(userId); mAutofillCompatState.removeCompatibilityModeRequests(userId); } @@ -835,15 +839,10 @@ public final class AutofillManagerService private void injectDisableAppInfo(@NonNull AutofillOptions options, int userId, String packageName) { - synchronized (mLock) { - final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); - if (service != null) { - options.appDisabledExpiration = service.getAppDisabledExpirationLocked( - packageName); - options.disabledActivities = service.getAppDisabledActivitiesLocked( - packageName); - } - } + options.appDisabledExpiration = + mDisabledInfoCache.getAppDisabledExpiration(userId, packageName); + options.disabledActivities = + mDisabledInfoCache.getAppDisabledActivities(userId, packageName); } } @@ -867,6 +866,234 @@ public final class AutofillManagerService } /** + * Stores autofill disable information, i.e. {@link AutofillDisabledInfo}, keyed by user id. + * The information is cleaned up when the service is removed. + */ + static final class DisabledInfoCache { + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final SparseArray<AutofillDisabledInfo> mCache = new SparseArray<>(); + + void remove(@UserIdInt int userId) { + synchronized (mLock) { + mCache.remove(userId); + } + } + + void addDisabledAppLocked(@UserIdInt int userId, @NonNull String packageName, + long expiration) { + Preconditions.checkNotNull(packageName); + synchronized (mLock) { + AutofillDisabledInfo info = + getOrCreateAutofillDisabledInfoByUserIdLocked(userId); + info.putDisableAppsLocked(packageName, expiration); + } + } + + void addDisabledActivityLocked(@UserIdInt int userId, @NonNull ComponentName componentName, + long expiration) { + Preconditions.checkNotNull(componentName); + synchronized (mLock) { + AutofillDisabledInfo info = + getOrCreateAutofillDisabledInfoByUserIdLocked(userId); + info.putDisableActivityLocked(componentName, expiration); + } + } + + boolean isAutofillDisabledLocked(@UserIdInt int userId, + @NonNull ComponentName componentName) { + Preconditions.checkNotNull(componentName); + final boolean disabled; + synchronized (mLock) { + final AutofillDisabledInfo info = mCache.get(userId); + disabled = info != null ? info.isAutofillDisabledLocked(componentName) : false; + } + return disabled; + } + + long getAppDisabledExpiration(@UserIdInt int userId, @NonNull String packageName) { + Preconditions.checkNotNull(packageName); + final Long expiration; + synchronized (mLock) { + final AutofillDisabledInfo info = mCache.get(userId); + expiration = info != null ? info.getAppDisabledExpirationLocked(packageName) : 0; + } + return expiration; + } + + @Nullable + ArrayMap<String, Long> getAppDisabledActivities(@UserIdInt int userId, + @NonNull String packageName) { + Preconditions.checkNotNull(packageName); + final ArrayMap<String, Long> disabledList; + synchronized (mLock) { + final AutofillDisabledInfo info = mCache.get(userId); + disabledList = + info != null ? info.getAppDisabledActivitiesLocked(packageName) : null; + } + return disabledList; + } + + void dump(@UserIdInt int userId, String prefix, PrintWriter pw) { + synchronized (mLock) { + final AutofillDisabledInfo info = mCache.get(userId); + if (info != null) { + info.dumpLocked(prefix, pw); + } + } + } + + @NonNull + private AutofillDisabledInfo getOrCreateAutofillDisabledInfoByUserIdLocked( + @UserIdInt int userId) { + AutofillDisabledInfo info = mCache.get(userId); + if (info == null) { + info = new AutofillDisabledInfo(); + mCache.put(userId, info); + } + return info; + } + } + + /** + * The autofill disable information. + * <p> + * This contains disable information set by the AutofillService, e.g. disabled application + * expiration, disable activity expiration. + */ + private static final class AutofillDisabledInfo { + /** + * Apps disabled by the service; key is package name, value is when they will be enabled + * again. + */ + private ArrayMap<String, Long> mDisabledApps; + /** + * Activities disabled by the service; key is component name, value is when they will be + * enabled again. + */ + private ArrayMap<ComponentName, Long> mDisabledActivities; + + void putDisableAppsLocked(@NonNull String packageName, long expiration) { + if (mDisabledApps == null) { + mDisabledApps = new ArrayMap<>(1); + } + mDisabledApps.put(packageName, expiration); + } + + void putDisableActivityLocked(@NonNull ComponentName componentName, long expiration) { + if (mDisabledActivities == null) { + mDisabledActivities = new ArrayMap<>(1); + } + mDisabledActivities.put(componentName, expiration); + } + + long getAppDisabledExpirationLocked(@NonNull String packageName) { + if (mDisabledApps == null) { + return 0; + } + final Long expiration = mDisabledApps.get(packageName); + return expiration != null ? expiration : 0; + } + + ArrayMap<String, Long> getAppDisabledActivitiesLocked(@NonNull String packageName) { + if (mDisabledActivities != null) { + final int size = mDisabledActivities.size(); + ArrayMap<String, Long> disabledList = null; + for (int i = 0; i < size; i++) { + final ComponentName component = mDisabledActivities.keyAt(i); + if (packageName.equals(component.getPackageName())) { + if (disabledList == null) { + disabledList = new ArrayMap<>(); + } + final long expiration = mDisabledActivities.valueAt(i); + disabledList.put(component.flattenToShortString(), expiration); + } + } + return disabledList; + } + return null; + } + + boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) { + // Check activities first. + long elapsedTime = 0; + if (mDisabledActivities != null) { + elapsedTime = SystemClock.elapsedRealtime(); + final Long expiration = mDisabledActivities.get(componentName); + if (expiration != null) { + if (expiration >= elapsedTime) return true; + // Restriction expired - clean it up. + if (sVerbose) { + Slog.v(TAG, "Removing " + componentName.toShortString() + + " from disabled list"); + } + mDisabledActivities.remove(componentName); + } + } + + // Then check apps. + final String packageName = componentName.getPackageName(); + if (mDisabledApps == null) return false; + + final Long expiration = mDisabledApps.get(packageName); + if (expiration == null) return false; + + if (elapsedTime == 0) { + elapsedTime = SystemClock.elapsedRealtime(); + } + + if (expiration >= elapsedTime) return true; + + // Restriction expired - clean it up. + if (sVerbose) Slog.v(TAG, "Removing " + packageName + " from disabled list"); + mDisabledApps.remove(packageName); + return false; + } + + void dumpLocked(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("Disabled apps: "); + if (mDisabledApps == null) { + pw.println("N/A"); + } else { + final int size = mDisabledApps.size(); + pw.println(size); + final StringBuilder builder = new StringBuilder(); + final long now = SystemClock.elapsedRealtime(); + for (int i = 0; i < size; i++) { + final String packageName = mDisabledApps.keyAt(i); + final long expiration = mDisabledApps.valueAt(i); + builder.append(prefix).append(prefix) + .append(i).append(". ").append(packageName).append(": "); + TimeUtils.formatDuration((expiration - now), builder); + builder.append('\n'); + } + pw.println(builder); + } + + pw.print(prefix); pw.print("Disabled activities: "); + if (mDisabledActivities == null) { + pw.println("N/A"); + } else { + final int size = mDisabledActivities.size(); + pw.println(size); + final StringBuilder builder = new StringBuilder(); + final long now = SystemClock.elapsedRealtime(); + for (int i = 0; i < size; i++) { + final ComponentName component = mDisabledActivities.keyAt(i); + final long expiration = mDisabledActivities.valueAt(i); + builder.append(prefix).append(prefix) + .append(i).append(". ").append(component).append(": "); + TimeUtils.formatDuration((expiration - now), builder); + builder.append('\n'); + } + pw.println(builder); + } + } + } + + /** * Compatibility mode metadata associated with all services. * * <p>This object is defined here instead of on each {@link AutofillManagerServiceImpl} because diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 6fbe1410bbad..d1805d96cad8 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -67,7 +67,6 @@ import android.util.LocalLog; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; -import android.util.TimeUtils; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.SmartSuggestionMode; @@ -80,6 +79,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.LocalServices; import com.android.server.autofill.AutofillManagerService.AutofillCompatState; +import com.android.server.autofill.AutofillManagerService.DisabledInfoCache; import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks; import com.android.server.autofill.ui.AutoFillUI; import com.android.server.contentcapture.ContentCaptureManagerInternal; @@ -90,7 +90,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Random; - /** * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the * app's {@link IAutoFillService} implementation. @@ -125,19 +124,6 @@ final class AutofillManagerServiceImpl private RemoteInlineSuggestionRenderService mRemoteInlineSuggestionRenderService; /** - * Apps disabled by the service; key is package name, value is when they will be enabled again. - */ - @GuardedBy("mLock") - private ArrayMap<String, Long> mDisabledApps; - - /** - * Activities disabled by the service; key is component name, value is when they will be enabled - * again. - */ - @GuardedBy("mLock") - private ArrayMap<ComponentName, Long> mDisabledActivities; - - /** * Data used for field classification. */ @GuardedBy("mLock") @@ -186,10 +172,12 @@ final class AutofillManagerServiceImpl private final ContentCaptureManagerInternal mContentCaptureManagerInternal; + private final DisabledInfoCache mDisabledInfoCache; + AutofillManagerServiceImpl(AutofillManagerService master, Object lock, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, AutofillCompatState autofillCompatState, - boolean disabled) { + boolean disabled, DisabledInfoCache disableCache) { super(master, lock, userId); mUiLatencyHistory = uiLatencyHistory; @@ -200,7 +188,7 @@ final class AutofillManagerServiceImpl mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); mContentCaptureManagerInternal = LocalServices.getService( ContentCaptureManagerInternal.class); - + mDisabledInfoCache = disableCache; updateLocked(disabled); } @@ -1045,45 +1033,7 @@ final class AutofillManagerServiceImpl pw.println(isInlineSuggestionsEnabled()); pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune); - pw.print(prefix); pw.print("Disabled apps: "); - - if (mDisabledApps == null) { - pw.println("N/A"); - } else { - final int size = mDisabledApps.size(); - pw.println(size); - final StringBuilder builder = new StringBuilder(); - final long now = SystemClock.elapsedRealtime(); - for (int i = 0; i < size; i++) { - final String packageName = mDisabledApps.keyAt(i); - final long expiration = mDisabledApps.valueAt(i); - builder.append(prefix).append(prefix) - .append(i).append(". ").append(packageName).append(": "); - TimeUtils.formatDuration((expiration - now), builder); - builder.append('\n'); - } - pw.println(builder); - } - - pw.print(prefix); pw.print("Disabled activities: "); - - if (mDisabledActivities == null) { - pw.println("N/A"); - } else { - final int size = mDisabledActivities.size(); - pw.println(size); - final StringBuilder builder = new StringBuilder(); - final long now = SystemClock.elapsedRealtime(); - for (int i = 0; i < size; i++) { - final ComponentName component = mDisabledActivities.keyAt(i); - final long expiration = mDisabledActivities.valueAt(i); - builder.append(prefix).append(prefix) - .append(i).append(". ").append(component).append(": "); - TimeUtils.formatDuration((expiration - now), builder); - builder.append('\n'); - } - pw.println(builder); - } + mDisabledInfoCache.dump(mUserId, prefix, pw); final int size = mSessions.size(); if (size == 0) { @@ -1480,15 +1430,13 @@ final class AutofillManagerServiceImpl void disableAutofillForApp(@NonNull String packageName, long duration, int sessionId, boolean compatMode) { synchronized (mLock) { - if (mDisabledApps == null) { - mDisabledApps = new ArrayMap<>(1); - } long expiration = SystemClock.elapsedRealtime() + duration; // Protect it against overflow if (expiration < 0) { expiration = Long.MAX_VALUE; } - mDisabledApps.put(packageName, expiration); + mDisabledInfoCache.addDisabledAppLocked(mUserId, packageName, expiration); + int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP, packageName, getServicePackageName(), sessionId, compatMode) @@ -1502,15 +1450,12 @@ final class AutofillManagerServiceImpl void disableAutofillForActivity(@NonNull ComponentName componentName, long duration, int sessionId, boolean compatMode) { synchronized (mLock) { - if (mDisabledActivities == null) { - mDisabledActivities = new ArrayMap<>(1); - } long expiration = SystemClock.elapsedRealtime() + duration; // Protect it against overflow if (expiration < 0) { expiration = Long.MAX_VALUE; } - mDisabledActivities.put(componentName, expiration); + mDisabledInfoCache.addDisabledActivityLocked(mUserId, componentName, expiration); final int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; @@ -1528,74 +1473,12 @@ final class AutofillManagerServiceImpl } } - // Called by AutofillManagerService - long getAppDisabledExpirationLocked(@NonNull String packageName) { - if (mDisabledApps == null) { - return 0; - } - final Long expiration = mDisabledApps.get(packageName); - return expiration != null ? expiration : 0; - } - - // Called by AutofillManagerService - @Nullable - ArrayMap<String, Long> getAppDisabledActivitiesLocked(@NonNull String packageName) { - if (mDisabledActivities != null) { - final int size = mDisabledActivities.size(); - ArrayMap<String, Long> disabledList = null; - for (int i = 0; i < size; i++) { - final ComponentName component = mDisabledActivities.keyAt(i); - if (packageName.equals(component.getPackageName())) { - if (disabledList == null) { - disabledList = new ArrayMap<>(); - } - final long expiration = mDisabledActivities.valueAt(i); - disabledList.put(component.flattenToShortString(), expiration); - } - } - return disabledList; - } - return null; - } - /** * Checks if autofill is disabled by service to the given activity. */ @GuardedBy("mLock") private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) { - // Check activities first. - long elapsedTime = 0; - if (mDisabledActivities != null) { - elapsedTime = SystemClock.elapsedRealtime(); - final Long expiration = mDisabledActivities.get(componentName); - if (expiration != null) { - if (expiration >= elapsedTime) return true; - // Restriction expired - clean it up. - if (sVerbose) { - Slog.v(TAG, "Removing " + componentName.toShortString() - + " from disabled list"); - } - mDisabledActivities.remove(componentName); - } - } - - // Then check apps. - final String packageName = componentName.getPackageName(); - if (mDisabledApps == null) return false; - - final Long expiration = mDisabledApps.get(packageName); - if (expiration == null) return false; - - if (elapsedTime == 0) { - elapsedTime = SystemClock.elapsedRealtime(); - } - - if (expiration >= elapsedTime) return true; - - // Restriction expired - clean it up. - if (sVerbose) Slog.v(TAG, "Removing " + packageName + " from disabled list"); - mDisabledApps.remove(packageName); - return false; + return mDisabledInfoCache.isAutofillDisabledLocked(mUserId, componentName); } // Called by AutofillManager, checks UID. diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java index 5de817101177..4ba2c3d0a0d3 100644 --- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java @@ -199,7 +199,10 @@ final class InlineSuggestionSession { return false; } - if (!mImeInputViewStarted || !autofillId.equalsIgnoreSession(mImeFieldId)) { + // TODO(b/151846600): IME doesn't have access to the virtual id of the webview, so we + // only compare the view id for now. + if (!mImeInputViewStarted || mImeFieldId == null + || autofillId.getViewId() != mImeFieldId.getViewId()) { if (sDebug) { Log.d(TAG, "onInlineSuggestionsResponseLocked not sent because input view is not " diff --git a/services/core/java/com/android/server/am/BugReportHandlerUtil.java b/services/core/java/com/android/server/am/BugReportHandlerUtil.java index 03f4a54086a8..ba89fce0b3f8 100644 --- a/services/core/java/com/android/server/am/BugReportHandlerUtil.java +++ b/services/core/java/com/android/server/am/BugReportHandlerUtil.java @@ -16,20 +16,15 @@ package com.android.server.am; -import static android.app.AppOpsManager.OP_NONE; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.app.Activity; import android.app.BroadcastOptions; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Binder; -import android.os.BugreportManager; -import android.os.BugreportParams; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -115,17 +110,9 @@ public final class BugReportHandlerUtil { options.setBackgroundActivityStartsAllowed(true); final long identity = Binder.clearCallingIdentity(); try { - // Handler app's BroadcastReceiver should call setResultCode(Activity.RESULT_OK) to - // let ResultBroadcastReceiver know the handler app is available. - context.sendOrderedBroadcastAsUser(intent, - UserHandle.of(handlerUser), + context.sendBroadcastAsUser(intent, UserHandle.of(handlerUser), android.Manifest.permission.DUMP, - OP_NONE, options.toBundle(), - new ResultBroadcastReceiver(), - /* scheduler= */ null, - Activity.RESULT_CANCELED, - /* initialData= */ null, - /* initialExtras= */ null); + options.toBundle()); } catch (RuntimeException e) { Slog.e(TAG, "Error while trying to launch bugreport handler app.", e); return false; @@ -189,19 +176,4 @@ public final class BugReportHandlerUtil { Binder.restoreCallingIdentity(identity); } } - - private static class ResultBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (getResultCode() == Activity.RESULT_OK) { - return; - } - - Slog.w(TAG, "Request bug report because handler app seems to be not available."); - BugreportManager bugreportManager = context.getSystemService(BugreportManager.class); - bugreportManager.requestBugreport( - new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE), - /* shareTitle= */null, /* shareDescription= */ null); - } - } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 60c387528c6a..7f805bef520d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -139,6 +139,7 @@ import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; @@ -387,10 +388,9 @@ public class NotificationManagerService extends SystemService { * still post toasts created with * {@link android.widget.Toast#makeText(Context, CharSequence, int)} and its variants while * in the background. - * - * TODO(b/144152069): Add @EnabledAfter(Q) to target R+ after assessing impact on dogfood */ @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private static final long CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK = 128611929L; private IActivityManager mAm; @@ -2751,24 +2751,18 @@ public class NotificationManagerService extends SystemService { @Override public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @Nullable ITransientNotificationCallback callback) { - enqueueToast(pkg, token, text, null, duration, displayId, callback, false); + enqueueToast(pkg, token, text, null, duration, displayId, callback); } @Override public void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId) { - enqueueToast(pkg, token, null, callback, duration, displayId, null, true); - } - - @Override - public void enqueueTextOrCustomToast(String pkg, IBinder token, - ITransientNotification callback, int duration, int displayId, boolean isCustom) { - enqueueToast(pkg, token, null, callback, duration, displayId, null, isCustom); + enqueueToast(pkg, token, null, callback, duration, displayId, null); } private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, int displayId, - @Nullable ITransientNotificationCallback textCallback, boolean isCustom) { + @Nullable ITransientNotificationCallback textCallback) { if (DBG) { Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token + " duration=" + duration + " displayId=" + displayId); @@ -2807,11 +2801,15 @@ public class NotificationManagerService extends SystemService { } boolean isAppRenderedToast = (callback != null); - if (isAppRenderedToast && isCustom && !isSystemToast - && !isPackageInForegroundForToast(pkg, callingUid)) { + if (isAppRenderedToast && !isSystemToast && !isPackageInForegroundForToast(pkg, + callingUid)) { boolean block; long id = Binder.clearCallingIdentity(); try { + // CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK is gated on targetSdk, so block will be + // false for apps with targetSdk < R. For apps with targetSdk R+, text toasts + // are not app-rendered, so isAppRenderedToast == true means it's a custom + // toast. block = mPlatformCompat.isChangeEnabledByPackageName( CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, pkg, callingUser.getIdentifier()); @@ -2824,11 +2822,6 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(id); } if (block) { - // TODO(b/144152069): Remove informative toast - mUiHandler.post(() -> Toast.makeText(getContext(), - "Background custom toast blocked for package " + pkg + ".\n" - + "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"); return; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 4e5d6c4efaab..e605eeba4d98 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -5070,15 +5070,16 @@ public class PackageManagerService extends IPackageManager.Stub * action and a {@code android.intent.category.BROWSABLE} category</li> * </ul> */ - int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps) { + int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, + boolean matchSystemOnly) { return updateFlagsForResolve(flags, userId, callingUid, - wantInstantApps, false /*onlyExposedExplicitly*/); + wantInstantApps, matchSystemOnly, false /*onlyExposedExplicitly*/); } int updateFlagsForResolve(int flags, int userId, int callingUid, - boolean wantInstantApps, boolean onlyExposedExplicitly) { + boolean wantInstantApps, boolean onlyExposedExplicitly, boolean matchSystemOnly) { // Safe mode means we shouldn't match any third-party components - if (mSafeMode) { + if (mSafeMode || matchSystemOnly) { flags |= PackageManager.MATCH_SYSTEM_ONLY; } if (getInstantAppPackageName(callingUid) != null) { @@ -6170,7 +6171,8 @@ public class PackageManagerService extends IPackageManager.Stub if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); - flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart); + flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); mPermissionManager.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/, false /*checkShell*/, "resolve intent"); @@ -6202,7 +6204,8 @@ public class PackageManagerService extends IPackageManager.Stub intent = updateIntentForResolve(intent); final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver()); final int flags = updateFlagsForResolve( - 0, userId, callingUid, false /*includeInstantApps*/); + 0, userId, callingUid, false /*includeInstantApps*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags, userId); synchronized (mLock) { @@ -6523,7 +6526,8 @@ public class PackageManagerService extends IPackageManager.Stub android.provider.Settings.Global.getInt(mContext.getContentResolver(), android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1; flags = updateFlagsForResolve( - flags, userId, callingUid, false /*includeInstantApps*/); + flags, userId, callingUid, false /*includeInstantApps*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); intent = updateIntentForResolve(intent); // writer synchronized (mLock) { @@ -6735,7 +6739,8 @@ public class PackageManagerService extends IPackageManager.Stub } synchronized (mLock) { int flags = updateFlagsForResolve(0, parent.id, callingUid, - false /*includeInstantApps*/); + false /*includeInstantApps*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); CrossProfileDomainInfo xpDomainInfo = getCrossProfileDomainPreferredLpr( intent, resolvedType, flags, sourceUserId, parent.id); return xpDomainInfo != null; @@ -6821,7 +6826,8 @@ public class PackageManagerService extends IPackageManager.Stub } flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart, - comp != null || pkgName != null /*onlyExposedExplicitly*/); + comp != null || pkgName != null /*onlyExposedExplicitly*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); if (comp != null) { final List<ResolveInfo> list = new ArrayList<>(1); final ActivityInfo ai = getActivityInfo(comp, flags, userId); @@ -7606,7 +7612,8 @@ public class PackageManagerService extends IPackageManager.Stub String resolvedType, int flags, int userId) { if (!mUserManager.exists(userId)) return Collections.emptyList(); final int callingUid = Binder.getCallingUid(); - flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/); + flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); mPermissionManager.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/, false /*checkShell*/, "query intent activity options"); @@ -7792,7 +7799,8 @@ public class PackageManagerService extends IPackageManager.Stub false /*requireFullPermission*/, false /*checkShell*/, "query intent receivers"); final String instantAppPkgName = getInstantAppPackageName(callingUid); - flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/); + flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, + intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -7882,7 +7890,8 @@ public class PackageManagerService extends IPackageManager.Stub private ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, int flags, int userId, int callingUid) { if (!mUserManager.exists(userId)) return null; - flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/); + flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, + false /* matchSystemOnly */); List<ResolveInfo> query = queryIntentServicesInternal( intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/); if (query != null) { @@ -7913,7 +7922,8 @@ public class PackageManagerService extends IPackageManager.Stub false /*checkShell*/, "query intent receivers"); final String instantAppPkgName = getInstantAppPackageName(callingUid); - flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps); + flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps, + false /* matchSystemOnly */); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -8050,7 +8060,8 @@ public class PackageManagerService extends IPackageManager.Stub if (!mUserManager.exists(userId)) return Collections.emptyList(); final int callingUid = Binder.getCallingUid(); final String instantAppPkgName = getInstantAppPackageName(callingUid); - flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/); + flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, + false /* matchSystemOnly */); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 6308d2596074..c6f375ea5d18 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -36,7 +36,6 @@ import static android.app.WindowConfiguration.windowingModeToString; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; @@ -3743,20 +3742,6 @@ class ActivityStack extends Task { return super.checkCompleteDeferredRemoval(); } - @Override - int getOrientation() { - return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET; - } - - private boolean canSpecifyOrientation() { - final int windowingMode = getWindowingMode(); - final int activityType = getActivityType(); - return windowingMode == WINDOWING_MODE_FULLSCREEN - || activityType == ACTIVITY_TYPE_HOME - || activityType == ACTIVITY_TYPE_RECENTS - || activityType == ACTIVITY_TYPE_ASSISTANT; - } - public DisplayInfo getDisplayInfo() { return mDisplayContent.getDisplayInfo(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 35492f4d145d..a5b0026398b6 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -31,6 +31,7 @@ import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -1273,9 +1274,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT; a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchActivityType(ACTIVITY_TYPE_DREAM); + try { getActivityStartController().obtainStarter(intent, "dream") .setActivityInfo(a) + .setActivityOptions(options.toBundle()) .setIsDream(true) .execute(); return true; diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 33dd9cf4ee05..1036af67c0db 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -467,6 +468,10 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return getActivityType() == ACTIVITY_TYPE_ASSISTANT; } + public boolean isActivityTypeDream() { + return getActivityType() == ACTIVITY_TYPE_DREAM; + } + public boolean isActivityTypeStandard() { return getActivityType() == ACTIVITY_TYPE_STANDARD; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 6365144f4f1c..ede456949116 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4485,6 +4485,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ private int findPositionForStack(int requestedPosition, ActivityStack stack, boolean adding) { + if (stack.isActivityTypeDream()) { + return POSITION_TOP; + } + if (stack.inPinnedWindowingMode()) { return POSITION_TOP; } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 5e88fb0437c6..4da4a79cfd83 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1527,25 +1527,7 @@ public class DisplayPolicy { && (mNotificationShade.getAttrs().privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0; - // When the navigation bar isn't visible, we put up a fake input window to catch all - // touch events. This way we can detect when the user presses anywhere to bring back the - // nav bar and ensure the application doesn't see the event. - if (navVisible || navAllowedHidden) { - if (mInputConsumer != null) { - mInputConsumer.dismiss(); - mHandler.sendMessage( - mHandler.obtainMessage(MSG_DISPOSE_INPUT_CONSUMER, mInputConsumer)); - mInputConsumer = null; - } - } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) { - mInputConsumer = mDisplayContent.getInputMonitor().createInputConsumer( - mHandler.getLooper(), - INPUT_CONSUMER_NAVIGATION, - HideNavInputEventReceiver::new); - // As long as mInputConsumer is active, hover events are not dispatched to the app - // and the pointer icon is likely to become stale. Hide it to avoid confusion. - InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL); - } + updateHideNavInputEventReceiver(navVisible, navAllowedHidden); // For purposes of positioning and showing the nav bar, if we have decided that it can't // be hidden (because of the screen aspect ratio), then take that into account. @@ -1567,6 +1549,28 @@ public class DisplayPolicy { mLastNotificationShadeForcesShowingNavigation = notificationShadeForcesShowingNavigation; } + void updateHideNavInputEventReceiver(boolean navVisible, boolean navAllowedHidden) { + // When the navigation bar isn't visible, we put up a fake input window to catch all + // touch events. This way we can detect when the user presses anywhere to bring back the + // nav bar and ensure the application doesn't see the event. + if (navVisible || navAllowedHidden) { + if (mInputConsumer != null) { + mInputConsumer.dismiss(); + mHandler.sendMessage( + mHandler.obtainMessage(MSG_DISPOSE_INPUT_CONSUMER, mInputConsumer)); + mInputConsumer = null; + } + } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) { + mInputConsumer = mDisplayContent.getInputMonitor().createInputConsumer( + mHandler.getLooper(), + INPUT_CONSUMER_NAVIGATION, + HideNavInputEventReceiver::new); + // As long as mInputConsumer is active, hover events are not dispatched to the app + // and the pointer icon is likely to become stale. Hide it to avoid confusion. + InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL); + } + } + private static void updateInsetsStateForDisplayCutout(DisplayFrames displayFrames, InsetsState state) { if (displayFrames.mDisplayCutout.getDisplayCutout().isEmpty()) { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index bb0278964f85..ac6e75c717ff 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -25,6 +25,7 @@ import static android.view.InsetsController.ANIMATION_TYPE_SHOW; 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.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; @@ -91,6 +92,12 @@ class InsetsPolicy { || focusedWin != getNavControlTarget(focusedWin) || focusedWin.getRequestedInsetsState().getSource(ITYPE_NAVIGATION_BAR) .isVisible()); + updateHideNavInputEventReceiver(); + } + + private void updateHideNavInputEventReceiver() { + mPolicy.updateHideNavInputEventReceiver(!isHidden(ITYPE_NAVIGATION_BAR), + mFocusedWin.mAttrs.insetsFlags.behavior != BEHAVIOR_SHOW_BARS_BY_TOUCH); } boolean isHidden(@InternalInsetsType int type) { @@ -169,6 +176,7 @@ class InsetsPolicy { if (windowState == getNavControlTarget(mFocusedWin)) { mNavBar.setVisible(state.getSource(ITYPE_NAVIGATION_BAR).isVisible()); } + updateHideNavInputEventReceiver(); } /** diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index bd5666dd9a27..244ba82ce32c 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -21,6 +21,7 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.ActivityManager.RECENT_WITH_EXCLUDED; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; @@ -1298,6 +1299,7 @@ class RecentTasks { switch (task.getActivityType()) { case ACTIVITY_TYPE_HOME: case ACTIVITY_TYPE_RECENTS: + case ACTIVITY_TYPE_DREAM: // Ignore certain activity types completely return false; case ACTIVITY_TYPE_ASSISTANT: diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java index 45f8a15979f4..420997a5b82d 100644 --- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java +++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java @@ -20,7 +20,6 @@ import static com.android.server.wm.ActivityStack.TAG_ADD_REMOVE; import static com.android.server.wm.ActivityStack.TAG_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS; -import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE; import android.app.ActivityOptions; import android.content.Intent; @@ -233,48 +232,42 @@ class ResetTargetTaskHelper { } final ActivityTaskManagerService atmService = mTargetStack.mAtmService; - final ArrayList<Task> createdTasks = new ArrayList<>(); + DisplayContent display = mTargetStack.getDisplay(); + final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance(); + if (singleTaskInstanceDisplay) { + display = atmService.mRootWindowContainer.getDefaultDisplay(); + } + + final int windowingMode = mTargetStack.getWindowingMode(); + final int activityType = mTargetStack.getActivityType(); + while (!mPendingReparentActivities.isEmpty()) { final ActivityRecord r = mPendingReparentActivities.remove(0); - final ActivityRecord bottom = mTargetStack.getBottomMostActivity(); - final Task targetTask; - if (bottom != null && r.taskAffinity.equals(bottom.getTask().affinity)) { + final boolean alwaysCreateTask = DisplayContent.alwaysCreateStack(windowingMode, + activityType); + final Task task = alwaysCreateTask + ? display.getBottomMostTask() : mTargetStack.getBottomMostTask(); + Task targetTask = null; + if (task != null && r.taskAffinity.equals(task.affinity)) { // If the activity currently at the bottom has the same task affinity as // the one we are moving, then merge it into the same task. - targetTask = bottom.getTask(); + targetTask = task; if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + r + " out to bottom task " + targetTask); - } else { - targetTask = mTargetStack.reuseOrCreateTask( - r.info, null /*intent*/, false /*toTop*/); + } + if (targetTask == null) { + if (alwaysCreateTask) { + targetTask = display.getOrCreateStack(windowingMode, activityType, + false /* onTop */); + } else { + targetTask = mTargetStack.reuseOrCreateTask(r.info, null /*intent*/, + false /*toTop*/); + } targetTask.affinityIntent = r.intent; - createdTasks.add(targetTask); - if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " - + r + " out to new task " + targetTask); } r.reparent(targetTask, 0 /* position */, "resetTargetTaskIfNeeded"); atmService.mStackSupervisor.mRecentTasks.add(targetTask); } - - DisplayContent display = mTargetStack.getDisplay(); - final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance(); - if (singleTaskInstanceDisplay) { - display = atmService.mRootWindowContainer.getDefaultDisplay(); - } - - final int windowingMode = mTargetStack.getWindowingMode(); - final int activityType = mTargetStack.getActivityType(); - if (!singleTaskInstanceDisplay && !display.alwaysCreateStack(windowingMode, activityType)) { - return; - } - - while (!createdTasks.isEmpty()) { - final Task targetTask = createdTasks.remove(createdTasks.size() - 1); - final ActivityStack targetStack = display.getOrCreateStack( - windowingMode, activityType, false /* onTop */); - targetTask.reparent(targetStack, false /* toTop */, REPARENT_LEAVE_STACK_IN_PLACE, - false /* animate */, true /* deferResume */, "resetTargetTask"); - } } private boolean takeOption(ActivityRecord p, boolean noOptions) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index d2ed48f36fab..b2920ee1d4aa 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -2919,6 +2920,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> case ACTIVITY_TYPE_HOME: return r.isActivityTypeHome(); case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents(); case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant(); + case ACTIVITY_TYPE_DREAM: return r.isActivityTypeDream(); } if (stack.mCreatedByOrganizer) { // Don't launch directly into task created by organizer...but why can't we? diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 7dd38e1c74b5..f19c10637c9b 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -20,6 +20,9 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP; @@ -3244,6 +3247,20 @@ class Task extends WindowContainer<WindowContainer> { } @Override + int getOrientation(int candidate) { + return canSpecifyOrientation() ? super.getOrientation(candidate) : SCREEN_ORIENTATION_UNSET; + } + + private boolean canSpecifyOrientation() { + final int windowingMode = getWindowingMode(); + final int activityType = getActivityType(); + return windowingMode == WINDOWING_MODE_FULLSCREEN + || activityType == ACTIVITY_TYPE_HOME + || activityType == ACTIVITY_TYPE_RECENTS + || activityType == ACTIVITY_TYPE_ASSISTANT; + } + + @Override boolean fillsParent() { return matchParentBounds(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 023a1e8ede9f..96cdbb300000 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -13070,26 +13070,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean addingProfileRestricted = mUserManager.hasUserRestriction( UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle); - UserInfo parentUser = mUserManager.getProfileParent(callingUserId); - final boolean addingProfileRestrictedOnParent = (parentUser != null) - && mUserManager.hasUserRestriction( - UserManager.DISALLOW_ADD_MANAGED_PROFILE, - UserHandle.of(parentUser.id)); - - Slog.i(LOG_TAG, String.format( - "When checking for managed profile provisioning: Has device owner? %b, adding" - + " profile restricted? %b, adding profile restricted on parent? %b", - hasDeviceOwner, addingProfileRestricted, addingProfileRestrictedOnParent)); - - // If there's a device owner, the restriction on adding a managed profile must be set - // somewhere. - if (hasDeviceOwner && !addingProfileRestricted && !addingProfileRestrictedOnParent) { + if (mUserManager.getUserInfo(callingUserId).isProfile()) { + Slog.i(LOG_TAG, + String.format("Calling user %d is a profile, cannot add another.", + callingUserId)); + // The check is called from inside a managed profile. A managed profile cannot + // be provisioned from within another managed profile. + return CODE_CANNOT_ADD_MANAGED_PROFILE; + } + + // If there's a device owner, the restriction on adding a managed profile must be set. + if (hasDeviceOwner && !addingProfileRestricted) { Slog.wtf(LOG_TAG, "Has a device owner but no restriction on adding a profile."); } - // Do not allow adding a managed profile if there's a restriction, either on the current - // user or its parent user. - if (addingProfileRestricted || addingProfileRestrictedOnParent) { + // Do not allow adding a managed profile if there's a restriction. + if (addingProfileRestricted) { + Slog.i(LOG_TAG, String.format( + "Adding a profile is restricted: User %s Has device owner? %b", + callingUserHandle, hasDeviceOwner)); return CODE_CANNOT_ADD_MANAGED_PROFILE; } // If there's a restriction on removing the managed profile then we have to take it @@ -13098,6 +13097,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { !mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, callingUserHandle); if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) { + Slog.i(LOG_TAG, String.format( + "Cannot add more profiles: Can remove current? %b", canRemoveProfile)); return CODE_CANNOT_ADD_MANAGED_PROFILE; } } finally { 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 7c6ac1717651..baf551e756e8 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -3205,6 +3205,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) .thenReturn(true); setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM); + when(getServices().userManager.getProfileParent(UserHandle.USER_SYSTEM)).thenReturn(null); mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; } @@ -3246,6 +3247,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) .thenReturn(true); setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM); + when(getServices().userManager.getProfileParent(UserHandle.USER_SYSTEM)).thenReturn(null); mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; } @@ -3617,14 +3619,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); - when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true); + when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false); when(getServices().userManager.getProfileParent(DpmMockContext.CALLER_USER_HANDLE)) .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0)); when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, true)).thenReturn(true); setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE); - mContext.binder.callingUid = DpmMockContext.CALLER_UID; + mContext.binder.callingUid = DpmMockContext.ANOTHER_UID; } public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_primaryUser() diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index e72f7df78162..56c19a47b68a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -27,6 +27,7 @@ import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.util.DisplayMetrics.DENSITY_DEFAULT; import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED; @@ -939,6 +940,20 @@ public class TaskRecordTests extends ActivityTestsBase { verify(persister, never()).saveTask(same(task), any()); } + @Test + public void testNotSpecifyOrientationByFloatingTask() { + final Task task = getTestTask(); + final ActivityRecord activity = task.getTopMostActivity(); + final WindowContainer<?> taskContainer = task.getParent(); + activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskContainer.getOrientation()); + + task.setWindowingMode(WINDOWING_MODE_PINNED); + + assertEquals(SCREEN_ORIENTATION_UNSET, taskContainer.getOrientation()); + } + private Task getTestTask() { final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); return stack.getBottomMostTask(); diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java index 488ee78f6230..47bf14892ccb 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java +++ b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java @@ -23,6 +23,9 @@ import android.util.Log; import com.android.server.wm.ActivityMetricsLaunchObserver; +import java.io.StringWriter; +import java.io.PrintWriter; + /** * A validator to check the correctness of event sequence during app startup. * @@ -100,7 +103,8 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @Override public void onIntentStarted(@NonNull Intent intent, long timestampNs) { if (state == State.UNKNOWN) { - Log.wtf(TAG, "IntentStarted during UNKNOWN." + intent); + logWarningWithStackTrace( + String.format("IntentStarted during UNKNOWN. " + intent)); incAccIntentStartedEvents(); return; } @@ -110,7 +114,7 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { state != State.ACTIVITY_CANCELLED && state != State.ACTIVITY_FINISHED && state != State.REPORT_FULLY_DRAWN) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED)); incAccIntentStartedEvents(); incAccIntentStartedEvents(); @@ -124,12 +128,12 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @Override public void onIntentFailed() { if (state == State.UNKNOWN) { - Log.wtf(TAG, "IntentFailed during UNKNOWN."); + logWarningWithStackTrace(String.format("onIntentFailed during UNKNOWN.")); decAccIntentStartedEvents(); return; } if (state != State.INTENT_STARTED) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED)); incAccIntentStartedEvents(); return; @@ -143,11 +147,12 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, @Temperature int temperature) { if (state == State.UNKNOWN) { - Log.wtf(TAG, "onActivityLaunched during UNKNOWN."); + logWarningWithStackTrace( + String.format("onActivityLaunched during UNKNOWN.")); return; } if (state != State.INTENT_STARTED) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED)); incAccIntentStartedEvents(); return; @@ -160,12 +165,13 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @Override public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) { if (state == State.UNKNOWN) { - Log.wtf(TAG, "onActivityLaunchCancelled during UNKNOWN."); + logWarningWithStackTrace( + String.format("onActivityLaunchCancelled during UNKNOWN.")); decAccIntentStartedEvents(); return; } if (state != State.ACTIVITY_LAUNCHED) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED)); incAccIntentStartedEvents(); return; @@ -179,13 +185,14 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity, long timestampNs) { if (state == State.UNKNOWN) { - Log.wtf(TAG, "onActivityLaunchFinished during UNKNOWN."); + logWarningWithStackTrace( + String.format("onActivityLaunchFinished during UNKNOWN.")); decAccIntentStartedEvents(); return; } if (state != State.ACTIVITY_LAUNCHED) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED)); incAccIntentStartedEvents(); return; @@ -199,7 +206,8 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity, long timestampNs) { if (state == State.UNKNOWN) { - Log.wtf(TAG, "onReportFullyDrawn during UNKNOWN."); + logWarningWithStackTrace( + String.format("onReportFullyDrawn during UNKNOWN.")); return; } if (state == State.INIT) { @@ -207,7 +215,7 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { } if (state != State.ACTIVITY_FINISHED) { - Log.wtf(TAG, + logWarningWithStackTrace( String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN)); return; } @@ -252,4 +260,11 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { Log.i(TAG, String.format("dec AccIntentStartedEvents to %d", accIntentStartedEvents)); } + + private void logWarningWithStackTrace(String log) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + new Throwable("EventSequenceValidator#getStackTrace").printStackTrace(pw); + Log.w(TAG, String.format("%s\n%s", log, sw)); + } } diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml index 269cec1ccca9..7b85cc84f1f5 100644 --- a/tests/RollbackTest/RollbackTest.xml +++ b/tests/RollbackTest/RollbackTest.xml @@ -23,6 +23,10 @@ <option name="run-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es package "com.google.android.gms.platformconfigurator" --es user '\\*' --esa flags "ModuleConfig__versioned_immediate_commit_packages" --esa types "bytes" --esa values "Cm5vdGFwYWNrYWdlOgA=" com.google.android.gms" /> <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package "com.google.android.gms.platformconfigurator" --es user '\*' --esa flag "ModuleConfig__immediate_commit_packages" com.google.android.gms" /> <option name="teardown-command" value="am broadcast -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' --es action delete --es package "com.google.android.gms.platformconfigurator" --es user '\*' --esa flag "ModuleConfig__versioned_immediate_commit_packages" com.google.android.gms" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.tests.rollback" /> diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 5a92d6849434..cab8b4258bc8 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -75,6 +75,12 @@ public class RollbackTest { private static final String PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS = "enable_rollback_timeout"; + private static boolean hasRollbackInclude(List<RollbackInfo> rollbacks, String packageName) { + return rollbacks.stream().anyMatch( + ri -> ri.getPackages().stream().anyMatch( + pri -> packageName.equals(pri.getPackageName()))); + } + /** * Test basic rollbacks. */ @@ -113,18 +119,14 @@ public class RollbackTest { // Uninstall TestApp.A Uninstall.packages(TestApp.A); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - // TODO: There is currently a race condition between when the app is - // uninstalled and when rollback manager deletes the rollback. Fix it - // so that's not the case! for (int i = 0; i < 5; ++i) { - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - if (rollback != null) { + if (hasRollbackInclude(rm.getRecentlyCommittedRollbacks(), TestApp.A)) { Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect."); Thread.sleep(1000); } } + assertThat(hasRollbackInclude(rm.getRecentlyCommittedRollbacks(), TestApp.A)).isFalse(); // The app should not be available for rollback. waitForUnavailableRollback(TestApp.A); diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index b2e8c378d96d..916c33981171 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -463,7 +463,9 @@ public class NetworkCapabilitiesTest { nc1.setSSID(TEST_SSID); nc2.combineCapabilities(nc1); - assertTrue(TEST_SSID.equals(nc2.getSsid())); + if (isAtLeastR()) { + assertTrue(TEST_SSID.equals(nc2.getSsid())); + } // Because they now have the same SSID, the following call should not throw nc2.combineCapabilities(nc1); @@ -601,12 +603,16 @@ public class NetworkCapabilitiesTest { // from nc2. assertFalse(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING)); - assertTrue(TEST_SSID.equals(nc2.getSsid())); + if (isAtLeastR()) { + assertTrue(TEST_SSID.equals(nc2.getSsid())); + } nc1.setSSID(DIFFERENT_TEST_SSID); nc2.set(nc1); assertEquals(nc1, nc2); - assertTrue(DIFFERENT_TEST_SSID.equals(nc2.getSsid())); + if (isAtLeastR()) { + assertTrue(DIFFERENT_TEST_SSID.equals(nc2.getSsid())); + } nc1.setUids(uidRange(10, 13)); nc2.set(nc1); // Overwrites, as opposed to combineCapabilities |