diff options
32 files changed, 1148 insertions, 209 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 4ab172d98d35..cd0bd8699794 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -40754,8 +40754,9 @@ package android.telecom { method public String getDefaultDialerPackage(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS) public java.util.List<android.telecom.PhoneAccountHandle> getOwnSelfManagedPhoneAccounts(); method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle); - method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_OWN_CALLS}) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts(); + method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts(); method public android.telecom.PhoneAccountHandle getSimCallManager(); method @Nullable public android.telecom.PhoneAccountHandle getSimCallManagerForSubscription(int); method @Nullable public String getSystemDialerPackage(); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index ebef0535f077..a03286d3ec6f 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2674,9 +2674,8 @@ public class Resources { // Putting into a map keyed on the apk assets to deduplicate resources that are different // objects but ultimately represent the same assets Map<List<ApkAssets>, Resources> history = new ArrayMap<>(); - for (Resources r : sResourcesHistory) { - history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r); - } + sResourcesHistory.forEach( + r -> history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r)); int i = 0; for (Resources r : history.values()) { if (r != null) { diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 5d9f2189df1b..8e5ed8f6e578 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -52,6 +52,9 @@ per-file *Power* = file:/services/core/java/com/android/server/power/OWNERS per-file *Telephony* = file:/telephony/OWNERS per-file *Zygote* = file:/ZYGOTE_OWNERS +# Time +per-file *Clock* = file:/services/core/java/com/android/server/timezonedetector/OWNERS + # RecoverySystem per-file *Recovery* = file:/services/core/java/com/android/server/recoverysystem/OWNERS diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 2e85b304ec47..31dd10a8ed53 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -66,6 +66,7 @@ import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.ECGenParameterSpec; +import java.security.spec.NamedParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; import java.util.ArrayList; import java.util.Arrays; @@ -119,36 +120,42 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private static final int RSA_MIN_KEY_SIZE = 512; private static final int RSA_MAX_KEY_SIZE = 8192; - private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE = + private static final Map<String, Integer> SUPPORTED_EC_CURVE_NAME_TO_SIZE = new HashMap<String, Integer>(); - private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>(); - private static final List<Integer> SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList<Integer>(); + private static final List<String> SUPPORTED_EC_CURVE_NAMES = new ArrayList<String>(); + private static final List<Integer> SUPPORTED_EC_CURVE_SIZES = new ArrayList<Integer>(); + private static final String CURVE_X_25519 = NamedParameterSpec.X25519.getName(); + private static final String CURVE_ED_25519 = NamedParameterSpec.ED25519.getName(); + static { // Aliases for NIST P-224 - SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224); - SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224); + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-224", 224); + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp224r1", 224); // Aliases for NIST P-256 - SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256); - SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256); - SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256); + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-256", 256); + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp256r1", 256); + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("prime256v1", 256); + // Aliases for Curve 25519 + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put(CURVE_X_25519.toLowerCase(Locale.US), 256); + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put(CURVE_ED_25519.toLowerCase(Locale.US), 256); // Aliases for NIST P-384 - SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384); - SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384); + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-384", 384); + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp384r1", 384); // Aliases for NIST P-521 - SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521); - SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521); + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("p-521", 521); + SUPPORTED_EC_CURVE_NAME_TO_SIZE.put("secp521r1", 521); - SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet()); - Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES); + SUPPORTED_EC_CURVE_NAMES.addAll(SUPPORTED_EC_CURVE_NAME_TO_SIZE.keySet()); + Collections.sort(SUPPORTED_EC_CURVE_NAMES); - SUPPORTED_EC_NIST_CURVE_SIZES.addAll( - new HashSet<Integer>(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values())); - Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES); + SUPPORTED_EC_CURVE_SIZES.addAll( + new HashSet<Integer>(SUPPORTED_EC_CURVE_NAME_TO_SIZE.values())); + Collections.sort(SUPPORTED_EC_CURVE_SIZES); } private final int mOriginalKeymasterAlgorithm; @@ -164,6 +171,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private int mKeySizeBits; private SecureRandom mRng; private KeyDescriptor mAttestKeyDescriptor; + private String mEcCurveName; private int[] mKeymasterPurposes; private int[] mKeymasterBlockModes; @@ -177,12 +185,15 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mOriginalKeymasterAlgorithm = keymasterAlgorithm; } - private @EcCurve int keySize2EcCurve(int keySizeBits) + private static @EcCurve int keySizeAndNameToEcCurve(int keySizeBits, String ecCurveName) throws InvalidAlgorithmParameterException { switch (keySizeBits) { case 224: return EcCurve.P_224; case 256: + if (isCurve25519(ecCurveName)) { + return EcCurve.CURVE_25519; + } return EcCurve.P_256; case 384: return EcCurve.P_384; @@ -247,7 +258,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato if (mKeySizeBits == -1) { mKeySizeBits = getDefaultKeySize(keymasterAlgorithm); } - checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked()); + checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked(), + mEcCurveName); if (spec.getKeystoreAlias() == null) { throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); @@ -299,6 +311,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec); checkAttestKeyPurpose(spec); + checkCorrectKeyPurposeForCurve(spec); success = true; } finally { @@ -317,6 +330,42 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } + private void checkCorrectKeyPurposeForCurve(KeyGenParameterSpec spec) + throws InvalidAlgorithmParameterException { + // Validate the key usage purposes against the curve. x25519 should be + // key exchange only, ed25519 signing and attesting. + + if (!isCurve25519(mEcCurveName)) { + return; + } + + if (mEcCurveName.equalsIgnoreCase(CURVE_X_25519) + && spec.getPurposes() != KeyProperties.PURPOSE_AGREE_KEY) { + throw new InvalidAlgorithmParameterException( + "x25519 may only be used for key agreement."); + } else if (mEcCurveName.equalsIgnoreCase(CURVE_ED_25519) + && !hasOnlyAllowedPurposeForEd25519(spec.getPurposes())) { + throw new InvalidAlgorithmParameterException( + "ed25519 may not be used for key agreement."); + } + } + + private static boolean isCurve25519(String ecCurveName) { + if (ecCurveName == null) { + return false; + } + return ecCurveName.equalsIgnoreCase(CURVE_X_25519) + || ecCurveName.equalsIgnoreCase(CURVE_ED_25519); + } + + private static boolean hasOnlyAllowedPurposeForEd25519(@KeyProperties.PurposeEnum int purpose) { + final int allowedPurposes = KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY + | KeyProperties.PURPOSE_ATTEST_KEY; + boolean hasAllowedPurpose = (purpose & allowedPurposes) != 0; + boolean hasDisallowedPurpose = (purpose & ~allowedPurposes) != 0; + return hasAllowedPurpose && !hasDisallowedPurpose; + } + private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec) throws InvalidAlgorithmParameterException { if (spec.getAttestKeyAlias() != null) { @@ -473,6 +522,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mRSAPublicExponent = null; mRng = null; mKeyStore = null; + mEcCurveName = null; } private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException { @@ -514,13 +564,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato case KeymasterDefs.KM_ALGORITHM_EC: if (algSpecificSpec instanceof ECGenParameterSpec) { ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec; - String curveName = ecSpec.getName(); - Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get( - curveName.toLowerCase(Locale.US)); + mEcCurveName = ecSpec.getName(); + final Integer ecSpecKeySizeBits = SUPPORTED_EC_CURVE_NAME_TO_SIZE.get( + mEcCurveName.toLowerCase(Locale.US)); if (ecSpecKeySizeBits == null) { throw new InvalidAlgorithmParameterException( - "Unsupported EC curve name: " + curveName - + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES); + "Unsupported EC curve name: " + mEcCurveName + + ". Supported: " + SUPPORTED_EC_CURVE_NAMES); } if (mKeySizeBits == -1) { mKeySizeBits = ecSpecKeySizeBits; @@ -744,7 +794,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) { params.add(KeyStore2ParameterUtils.makeEnum( - Tag.EC_CURVE, keySize2EcCurve(mKeySizeBits) + Tag.EC_CURVE, keySizeAndNameToEcCurve(mKeySizeBits, mEcCurveName) )); } @@ -864,7 +914,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private static void checkValidKeySize( int keymasterAlgorithm, int keySize, - boolean isStrongBoxBacked) + boolean isStrongBoxBacked, + String mEcCurveName) throws InvalidAlgorithmParameterException { switch (keymasterAlgorithm) { case KeymasterDefs.KM_ALGORITHM_EC: @@ -873,9 +924,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato "Unsupported StrongBox EC key size: " + keySize + " bits. Supported: 256"); } - if (!SUPPORTED_EC_NIST_CURVE_SIZES.contains(keySize)) { + if (isStrongBoxBacked && isCurve25519(mEcCurveName)) { + throw new InvalidAlgorithmParameterException( + "Unsupported StrongBox EC: " + mEcCurveName); + } + if (!SUPPORTED_EC_CURVE_SIZES.contains(keySize)) { throw new InvalidAlgorithmParameterException("Unsupported EC key size: " - + keySize + " bits. Supported: " + SUPPORTED_EC_NIST_CURVE_SIZES); + + keySize + " bits. Supported: " + SUPPORTED_EC_CURVE_SIZES); } break; case KeymasterDefs.KM_ALGORITHM_RSA: diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java index 72a145fa6a05..358104fffbf6 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -66,6 +66,11 @@ public class AndroidKeyStoreProvider extends Provider { private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede"; + // Conscrypt returns the Ed25519 OID as the JCA key algorithm. + private static final String ED25519_OID = "1.3.101.112"; + // Conscrypt returns "XDH" as the X25519 JCA key algorithm. + private static final String X25519_ALIAS = "XDH"; + /** @hide **/ public AndroidKeyStoreProvider() { super(PROVIDER_NAME, 1.0, "Android KeyStore security provider"); @@ -78,10 +83,16 @@ public class AndroidKeyStoreProvider extends Provider { // java.security.KeyPairGenerator put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC"); put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA"); + put("KeyPairGenerator." + X25519_ALIAS, + PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA"); + put("KeyPairGenerator." + ED25519_OID, + PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA"); // java.security.KeyFactory putKeyFactoryImpl("EC"); putKeyFactoryImpl("RSA"); + putKeyFactoryImpl(X25519_ALIAS); + putKeyFactoryImpl(ED25519_OID); // javax.crypto.KeyGenerator put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES"); @@ -219,12 +230,17 @@ public class AndroidKeyStoreProvider extends Provider { KeyStoreSecurityLevel securityLevel = iSecurityLevel; if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) { - return new AndroidKeyStoreECPublicKey(descriptor, metadata, iSecurityLevel, (ECPublicKey) publicKey); } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(jcaKeyAlgorithm)) { return new AndroidKeyStoreRSAPublicKey(descriptor, metadata, iSecurityLevel, (RSAPublicKey) publicKey); + } else if (ED25519_OID.equalsIgnoreCase(jcaKeyAlgorithm)) { + //TODO(b/214203951) missing classes in conscrypt + throw new ProviderException("Curve " + ED25519_OID + " not supported yet"); + } else if (X25519_ALIAS.equalsIgnoreCase(jcaKeyAlgorithm)) { + //TODO(b/214203951) missing classes in conscrypt + throw new ProviderException("Curve " + X25519_ALIAS + " not supported yet"); } else { throw new ProviderException("Unsupported Android Keystore public key algorithm: " + jcaKeyAlgorithm); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index c6a68dc98da6..58f79f3600de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1608,7 +1608,9 @@ public class BubbleStackView extends FrameLayout Log.d(TAG, "addBubble: " + bubble); } - if (getBubbleCount() == 0 && shouldShowStackEdu()) { + final boolean firstBubble = getBubbleCount() == 0; + + if (firstBubble && shouldShowStackEdu()) { // Override the default stack position if we're showing user education. mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition()); } @@ -1621,7 +1623,7 @@ public class BubbleStackView extends FrameLayout new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), mPositioner.getBubbleSize())); - if (getBubbleCount() == 0) { + if (firstBubble) { mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); } // Set the dot position to the opposite of the side the stack is resting on, since the stack @@ -1652,6 +1654,10 @@ public class BubbleStackView extends FrameLayout bubble.cleanupViews(); } updateExpandedView(); + if (getBubbleCount() == 0 && !isExpanded()) { + // This is the last bubble and the stack is collapsed + updateStackPosition(); + } logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); return; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 7e1fce298fbd..ebc766635733 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -19,6 +19,8 @@ package com.android.systemui.dreams; import android.content.Context; import android.graphics.drawable.ColorDrawable; import android.util.Log; +import android.view.View; +import android.view.ViewGroup; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; @@ -177,9 +179,26 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ } mDreamOverlayContainerViewController.init(); + // Make extra sure the container view has been removed from its old parent (otherwise we + // risk an IllegalStateException in some cases when setting the container view as the + // window's content view and the container view hasn't been properly removed previously). + removeContainerViewFromParent(); mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView()); final WindowManager windowManager = mContext.getSystemService(WindowManager.class); windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes()); } + + private void removeContainerViewFromParent() { + View containerView = mDreamOverlayContainerViewController.getContainerView(); + if (containerView == null) { + return; + } + ViewGroup parentView = (ViewGroup) containerView.getParent(); + if (parentView == null) { + return; + } + Log.w(TAG, "Removing dream overlay container view parent!"); + parentView.removeView(containerView); + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 582965a12528..35f29b94966f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -23,6 +23,7 @@ import android.content.Context import android.graphics.Matrix import android.graphics.Rect import android.os.Handler +import android.util.Log import android.view.RemoteAnimationTarget import android.view.SyncRtSurfaceTransactionApplier import android.view.View @@ -47,6 +48,8 @@ import dagger.Lazy import javax.inject.Inject import kotlin.math.min +const val TAG = "KeyguardUnlock" + /** * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating * in during keyguard exit. @@ -584,8 +587,16 @@ class KeyguardUnlockAnimationController @Inject constructor( * animation. */ fun hideKeyguardViewAfterRemoteAnimation() { - // Hide the keyguard, with no fade out since we animated it away during the unlock. - keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 0 /* fadeOutDuration */) + if (keyguardViewController.isShowing) { + // Hide the keyguard, with no fade out since we animated it away during the unlock. + keyguardViewController.hide( + surfaceBehindRemoteAnimationStartTime, + 0 /* fadeOutDuration */ + ) + } else { + Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " + + "showing. Ignoring...") + } } private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 9ea27634df6a..371e4f454a39 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -936,6 +936,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker public void setBackAnimation(BackAnimation backAnimation) { mBackAnimation = backAnimation; + if (mEdgeBackPlugin != null && mEdgeBackPlugin instanceof NavigationBarEdgePanel) { + ((NavigationBarEdgePanel) mEdgeBackPlugin).setBackAnimation(backAnimation); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java index a6bad15e0865..a6919e826d4f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java @@ -280,7 +280,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl } }; private BackCallback mBackCallback; - private final BackAnimation mBackAnimation; + private BackAnimation mBackAnimation; public NavigationBarEdgePanel(Context context, BackAnimation backAnimation) { @@ -385,6 +385,10 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl mShowProtection = !isPrimaryDisplay; } + public void setBackAnimation(BackAnimation backAnimation) { + mBackAnimation = backAnimation; + } + @Override public void onDestroy() { cancelFailsafe(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index 2860b50c2295..b3d54590dc99 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -28,6 +28,7 @@ import android.service.dreams.DreamService; import android.service.dreams.IDreamOverlay; import android.service.dreams.IDreamOverlayCallback; import android.testing.AndroidTestingRunner; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowManagerImpl; @@ -99,6 +100,9 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Mock DreamPreviewComplication mPreviewComplication; + @Mock + ViewGroup mDreamOverlayContainerViewParent; + DreamOverlayService mService; @Before @@ -152,6 +156,23 @@ public class DreamOverlayServiceTest extends SysuiTestCase { } @Test + public void testDreamOverlayContainerViewRemovedFromOldParentWhenInitialized() + throws Exception { + when(mDreamOverlayContainerView.getParent()) + .thenReturn(mDreamOverlayContainerViewParent) + .thenReturn(null); + + final IBinder proxy = mService.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + + // Inform the overlay service of dream starting. + overlay.startDream(mWindowParams, mDreamOverlayCallback); + mMainExecutor.runAllReady(); + + verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView); + } + + @Test public void testShouldShowComplicationsFalseByDefault() { mService.onBind(new Intent()); diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index 4129feb4e082..6ff8e36a1f82 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -21,14 +21,13 @@ per-file *AppOp* = file:/core/java/android/permission/OWNERS per-file *Battery* = file:/BATTERY_STATS_OWNERS per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS -per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS per-file *Storage* = file:/core/java/android/os/storage/OWNERS -per-file *TimeUpdate* = file:/core/java/android/app/timezone/OWNERS +per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS per-file MmsServiceBroker.java = file:/telephony/OWNERS diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index c15242a45482..140a28f111e6 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -144,6 +144,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.OptionalInt; /* * Wraps the C++ InputManager and provides its callbacks. @@ -2915,48 +2916,17 @@ public class InputManagerService extends IInputManager.Stub // Native callback @SuppressWarnings("unused") - private void notifyWindowUnresponsive(IBinder token, String reason) { - int gestureMonitorPid = -1; - synchronized (mInputMonitors) { - final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token); - if (gestureMonitor != null) { - gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid; - } - } - if (gestureMonitorPid != -1) { - mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(gestureMonitorPid, reason); - return; - } - mWindowManagerCallbacks.notifyWindowUnresponsive(token, reason); - } - - // Native callback - @SuppressWarnings("unused") - private void notifyMonitorUnresponsive(int pid, String reason) { - mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(pid, reason); + private void notifyWindowUnresponsive(IBinder token, int pid, boolean isPidValid, + String reason) { + mWindowManagerCallbacks.notifyWindowUnresponsive(token, + isPidValid ? OptionalInt.of(pid) : OptionalInt.empty(), reason); } // Native callback @SuppressWarnings("unused") - private void notifyWindowResponsive(IBinder token) { - int gestureMonitorPid = -1; - synchronized (mInputMonitors) { - final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token); - if (gestureMonitor != null) { - gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid; - } - } - if (gestureMonitorPid != -1) { - mWindowManagerCallbacks.notifyGestureMonitorResponsive(gestureMonitorPid); - return; - } - mWindowManagerCallbacks.notifyWindowResponsive(token); - } - - // Native callback - @SuppressWarnings("unused") - private void notifyMonitorResponsive(int pid) { - mWindowManagerCallbacks.notifyGestureMonitorResponsive(pid); + private void notifyWindowResponsive(IBinder token, int pid, boolean isPidValid) { + mWindowManagerCallbacks.notifyWindowResponsive(token, + isPidValid ? OptionalInt.of(pid) : OptionalInt.empty()); } // Native callback. @@ -3329,34 +3299,22 @@ public class InputManagerService extends IInputManager.Stub void notifyNoFocusedWindowAnr(InputApplicationHandle applicationHandle); /** - * Notify the window manager about a gesture monitor that is unresponsive. - * - * @param pid the pid of the gesture monitor process - * @param reason the reason why this connection is unresponsive - */ - void notifyGestureMonitorUnresponsive(int pid, @NonNull String reason); - - /** * Notify the window manager about a window that is unresponsive. * * @param token the token that can be used to look up the window + * @param pid the pid of the window owner, if known * @param reason the reason why this connection is unresponsive */ - void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull String reason); - - /** - * Notify the window manager about a gesture monitor that has become responsive. - * - * @param pid the pid of the gesture monitor process - */ - void notifyGestureMonitorResponsive(int pid); + void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid, + @NonNull String reason); /** * Notify the window manager about a window that has become responsive. * * @param token the token that can be used to look up the window + * @param pid the pid of the window owner, if known */ - void notifyWindowResponsive(@NonNull IBinder token); + void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid); /** * This callback is invoked when an event first arrives to InputDispatcher and before it is diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 49553f4c91f4..36633cc635e7 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -1021,7 +1021,8 @@ final class DefaultPermissionGrantPolicy { } for (String packageName : packageNames) { grantPermissionsToSystemPackage(NO_PM_CACHE, packageName, userId, - PHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, SMS_PERMISSIONS); + PHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, SMS_PERMISSIONS, + NOTIFICATION_PERMISSIONS); } } diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java index 3d54b27c819b..6befefda8a12 100644 --- a/services/core/java/com/android/server/wm/AnrController.java +++ b/services/core/java/com/android/server/wm/AnrController.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static com.android.server.wm.ActivityRecord.INVALID_PID; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.annotation.NonNull; import android.os.Build; import android.os.IBinder; import android.os.Process; @@ -35,6 +36,7 @@ import com.android.server.criticalevents.CriticalEventLog; import java.io.File; import java.util.ArrayList; +import java.util.OptionalInt; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -75,7 +77,33 @@ class AnrController { activity.inputDispatchingTimedOut(reason, INVALID_PID); } - void notifyWindowUnresponsive(IBinder inputToken, String reason) { + + /** + * Notify a window was unresponsive. + * + * @param token - the input token of the window + * @param pid - the pid of the window, if known + * @param reason - the reason for the window being unresponsive + */ + void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid, + @NonNull String reason) { + if (notifyWindowUnresponsive(token, reason)) { + return; + } + if (!pid.isPresent()) { + Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was unresponsive."); + return; + } + notifyWindowUnresponsive(pid.getAsInt(), reason); + } + + /** + * Notify a window identified by its input token was unresponsive. + * + * @return true if the window was identified by the given input token and the request was + * handled, false otherwise. + */ + private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken, String reason) { preDumpIfLockTooSlow(); final int pid; final boolean aboveSystem; @@ -83,10 +111,8 @@ class AnrController { synchronized (mService.mGlobalLock) { InputTarget target = mService.getInputTargetFromToken(inputToken); if (target == null) { - Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request"); - return; + return false; } - WindowState windowState = target.getWindowState(); pid = target.getPid(); // Blame the activity if the input token belongs to the window. If the target is @@ -102,34 +128,63 @@ class AnrController { } else { mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, reason); } + return true; + } + + /** + * Notify a window owned by the provided pid was unresponsive. + */ + private void notifyWindowUnresponsive(int pid, String reason) { + Slog.i(TAG_WM, "ANR in input window owned by pid=" + pid + ". Reason: " + reason); + dumpAnrStateLocked(null /* activity */, null /* windowState */, reason); + + // We cannot determine the z-order of the window, so place the anr dialog as high + // as possible. + mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, reason); + } + + /** + * Notify a window was responsive after previously being unresponsive. + * + * @param token - the input token of the window + * @param pid - the pid of the window, if known + */ + void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid) { + if (notifyWindowResponsive(token)) { + return; + } + if (!pid.isPresent()) { + Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was responsive."); + return; + } + notifyWindowResponsive(pid.getAsInt()); } - void notifyWindowResponsive(IBinder inputToken) { + /** + * Notify a window identified by its input token was responsive after previously being + * unresponsive. + * + * @return true if the window was identified by the given input token and the request was + * handled, false otherwise. + */ + private boolean notifyWindowResponsive(@NonNull IBinder inputToken) { final int pid; synchronized (mService.mGlobalLock) { InputTarget target = mService.getInputTargetFromToken(inputToken); if (target == null) { - Slog.e(TAG_WM, "Unknown token, dropping notifyWindowConnectionResponsive request"); - return; + return false; } pid = target.getPid(); } mService.mAmInternal.inputDispatchingResumed(pid); + return true; } - void notifyGestureMonitorUnresponsive(int gestureMonitorPid, String reason) { - preDumpIfLockTooSlow(); - synchronized (mService.mGlobalLock) { - Slog.i(TAG_WM, "ANR in gesture monitor owned by pid:" + gestureMonitorPid - + ". Reason: " + reason); - dumpAnrStateLocked(null /* activity */, null /* windowState */, reason); - } - mService.mAmInternal.inputDispatchingTimedOut(gestureMonitorPid, /* aboveSystem */ true, - reason); - } - - void notifyGestureMonitorResponsive(int gestureMonitorPid) { - mService.mAmInternal.inputDispatchingResumed(gestureMonitorPid); + /** + * Notify a window owned by the provided pid was responsive after previously being unresponsive. + */ + private void notifyWindowResponsive(int pid) { + mService.mAmInternal.inputDispatchingResumed(pid); } /** @@ -228,12 +283,7 @@ class AnrController { mService.mAtmService.saveANRState(reason); } - private boolean isWindowAboveSystem(WindowState windowState) { - if (windowState == null) { - // If the window state is not available we cannot easily determine its z order. Try to - // place the anr dialog as high as possible. - return true; - } + private boolean isWindowAboveSystem(@NonNull WindowState windowState) { int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw( TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow); return windowState.mBaseLayer > systemAlertLayer; diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 1f0fdcf0a8d2..8d1425d17d47 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -39,6 +39,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.input.InputManagerService; import java.io.PrintWriter; +import java.util.OptionalInt; final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks { private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM; @@ -98,23 +99,14 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } @Override - public void notifyGestureMonitorUnresponsive(int pid, @NonNull String reason) { - mService.mAnrController.notifyGestureMonitorUnresponsive(pid, reason); + public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid, + @NonNull String reason) { + mService.mAnrController.notifyWindowUnresponsive(token, pid, reason); } @Override - public void notifyWindowUnresponsive(@NonNull IBinder token, String reason) { - mService.mAnrController.notifyWindowUnresponsive(token, reason); - } - - @Override - public void notifyGestureMonitorResponsive(int pid) { - mService.mAnrController.notifyGestureMonitorResponsive(pid); - } - - @Override - public void notifyWindowResponsive(@NonNull IBinder token) { - mService.mAnrController.notifyWindowResponsive(token); + public void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid) { + mService.mAnrController.notifyWindowResponsive(token, pid); } /** Notifies that the input device configuration has changed. */ diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 3c122b03d0d1..31b557949e36 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -96,8 +96,6 @@ static struct { jmethodID notifyNoFocusedWindowAnr; jmethodID notifyWindowUnresponsive; jmethodID notifyWindowResponsive; - jmethodID notifyMonitorUnresponsive; - jmethodID notifyMonitorResponsive; jmethodID notifyFocusChanged; jmethodID notifySensorEvent; jmethodID notifySensorAccuracy; @@ -308,10 +306,9 @@ public: void notifyConfigurationChanged(nsecs_t when) override; // ANR-related callbacks -- start void notifyNoFocusedWindowAnr(const std::shared_ptr<InputApplicationHandle>& handle) override; - void notifyWindowUnresponsive(const sp<IBinder>& token, const std::string& reason) override; - void notifyWindowResponsive(const sp<IBinder>& token) override; - void notifyMonitorUnresponsive(int32_t pid, const std::string& reason) override; - void notifyMonitorResponsive(int32_t pid) override; + void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<int32_t> pid, + const std::string& reason) override; + void notifyWindowResponsive(const sp<IBinder>& token, std::optional<int32_t> pid) override; // ANR-related callbacks -- end void notifyInputChannelBroken(const sp<IBinder>& token) override; void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken) override; @@ -838,6 +835,7 @@ void NativeInputManager::notifyNoFocusedWindowAnr( } void NativeInputManager::notifyWindowUnresponsive(const sp<IBinder>& token, + std::optional<int32_t> pid, const std::string& reason) { #if DEBUG_INPUT_DISPATCHER_POLICY ALOGD("notifyWindowUnresponsive"); @@ -851,11 +849,12 @@ void NativeInputManager::notifyWindowUnresponsive(const sp<IBinder>& token, ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str())); env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowUnresponsive, tokenObj, - reasonObj.get()); + pid.value_or(0), pid.has_value(), reasonObj.get()); checkAndClearExceptionFromCallback(env, "notifyWindowUnresponsive"); } -void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token) { +void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token, + std::optional<int32_t> pid) { #if DEBUG_INPUT_DISPATCHER_POLICY ALOGD("notifyWindowResponsive"); #endif @@ -866,39 +865,11 @@ void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token) { jobject tokenObj = javaObjectForIBinder(env, token); - env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowResponsive, tokenObj); + env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowResponsive, tokenObj, + pid.value_or(0), pid.has_value()); checkAndClearExceptionFromCallback(env, "notifyWindowResponsive"); } -void NativeInputManager::notifyMonitorUnresponsive(int32_t pid, const std::string& reason) { -#if DEBUG_INPUT_DISPATCHER_POLICY - ALOGD("notifyMonitorUnresponsive"); -#endif - ATRACE_CALL(); - - JNIEnv* env = jniEnv(); - ScopedLocalFrame localFrame(env); - - ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str())); - - env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyMonitorUnresponsive, pid, - reasonObj.get()); - checkAndClearExceptionFromCallback(env, "notifyMonitorUnresponsive"); -} - -void NativeInputManager::notifyMonitorResponsive(int32_t pid) { -#if DEBUG_INPUT_DISPATCHER_POLICY - ALOGD("notifyMonitorResponsive"); -#endif - ATRACE_CALL(); - - JNIEnv* env = jniEnv(); - ScopedLocalFrame localFrame(env); - - env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyMonitorResponsive, pid); - checkAndClearExceptionFromCallback(env, "notifyMonitorResponsive"); -} - void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) { #if DEBUG_INPUT_DISPATCHER_POLICY ALOGD("notifyInputChannelBroken"); @@ -2506,16 +2477,10 @@ int register_android_server_InputManager(JNIEnv* env) { "(Landroid/view/InputApplicationHandle;)V"); GET_METHOD_ID(gServiceClassInfo.notifyWindowUnresponsive, clazz, "notifyWindowUnresponsive", - "(Landroid/os/IBinder;Ljava/lang/String;)V"); - - GET_METHOD_ID(gServiceClassInfo.notifyMonitorUnresponsive, clazz, "notifyMonitorUnresponsive", - "(ILjava/lang/String;)V"); + "(Landroid/os/IBinder;IZLjava/lang/String;)V"); GET_METHOD_ID(gServiceClassInfo.notifyWindowResponsive, clazz, "notifyWindowResponsive", - "(Landroid/os/IBinder;)V"); - - GET_METHOD_ID(gServiceClassInfo.notifyMonitorResponsive, clazz, "notifyMonitorResponsive", - "(I)V"); + "(Landroid/os/IBinder;IZ)V"); GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz, "filterInputEvent", "(Landroid/view/InputEvent;I)Z"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 28bf0d239b26..5cda9ea66999 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -8610,20 +8610,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { admin.getPackageName(), userId, "set-device-owner"); Slogf.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId); + } - if (setProfileOwnerOnCurrentUserIfNecessary - && mInjector.userManagerIsHeadlessSystemUserMode()) { - int currentForegroundUser = getCurrentForegroundUserId(); - Slogf.i(LOG_TAG, "setDeviceOwner(): setting " + admin - + " as profile owner on user " + currentForegroundUser); - // Sets profile owner on current foreground user since - // the human user will complete the DO setup workflow from there. - manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin, - /* managedUser= */ currentForegroundUser, /* adminExtras= */ null, - /* showDisclaimer= */ false); + if (setProfileOwnerOnCurrentUserIfNecessary + && mInjector.userManagerIsHeadlessSystemUserMode()) { + int currentForegroundUser; + synchronized (getLockObject()) { + currentForegroundUser = getCurrentForegroundUserId(); } - return true; + Slogf.i(LOG_TAG, "setDeviceOwner(): setting " + admin + + " as profile owner on user " + currentForegroundUser); + // Sets profile owner on current foreground user since + // the human user will complete the DO setup workflow from there. + manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin, + /* managedUser= */ currentForegroundUser, /* adminExtras= */ null, + /* showDisclaimer= */ false); } + return true; } @Override diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index f43e5aacad33..df114dbabe5b 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1292,31 +1292,22 @@ public class TelecomManager { } /** - * Returns a list of {@link PhoneAccountHandle}s for self-managed {@link ConnectionService}s. + * Returns a list of {@link PhoneAccountHandle}s for all self-managed + * {@link ConnectionService}s owned by the calling {@link UserHandle}. * <p> * Self-Managed {@link ConnectionService}s have a {@link PhoneAccount} with * {@link PhoneAccount#CAPABILITY_SELF_MANAGED}. * <p> * Requires permission {@link android.Manifest.permission#READ_PHONE_STATE}, or that the caller - * is the default dialer app to get all phone account handles. - * <P> - * If the caller doesn't meet any of the above requirements and has {@link - * android.Manifest.permission#MANAGE_OWN_CALLS}, the caller can get only the phone account - * handles they have registered. + * is the default dialer app. * <p> - * A {@link SecurityException} will be thrown if the caller is not the default dialer - * or the caller does not have at least one of the following permissions: - * {@link android.Manifest.permission#READ_PHONE_STATE} permission, - * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission + * A {@link SecurityException} will be thrown if a called is not the default dialer, or lacks + * the {@link android.Manifest.permission#READ_PHONE_STATE} permission. * * @return A list of {@code PhoneAccountHandle} objects. */ - @RequiresPermission(anyOf = { - READ_PRIVILEGED_PHONE_STATE, - android.Manifest.permission.READ_PHONE_STATE, - android.Manifest.permission.MANAGE_OWN_CALLS - }) - public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() { + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public @NonNull List<PhoneAccountHandle> getSelfManagedPhoneAccounts() { ITelecomService service = getTelecomService(); if (service != null) { try { @@ -1330,6 +1321,34 @@ public class TelecomManager { } /** + * Returns a list of {@link PhoneAccountHandle}s owned by the calling self-managed + * {@link ConnectionService}. + * <p> + * Self-Managed {@link ConnectionService}s have a {@link PhoneAccount} with + * {@link PhoneAccount#CAPABILITY_SELF_MANAGED}. + * <p> + * Requires permission {@link android.Manifest.permission#MANAGE_OWN_CALLS} + * <p> + * A {@link SecurityException} will be thrown if a caller lacks the + * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission. + * + * @return A list of {@code PhoneAccountHandle} objects. + */ + @RequiresPermission(Manifest.permission.MANAGE_OWN_CALLS) + public @NonNull List<PhoneAccountHandle> getOwnSelfManagedPhoneAccounts() { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + return service.getOwnSelfManagedPhoneAccounts(mContext.getOpPackageName(), + mContext.getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + throw new IllegalStateException("Telecom is not available"); + } + + /** * Returns a list of {@link PhoneAccountHandle}s including those which have not been enabled * by the user. * diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index b9936ce2e1b2..9999c897d1b6 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -66,6 +66,12 @@ interface ITelecomService { String callingFeatureId); /** + * @see TelecomServiceImpl#getOwnSelfManagedPhoneAccounts + */ + List<PhoneAccountHandle> getOwnSelfManagedPhoneAccounts(String callingPackage, + String callingFeatureId); + + /** * @see TelecomManager#getPhoneAccountsSupportingScheme */ List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(in String uriScheme, diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp new file mode 100644 index 000000000000..c9c6c5cf193b --- /dev/null +++ b/tests/TrustTests/Android.bp @@ -0,0 +1,39 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "TrustTests", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "androidx.test.rules", + "androidx.test.ext.junit", + "androidx.test.uiautomator", + "truth-prebuilt", + ], + libs: [ + "android.test.runner", + "android.test.base", + ], + test_suites: [ + "device-tests", + ], + platform_apis: true, + certificate: "platform", +} diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml new file mode 100644 index 000000000000..c94152da2bf6 --- /dev/null +++ b/tests/TrustTests/AndroidManifest.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.trust.test" + android:targetSandboxVersion="2"> + + <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> + <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" /> + <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> + <uses-permission android:name="android.permission.DEVICE_POWER" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" /> + <uses-permission android:name="android.permission.TRUST_LISTENER" /> + + <application> + <uses-library android:name="android.test.runner"/> + <activity android:name="android.trust.TrustTestActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + + <service + android:name=".UserUnlockRequestTrustAgent" + android:exported="true" + android:label="Test Agent" + android:permission="android.permission.BIND_TRUST_AGENT"> + <intent-filter> + <action android:name="android.service.trust.TrustAgentService" /> + </intent-filter> + </service> + + <service + android:name=".LockUserTrustAgent" + android:exported="true" + android:label="Test Agent" + android:permission="android.permission.BIND_TRUST_AGENT"> + <intent-filter> + <action android:name="android.service.trust.TrustAgentService" /> + </intent-filter> + </service> + + <service + android:name=".GrantAndRevokeTrustAgent" + android:exported="true" + android:label="Test Agent" + android:permission="android.permission.BIND_TRUST_AGENT"> + <intent-filter> + <action android:name="android.service.trust.TrustAgentService" /> + </intent-filter> + </service> + </application> + + <!-- self-instrumenting test package. --> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.trust.test"> + </instrumentation> +</manifest> diff --git a/tests/TrustTests/AndroidTest.xml b/tests/TrustTests/AndroidTest.xml new file mode 100644 index 000000000000..61b711eb7273 --- /dev/null +++ b/tests/TrustTests/AndroidTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="TrustTests configuration"> + <option name="test-tag" value="TrustTests" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="TrustTests.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.trust.test" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false" /> + </test> +</configuration> diff --git a/tests/TrustTests/README.md b/tests/TrustTests/README.md new file mode 100644 index 000000000000..3427e30536d9 --- /dev/null +++ b/tests/TrustTests/README.md @@ -0,0 +1,40 @@ +# TrustTests framework tests + +These tests test the "trust" part of the platform primarily implemented via TrustManagerService in +the system server and TrustAgentService in system apps. + +Tests are separated into separate files based on major groupings. When creating new tests, find a +_closely_ matching existing test file or create a new test file. Prefer many test files over large +test files. + +Each test file has its own trust agent. To create a new trust agent: + +1. Create a new class extending from `BaseTrustAgentService` class in your test file +2. Add a new `<service>` stanza to `AndroidManifest.xml` in this directory for the new agent + following the pattern fo the existing agents. + +To run: + +```atest TrustTests``` + +## Testing approach: + +1. Test the agent service as a black box; avoid inspecting internal state of the service or + modifying the system code outside of this directory. +2. The primary interface to the system is through these three points: + 1. `TrustAgentService`, your agent created by the `TrustAgentRule` and accessible via + the `agent` property of the rule. + 1. Call command methods (e.g. `grantTrust`) directly on the agent + 2. Listen to events (e.g. `onUserRequestedUnlock`) by implementing the method in + your test's agent class and tracking invocations. See `UserUnlockRequestTest` for an + example. + 2. `TrustManager` which is the interface the rest of the system (e.g. SystemUI) has to the + service. + 1. Through this API, simulate system events that the service cares about + (e.g. `reportUnlockAttempt`). + 3. `TrustListener` which is the interface the rest of the system (e.g. SystemUI) uses to receive + events from the service. + 1. Through this, verify behavior that affects the rest of the system. For example, + see `LockStateTrackingRule`. +3. To re-use code between tests, prefer creating new rules alongside the existing rules or adding + functionality to a _closely_ matching existing rule. diff --git a/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt new file mode 100644 index 000000000000..493f3bd22d2b --- /dev/null +++ b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.trust + +import android.service.trust.TrustAgentService +import android.util.Log +import kotlin.reflect.KClass + +/** + * Base class for test trust agents. + */ +abstract class BaseTrustAgentService : TrustAgentService() { + + override fun onCreate() { + super.onCreate() + Log.d(TAG, "${this::class.simpleName} created") + instances[this::class] = this + } + + override fun onDestroy() { + super.onDestroy() + instances.remove(this::class) + } + + companion object { + private val instances = + mutableMapOf<KClass<out BaseTrustAgentService>, BaseTrustAgentService>() + private const val TAG = "BaseTrustAgentService" + + fun instance(serviceClass: KClass<out BaseTrustAgentService>): BaseTrustAgentService? { + return instances[serviceClass]!! + } + } +} diff --git a/tests/TrustTests/src/android/trust/TrustTestActivity.kt b/tests/TrustTests/src/android/trust/TrustTestActivity.kt new file mode 100644 index 000000000000..6c56feace3d7 --- /dev/null +++ b/tests/TrustTests/src/android/trust/TrustTestActivity.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.trust + +import android.app.Activity +import android.os.Bundle + +/** + * Activity for testing Trust. + */ +class TrustTestActivity : Activity() { + + public override fun onCreate(icicle: Bundle?) { + super.onCreate(icicle) + setTurnScreenOn(true) + } +} diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt new file mode 100644 index 000000000000..790afd389152 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.trust.test + +import android.trust.BaseTrustAgentService +import android.trust.TrustTestActivity +import android.trust.test.lib.LockStateTrackingRule +import android.trust.test.lib.ScreenLockRule +import android.trust.test.lib.TrustAgentRule +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.uiautomator.UiDevice +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +/** + * Test for testing revokeTrust & grantTrust for non-renewable trust. + * + * atest TrustTests:GrantAndRevokeTrustTest + */ +@RunWith(AndroidJUnit4::class) +class GrantAndRevokeTrustTest { + private val uiDevice = UiDevice.getInstance(getInstrumentation()) + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val lockStateTrackingRule = LockStateTrackingRule() + private val trustAgentRule = TrustAgentRule<GrantAndRevokeTrustAgent>() + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(lockStateTrackingRule) + .around(trustAgentRule) + + @Before + fun manageTrust() { + trustAgentRule.agent.setManagingTrust(true) + } + + // This test serves a baseline for Grant tests, verifying that the default behavior of the + // device is to lock when put to sleep + @Test + fun sleepingDeviceWithoutGrantLocksDevice() { + uiDevice.sleep() + await() + + lockStateTrackingRule.assertLocked() + } + + @Test + fun grantKeepsDeviceUnlocked() { + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) + uiDevice.sleep() + await() + + lockStateTrackingRule.assertUnlocked() + } + + @Test + fun grantKeepsDeviceUnlocked_untilRevoked() { + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0) + await() + uiDevice.sleep() + trustAgentRule.agent.revokeTrust() + await() + + lockStateTrackingRule.assertLocked() + } + + companion object { + private const val TAG = "GrantAndRevokeTrustTest" + private const val GRANT_MESSAGE = "granted by test" + private fun await() = Thread.sleep(250) + } +} + +class GrantAndRevokeTrustAgent : BaseTrustAgentService() diff --git a/tests/TrustTests/src/android/trust/test/LockUserTest.kt b/tests/TrustTests/src/android/trust/test/LockUserTest.kt new file mode 100644 index 000000000000..83fc28fee818 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/LockUserTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.trust.test + +import android.trust.BaseTrustAgentService +import android.trust.TrustTestActivity +import android.trust.test.lib.LockStateTrackingRule +import android.trust.test.lib.ScreenLockRule +import android.trust.test.lib.TrustAgentRule +import android.util.Log +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +/** + * Test for testing lockUser. + * + * atest TrustTests:LockUserTest + */ +@RunWith(AndroidJUnit4::class) +class LockUserTest { + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val lockStateTrackingRule = LockStateTrackingRule() + private val trustAgentRule = TrustAgentRule<LockUserTrustAgent>() + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(lockStateTrackingRule) + .around(trustAgentRule) + + @Ignore("Causes issues with subsequent tests") // TODO: Enable test + @Test + fun lockUser_locksTheDevice() { + Log.i(TAG, "Locking user") + trustAgentRule.agent.lockUser() + await() + + assertThat(lockStateTrackingRule.lockState.locked).isTrue() + } + + companion object { + private const val TAG = "LockUserTest" + private fun await() = Thread.sleep(250) + } +} + +class LockUserTrustAgent : BaseTrustAgentService() diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt new file mode 100644 index 000000000000..f8783fbaf121 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.trust.test + +import android.app.trust.TrustManager +import android.content.Context +import android.trust.BaseTrustAgentService +import android.trust.TrustTestActivity +import android.trust.test.lib.ScreenLockRule +import android.trust.test.lib.TrustAgentRule +import android.util.Log +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +/** + * Test for testing the user unlock trigger. + * + * atest TrustTests:UserUnlockRequestTest + */ +@RunWith(AndroidJUnit4::class) +class UserUnlockRequestTest { + private val context: Context = getApplicationContext() + private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager + private val userId = context.userId + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val trustAgentRule = TrustAgentRule<UserUnlockRequestTrustAgent>() + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(trustAgentRule) + + @Test + fun reportUserRequestedUnlock_propagatesToAgent() { + val oldCount = trustAgentRule.agent.onUserRequestedUnlockCallCount + trustManager.reportUserRequestedUnlock(userId) + await() + + assertThat(trustAgentRule.agent.onUserRequestedUnlockCallCount) + .isEqualTo(oldCount + 1) + } + + companion object { + private const val TAG = "UserUnlockRequestTest" + private fun await() = Thread.sleep(250) + } +} + +class UserUnlockRequestTrustAgent : BaseTrustAgentService() { + var onUserRequestedUnlockCallCount: Long = 0 + private set + + override fun onUserRequestedUnlock() { + Log.i(TAG, "onUserRequestedUnlock") + onUserRequestedUnlockCallCount++ + } + + companion object { + private const val TAG = "UserUnlockRequestTrustAgent" + } +} diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt new file mode 100644 index 000000000000..0023af8893e2 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.trust.test.lib + +import android.app.trust.TrustManager +import android.app.trust.TrustManager.TrustListener +import android.content.Context +import android.util.Log +import android.view.WindowManagerGlobal +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import com.google.common.truth.Truth.assertThat +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Rule for tracking the lock state of the device based on events emitted to [TrustListener]. + */ +class LockStateTrackingRule : TestRule { + private val context: Context = getApplicationContext() + private val windowManager = WindowManagerGlobal.getWindowManagerService() + + @Volatile lateinit var lockState: LockState + private set + + override fun apply(base: Statement, description: Description) = object : Statement() { + override fun evaluate() { + lockState = LockState(locked = windowManager.isKeyguardLocked) + val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager + val listener = Listener() + + trustManager.registerTrustListener(listener) + try { + base.evaluate() + } finally { + trustManager.unregisterTrustListener(listener) + } + } + } + + fun assertLocked() = assertThat(lockState.locked).isTrue() + fun assertUnlocked() = assertThat(lockState.locked).isFalse() + + inner class Listener : TrustListener { + override fun onTrustChanged( + enabled: Boolean, + userId: Int, + flags: Int, + trustGrantedMessages: MutableList<String> + ) { + Log.d(TAG, "Device became trusted=$enabled") + lockState = lockState.copy(locked = !enabled) + } + + override fun onTrustManagedChanged(enabled: Boolean, userId: Int) { + } + + override fun onTrustError(message: CharSequence) { + } + } + + data class LockState( + val locked: Boolean? = null + ) + + companion object { + private const val TAG = "LockStateTrackingRule" + } +} diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt new file mode 100644 index 000000000000..c682a00eb8b9 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.trust.test.lib + +import android.content.Context +import android.util.Log +import android.view.WindowManagerGlobal +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockscreenCredential +import com.google.common.truth.Truth.assertWithMessage +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Sets a screen lock on the device for the duration of the test. + */ +class ScreenLockRule : TestRule { + private val context: Context = getApplicationContext() + private val windowManager = WindowManagerGlobal.getWindowManagerService() + private val lockPatternUtils = LockPatternUtils(context) + private var instantLockSavedValue = false + + override fun apply(base: Statement, description: Description) = object : Statement() { + override fun evaluate() { + verifyNoScreenLockAlreadySet() + verifyKeyguardDismissed() + setScreenLock() + setLockOnPowerButton() + + try { + base.evaluate() + } finally { + removeScreenLock() + revertLockOnPowerButton() + } + } + } + + private fun verifyNoScreenLockAlreadySet() { + assertWithMessage("Screen Lock must not already be set on device") + .that(lockPatternUtils.isSecure(context.userId)) + .isFalse() + } + + private fun verifyKeyguardDismissed() { + windowManager.dismissKeyguard(null, null) + Thread.sleep(250) + assertWithMessage("Keyguard should be unlocked") + .that(windowManager.isKeyguardLocked) + .isFalse() + } + + private fun setScreenLock() { + lockPatternUtils.setLockCredential( + LockscreenCredential.createPin(PIN), + LockscreenCredential.createNone(), + context.userId + ) + assertWithMessage("Screen Lock should now be set") + .that(lockPatternUtils.isSecure(context.userId)) + .isTrue() + Log.i(TAG, "Device PIN set to $PIN") + } + + private fun setLockOnPowerButton() { + instantLockSavedValue = lockPatternUtils.getPowerButtonInstantlyLocks(context.userId) + lockPatternUtils.setPowerButtonInstantlyLocks(true, context.userId) + } + + private fun removeScreenLock() { + lockPatternUtils.setLockCredential( + LockscreenCredential.createNone(), + LockscreenCredential.createPin(PIN), + context.userId + ) + Log.i(TAG, "Device PIN cleared; waiting 50 ms then dismissing Keyguard") + Thread.sleep(50) + windowManager.dismissKeyguard(null, null) + } + + private fun revertLockOnPowerButton() { + lockPatternUtils.setPowerButtonInstantlyLocks(instantLockSavedValue, context.userId) + } + + companion object { + private const val TAG = "ScreenLockRule" + private const val PIN = "0000" + } +} diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt new file mode 100644 index 000000000000..2a9e00276475 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.trust.test.lib + +import android.app.trust.TrustManager +import android.content.ComponentName +import android.content.Context +import android.trust.BaseTrustAgentService +import android.util.Log +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import com.android.internal.widget.LockPatternUtils +import com.google.common.truth.Truth.assertWithMessage +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import kotlin.reflect.KClass + +/** + * Enables a trust agent and causes the system service to bind to it. + * + * The enabled agent can be accessed during the test via the [agent] property. + * + * @constructor Creates the rule. Do not use; instead, use [invoke]. + */ +class TrustAgentRule<T : BaseTrustAgentService>( + private val serviceClass: KClass<T> +) : TestRule { + private val context: Context = getApplicationContext() + private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager + private val lockPatternUtils = LockPatternUtils(context) + + val agent get() = BaseTrustAgentService.instance(serviceClass) as T + + override fun apply(base: Statement, description: Description) = object : Statement() { + override fun evaluate() { + verifyTrustServiceRunning() + unlockDeviceWithCredential() + enableTrustAgent() + waitForEnablement() + + try { + verifyAgentIsRunning() + base.evaluate() + } finally { + disableTrustAgent() + } + } + } + + private fun verifyTrustServiceRunning() { + assertWithMessage("Trust service is not running").that(trustManager).isNotNull() + } + + private fun unlockDeviceWithCredential() { + Log.d(TAG, "Unlocking device with credential") + trustManager.reportUnlockAttempt(true, context.userId) + } + + private fun enableTrustAgent() { + val componentName = ComponentName(context, serviceClass.java) + val userId = context.userId + Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId") + val agents = mutableListOf(componentName) + .plus(lockPatternUtils.getEnabledTrustAgents(userId)) + .distinct() + lockPatternUtils.setEnabledTrustAgents(agents, userId) + } + + private fun waitForEnablement() { + Log.d(TAG, "Waiting for $WAIT_TIME ms") + Thread.sleep(WAIT_TIME) + Log.d(TAG, "Done waiting") + } + + private fun verifyAgentIsRunning() { + assertWithMessage("${serviceClass.simpleName} should be running") + .that(BaseTrustAgentService.instance(serviceClass)).isNotNull() + } + + private fun disableTrustAgent() { + val componentName = ComponentName(context, serviceClass.java) + val userId = context.userId + Log.i(TAG, "Disabling trust agent ${componentName.flattenToString()} for user $userId") + val agents = lockPatternUtils.getEnabledTrustAgents(userId).toMutableList() + .distinct() + .minus(componentName) + lockPatternUtils.setEnabledTrustAgents(agents, userId) + } + + companion object { + /** + * Creates a new rule for the specified agent class. Example usage: + * ``` + * @get:Rule val rule = TrustAgentRule<MyTestAgent>() + * ``` + */ + inline operator fun <reified T : BaseTrustAgentService> invoke() = + TrustAgentRule(T::class) + + private const val TAG = "TrustAgentRule" + private val WAIT_TIME = 1000L + } +} |