Merge "Update CTS test for FGS while-in-use permission restriction to reflect enforcement mode" into rvc-dev
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 0d5b5b1..fdea362 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -2407,6 +2407,10 @@
                   android:label="@string/wifi_test_network_request_unavailable"
                   android:configChanges="keyboardHidden|orientation|screenSize" />
 
+        <activity android:name=".wifi.NetworkRequestInvalidCredentialNetworkSpecifierTestActivity"
+                  android:label="@string/wifi_test_network_request_invalid_credential"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
         <activity android:name=".wifi.NetworkSuggestionSsidTestActivity"
                   android:label="@string/wifi_test_network_suggestion_ssid"
                   android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -2419,6 +2423,10 @@
                   android:label="@string/wifi_test_network_suggestion_ssid_post_connect"
                   android:configChanges="keyboardHidden|orientation|screenSize" />
 
+        <activity android:name=".wifi.NetworkSuggestionConnectionFailureTestActivity"
+                  android:label="@string/wifi_test_network_suggestion_connection_failure"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
         <activity android:name=".p2p.GoNegRequesterTestListActivity"
                 android:label="@string/p2p_go_neg_requester"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index bbc663c..0ff0798 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1651,10 +1651,8 @@
         Click the button below to go to system settings and enable Wi-Fi and Location Mode.</string>
     <string name="wifi_settings">Wi-Fi Settings</string>
     <string name="location_settings">Location Settings</string>
-    <string name="wifi_setup_error">
-        Test failed.\n\nSet up error. Check whether Wi-Fi is enabled.</string>
-    <string name="wifi_unexpected_error">
-        Test failed.\n\nUnexpected error. Check logcat.</string>
+    <string name="wifi_setup_error">Test failed with set up error.</string>
+    <string name="wifi_unexpected_error">Test failed with unexpected error. Check logcat.</string>
 
     <string name="wifi_status_initiating_scan">Initiating scan.</string>
     <string name="wifi_status_scan_failure">Unable to initiate scan or find any open network in scan results.</string>
@@ -1662,7 +1660,9 @@
     <string name="wifi_status_initiating_network_request">Initiating network request.</string>
     <string name="wifi_status_network_wait_for_available">Waiting for network connection. Please click the network in the dialog that pops up for approving the request.</string>
     <string name="wifi_status_network_available">"Connected to network."</string>
+    <string name="wifi_status_network_available_error">Connected to network unexpectedly.</string>
     <string name="wifi_status_network_wait_for_unavailable">"Ensuring device does not connect to any network. You should see an empty dialog that pops up for approving the request."</string>
+    <string name="wifi_status_network_wait_for_unavailable_invalid_credential">"Ensuring device does not connect to the network. Please click the network in the dialog that pops up for approving the request."</string>
     <string name="wifi_status_network_unavailable">"Did not connect to any network."</string>
     <string name="wifi_status_network_wait_for_lost">Ensuring device does not disconnect from the network until the request is released.</string>
     <string name="wifi_status_network_lost">Disconnected from the network.</string>
@@ -1670,14 +1670,21 @@
 
     <string name="wifi_status_suggestion_add">Adding suggestions to the device.</string>
     <string name="wifi_status_suggestion_add_failure">Failed to add suggestions.</string>
+    <string name="wifi_status_suggestion_get_failure">Failed to get suggestions.</string>
     <string name="wifi_status_suggestion_remove">Removing suggestions from the device.</string>
     <string name="wifi_status_suggestion_remove_failure">Failed to remove suggestions.</string>
     <string name="wifi_status_suggestion_wait_for_connect">Waiting for network connection. Please click \"Yes\" in the notification that pops up for approving the request.</string>
+    <string name="wifi_status_suggestion_ensure_no_connect">Ensuring no network connection. Please click \"Yes\" in the notification that pops up for approving the request.</string>
     <string name="wifi_status_suggestion_connect">Connected to the network.</string>
+    <string name="wifi_status_suggestion_not_connected">Did not connect to the network.</string>
     <string name="wifi_status_suggestion_wait_for_post_connect_bcast">Waiting for post connection broadcast.</string>
     <string name="wifi_status_suggestion_post_connect_bcast">Received post connection broadcast.</string>
     <string name="wifi_status_suggestion_post_connect_bcast_failure">Failed to receive post connection broadcast.</string>
-    <string name="wifi_status_suggestion_wait_for_disconnect">Ensuring device does not disconnect from the network after removing suggestions.</string>
+    <string name="wifi_status_suggestion_wait_for_connection_status">Waiting for connection status.</string>
+    <string name="wifi_status_suggestion_connection_status">Received connection status.</string>
+    <string name="wifi_status_suggestion_connection_status_failure">Failed to receive connection status.</string>
+    <string name="wifi_status_suggestion_wait_for_disconnect">Ensuring device does disconnect from the network after removing suggestions.</string>
+    <string name="wifi_status_suggestion_not_disconnected">Did not disconnect from the network.</string>
     <string name="wifi_status_suggestion_disconnected">Disconnected from the network.</string>
 
     <string name="wifi_status_test_success">Test completed successfully!</string>
@@ -1690,6 +1697,8 @@
     <string name="wifi_test_network_request_pattern_info">Tests whether the API can be used to a connect to network with a SSID and BSSID pattern specified in the request.</string>
     <string name="wifi_test_network_request_unavailable">Network Request with a specific network that is unavailable.</string>
     <string name="wifi_test_network_request_unavailable_info">Tests that the API fails to connect when an unavailable network is specified in the request.</string>
+    <string name="wifi_test_network_request_invalid_credential">Network Request with a specific network with wrong credential.</string>
+    <string name="wifi_test_network_request_invalid_credential_info">Tests that the API fails to connect when a network with wrong credential is specified in the request.</string>
 
     <string name="wifi_test_network_suggestion">Network Suggestion tests</string>
     <string name="wifi_test_network_suggestion_ssid">Network suggestion with SSID.</string>
@@ -1698,6 +1707,8 @@
     <string name="wifi_test_network_suggestion_ssid_bssid_info">Tests whether the API can be used to suggest a network with SSID and specific BSSID to the device and the device connects to it.</string>
     <string name="wifi_test_network_suggestion_ssid_post_connect">Network suggestion with SSID and post connection broadcast.</string>
     <string name="wifi_test_network_suggestion_ssid_post_connect_info">Tests whether the API can be used to suggest a network with SSID to the device and the device connects to it and sends the post connect broadcast back to the app.</string>
+    <string name="wifi_test_network_suggestion_connection_failure">Network suggestion connection failure.</string>
+    <string name="wifi_test_network_suggestion_connection_failure_info">Tests whether the SuggestionConnectionStatusListener API can be used to listen to connection failures for suggested networks.</string>
 
     <!-- Strings for P2pTestActivity -->
     <string name="p2p_test">Wi-Fi Direct Test</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractBaseTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractBaseTest.java
index 65cf3a2..d1a300e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractBaseTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractBaseTest.java
@@ -45,6 +45,7 @@
     private static final int REQUEST_ENROLL = 1;
 
     abstract protected String getTag();
+    abstract protected boolean isOnPauseAllowed();
 
     protected final Handler mHandler = new Handler(Looper.getMainLooper());
     protected final Executor mExecutor = mHandler::post;
@@ -60,6 +61,21 @@
     }
 
     @Override
+    protected void onPause() {
+        super.onPause();
+
+        // Assume we only enable the pass button when all tests pass. There actually  isn't a way
+        // to easily do something like `this.isTestPassed()`
+        if (!getPassButton().isEnabled() && !isOnPauseAllowed()) {
+            showToastAndLog("This test must be completed without going onPause");
+            // Do not allow the test to continue if it loses foreground. Testers must start over.
+            // 1) This is to avoid any potential change to the current enrollment / biometric state.
+            // 2) The authentication UI must not affect the caller's activity lifecycle.
+            finish();
+        }
+    }
+
+    @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == REQUEST_ENROLL) {
             mCurrentlyEnrolling = false;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricStrongTests.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricStrongTests.java
index ce71403..367d76d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricStrongTests.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricStrongTests.java
@@ -63,6 +63,8 @@
     private static final String KEY_NAME_NO_STRONGBOX = "key_without_strongbox";
     private static final byte[] PAYLOAD = new byte[] {1, 2, 3, 4, 5, 6};
 
+    // TODO: Build these lists in a smarter way. For now, when adding a test to this list, please
+    // double check the logic in isOnPauseAllowed()
     private boolean mHasStrongBox;
     private Button mCheckAndEnrollButton;
     private Button mAuthenticateWithoutStrongBoxButton;
@@ -239,6 +241,30 @@
         });
     }
 
+    @Override
+    protected boolean isOnPauseAllowed() {
+        // Test hasn't started yet, user may need to go to Settings to remove enrollments
+        if (mCheckAndEnrollButton.isEnabled()) {
+            return true;
+        }
+
+        // Key invalidation test is currently the last test. Thus, if every other test is currently
+        // completed, let's allow onPause (allow tester to go into settings multiple times if
+        // needed).
+        if (mAuthenticateWithoutStrongBoxPassed && mAuthenticateWithStrongBoxPassed
+                && mAuthenticateUIPassed && mAuthenticateCredential1Passed
+                && mAuthenticateCredential2Passed && mAuthenticateCredential3Passed
+                && mCheckInvalidInputsPassed && mRejectThenAuthenticatePassed) {
+            return true;
+        }
+
+        if (mCurrentlyEnrolling) {
+            return true;
+        }
+
+        return false;
+    }
+
     private boolean isKeyInvalidated(String keyName) {
         try {
             Utils.initCipher(keyName);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricWeakTests.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricWeakTests.java
index 56c9224..f6fad5c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricWeakTests.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricWeakTests.java
@@ -184,14 +184,17 @@
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
-
-        if (!mCurrentlyEnrolling) {
-            // Do not allow the test to continue if it loses foreground. Testers must start over.
-            // This is to avoid any potential change to the current enrollment / biometric state.
-            finish();
+    protected boolean isOnPauseAllowed() {
+        // Test hasn't started yet, user may need to go to Settings to remove enrollments
+        if (mEnrollButton.isEnabled()) {
+            return true;
         }
+
+        if (mCurrentlyEnrolling) {
+            return true;
+        }
+
+        return false;
     }
 
     private void updatePassButton() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/CredentialCryptoTests.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/CredentialCryptoTests.java
index 3b3b289..caf9e29 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/CredentialCryptoTests.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/CredentialCryptoTests.java
@@ -40,7 +40,7 @@
 
 import javax.crypto.Cipher;
 
-public class CredentialCryptoTests extends PassFailButtons.Activity {
+public class CredentialCryptoTests extends AbstractBaseTest {
     private static final String TAG = "CredentialCryptoTests";
 
     private static final String KEY_NAME_STRONGBOX = "credential_timed_key_strongbox";
@@ -57,6 +57,17 @@
     private boolean mCredentialTimedKeyNoStrongBoxPassed;
 
     @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected boolean isOnPauseAllowed() {
+        // Tests haven't started yet
+        return !mCredentialTimedKeyStrongBoxPassed && !mCredentialTimedKeyNoStrongBoxPassed;
+    }
+
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.biometric_test_credential_crypto_tests);
@@ -163,9 +174,4 @@
             getPassButton().setEnabled(true);
         }
     }
-
-    private void showToastAndLog(String s) {
-        Log.d(TAG, s);
-        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
-    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/CredentialEnrolledTests.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/CredentialEnrolledTests.java
index 473579f..1fc950e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/CredentialEnrolledTests.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/CredentialEnrolledTests.java
@@ -36,17 +36,22 @@
  * This test checks that when a credential is enrolled, and biometrics are not enrolled,
  * BiometricManager and BiometricPrompt receive the correct results.
  */
-public class CredentialEnrolledTests extends PassFailButtons.Activity {
+public class CredentialEnrolledTests extends AbstractBaseTest {
     private static final String TAG = "CredentialEnrolledTests";
 
-    boolean mBiometricManagerPass;
-    boolean mBiometricPromptSetAllowedAuthenticatorsPass;
-    boolean mBiometricPromptSetDeviceCredentialAllowedPass;
+    private boolean mBiometricManagerPass;
+    private boolean mBiometricPromptSetAllowedAuthenticatorsPass;
+    private boolean mBiometricPromptSetDeviceCredentialAllowedPass;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Executor mExecutor = mHandler::post;
 
     @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.biometric_test_credential_enrolled_tests);
@@ -154,9 +159,10 @@
         });
     }
 
-    private void showToastAndLog(String s) {
-        Log.d(TAG, s);
-        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
+    @Override
+    protected boolean isOnPauseAllowed() {
+        // Allow user to go to Settings, etc to figure out why this test isn't passing.
+        return !mBiometricManagerPass;
     }
 
     private void updatePassButton() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java
index 3d507cd..4db3766 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java
@@ -118,6 +118,7 @@
             public void run() {
                 if (reason != null) {
                     mWifiInfo.append(reason);
+                    mWifiInfo.append("\n");
                 }
                 getPassButton().setEnabled(false);
                 mWifiInfo.append(getString(R.string.wifi_status_test_failed));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestInvalidCredentialNetworkSpecifierTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestInvalidCredentialNetworkSpecifierTestActivity.java
new file mode 100644
index 0000000..17c86a7
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestInvalidCredentialNetworkSpecifierTestActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.wifi;
+
+import static com.android.cts.verifier.wifi.testcase.NetworkRequestTestCase.NETWORK_SPECIFIER_INVALID_CREDENTIAL;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifi.testcase.NetworkRequestTestCase;
+
+/**
+ * Test activity for specifier with specific ssid/bssid, but with wrong credentials.
+ */
+public class NetworkRequestInvalidCredentialNetworkSpecifierTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new NetworkRequestTestCase(context, NETWORK_SPECIFIER_INVALID_CREDENTIAL);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.wifi_test_network_request_invalid_credential,
+                R.string.wifi_test_network_request_invalid_credential_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkSuggestionConnectionFailureTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkSuggestionConnectionFailureTestActivity.java
new file mode 100644
index 0000000..d70d5ae
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkSuggestionConnectionFailureTestActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.wifi;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifi.testcase.NetworkSuggestionTestCase;
+
+/**
+ * Test activity for suggestion with ssid specified, but with wrong credentials.
+ */
+public class NetworkSuggestionConnectionFailureTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new NetworkSuggestionTestCase(
+                context,
+                false, /* setBssid */
+                false, /* setRequiresAppInteraction */
+                true /* simulateConnectionFailure */);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.wifi_test_network_suggestion_connection_failure,
+                R.string.wifi_test_network_suggestion_connection_failure_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestListActivity.java
index b7c6c8a..c88bd1f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestListActivity.java
@@ -74,6 +74,11 @@
                 NetworkRequestUnavailableNetworkSpecifierTestActivity.class.getName(),
                 new Intent(this, NetworkRequestUnavailableNetworkSpecifierTestActivity.class),
                 null));
+        adapter.add(TestListAdapter.TestListItem.newTest(this,
+                R.string.wifi_test_network_request_invalid_credential,
+                NetworkRequestInvalidCredentialNetworkSpecifierTestActivity.class.getName(),
+                new Intent(this, NetworkRequestInvalidCredentialNetworkSpecifierTestActivity.class),
+                null));
         adapter.add(TestListAdapter.TestListItem.newCategory(this,
                 R.string.wifi_test_network_suggestion));
         adapter.add(TestListAdapter.TestListItem.newTest(this,
@@ -88,6 +93,10 @@
                 R.string.wifi_test_network_suggestion_ssid_post_connect,
                 NetworkSuggestionSsidPostConnectTestActivity.class.getName(),
                 new Intent(this, NetworkSuggestionSsidPostConnectTestActivity.class), null));
+        adapter.add(TestListAdapter.TestListItem.newTest(this,
+                R.string.wifi_test_network_suggestion_connection_failure,
+                NetworkSuggestionConnectionFailureTestActivity.class.getName(),
+                new Intent(this, NetworkSuggestionConnectionFailureTestActivity.class), null));
 
         adapter.registerDataSetObserver(new DataSetObserver() {
             @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestUtils.java
index 2b7b4b1..dadacd3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.wifi;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -31,7 +32,10 @@
 
 import com.android.cts.verifier.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.Random;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -93,8 +97,18 @@
         return true;
     }
 
-    // Helper to check if the scan result corresponds to an open network.
-    private static boolean isScanResultForOpenNetwork(@NonNull ScanResult scanResult) {
+    public static final int SCAN_RESULT_TYPE_OPEN = 0;
+    public static final int SCAN_RESULT_TYPE_PSK = 1;
+
+    @IntDef(prefix = { "SCAN_RESULT_TYPE_" }, value = {
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScanResultType {}
+
+    /**
+     * Helper to check if the scan result corresponds to an open network.
+     */
+    public static boolean isScanResultForOpenNetwork(@NonNull ScanResult scanResult) {
         String capabilities = scanResult.capabilities;
         return !capabilities.contains("PSK") && !capabilities.contains("EAP")
                 && !capabilities.contains("WEP") && !capabilities.contains("SAE")
@@ -102,12 +116,42 @@
     }
 
     /**
-     * Helper method to start a scan and find any open networks in the scan results returned by the
-     * device.
-     * @return ScanResult instance corresponding to an open network if one exists, null if the
-     * scan failed or if there are no open networks found.
+     * Helper to check if the scan result corresponds to a WPA2 PSK network.
      */
-    public @Nullable ScanResult startScanAndFindAnyOpenNetworkInResults()
+    public static boolean isScanResultForWpa2Network(@NonNull ScanResult scanResult) {
+        String capabilities = scanResult.capabilities;
+        return capabilities.contains("PSK");
+    }
+
+    /**
+     * Helper to check if the scan result corresponds to a WPA3 PSK network.
+     */
+    public static boolean isScanResultForWpa3Network(@NonNull ScanResult scanResult) {
+        String capabilities = scanResult.capabilities;
+        return capabilities.contains("SAE");
+    }
+
+    private static boolean doesScanResultMatchType(
+            ScanResult scanResult, @ScanResultType int type) {
+        switch(type) {
+            case SCAN_RESULT_TYPE_OPEN:
+                return isScanResultForOpenNetwork(scanResult);
+            case SCAN_RESULT_TYPE_PSK:
+                return isScanResultForWpa2Network(scanResult)
+                        || isScanResultForWpa3Network(scanResult);
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Helper method to start a scan and find any type of networks in the scan results returned by
+     * the device.
+     * @return ScanResult instance corresponding to an network of type if one exists, null if the
+     * scan failed or if there are networks found.
+     */
+    public @Nullable ScanResult startScanAndFindAnyMatchingNetworkInResults(
+            @ScanResultType int type)
             throws InterruptedException {
         // Start scan and wait for new results.
         if (!startScanAndWaitForResults()) {
@@ -118,7 +162,7 @@
         for (ScanResult scanResult : scanResults) {
             if (!TextUtils.isEmpty(scanResult.SSID)
                     && !TextUtils.isEmpty(scanResult.BSSID)
-                    && isScanResultForOpenNetwork(scanResult)) {
+                    && doesScanResultMatchType(scanResult, type)) {
                 if (DBG) Log.v(TAG, "Found open network " + scanResult);
                 return scanResult;
             }
@@ -169,5 +213,14 @@
         return true;
     }
 
-
+    /**
+     * Generate random passphrase to use for tests.
+     * @return
+     */
+    public String generateRandomPassphrase() {
+        return new Random().ints('a', 'z' + 1)
+                .limit(45)
+                .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
+                .toString();
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java
index 12269a6..96f9547 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java
@@ -19,9 +19,11 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.cts.verifier.wifi.TestUtils.SCAN_RESULT_TYPE_OPEN;
+import static com.android.cts.verifier.wifi.TestUtils.SCAN_RESULT_TYPE_PSK;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.MacAddress;
@@ -37,6 +39,7 @@
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.wifi.BaseTestCase;
 import com.android.cts.verifier.wifi.CallbackUtils;
+import com.android.cts.verifier.wifi.TestUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -57,11 +60,13 @@
     public static final int NETWORK_SPECIFIER_SPECIFIC_SSID_BSSID = 0;
     public static final int NETWORK_SPECIFIER_PATTERN_SSID_BSSID = 1;
     public static final int NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID = 2;
+    public static final int NETWORK_SPECIFIER_INVALID_CREDENTIAL = 3;
 
     @IntDef(prefix = { "NETWORK_SPECIFIER_" }, value = {
             NETWORK_SPECIFIER_SPECIFIC_SSID_BSSID,
             NETWORK_SPECIFIER_PATTERN_SSID_BSSID,
             NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID,
+            NETWORK_SPECIFIER_INVALID_CREDENTIAL
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface NetworkSpecifierType{}
@@ -106,6 +111,16 @@
                 configBuilder.setSsid(UNAVAILABLE_SSID);
                 configBuilder.setBssid(bssid);
                 break;
+            case NETWORK_SPECIFIER_INVALID_CREDENTIAL:
+                configBuilder.setSsid(scanResult.SSID);
+                configBuilder.setBssid(MacAddress.fromString(scanResult.BSSID));
+                // Use a random password to simulate connection failure.
+                if (TestUtils.isScanResultForWpa2Network(scanResult)) {
+                    configBuilder.setWpa2Passphrase(mTestUtils.generateRandomPassphrase());
+                } else if (TestUtils.isScanResultForWpa3Network(scanResult)) {
+                    configBuilder.setWpa3Passphrase(mTestUtils.generateRandomPassphrase());
+                }
+                break;
             default:
                 throw new IllegalStateException("Unknown specifier type specifier");
         }
@@ -121,26 +136,28 @@
 
     @Override
     protected boolean executeTest() throws InterruptedException {
-        // Step 1: Scan and find any open network around.
+        // Step: Scan and find any open network around.
         if (DBG) Log.v(TAG, "Scan and find an open network");
-        ScanResult openNetwork = mTestUtils.startScanAndFindAnyOpenNetworkInResults();
-        if (openNetwork == null) {
+        ScanResult testNetwork = mTestUtils.startScanAndFindAnyMatchingNetworkInResults(
+                mNetworkSpecifierType == NETWORK_SPECIFIER_INVALID_CREDENTIAL
+                        ? SCAN_RESULT_TYPE_PSK : SCAN_RESULT_TYPE_OPEN);
+        if (testNetwork == null) {
             setFailureReason(mContext.getString(R.string.wifi_status_scan_failure));
             return false;
         }
 
-        // Step 2: Create a specifier for the chosen open network depending on the type of test.
-        NetworkSpecifier wns = createNetworkSpecifier(openNetwork);
+        // Step: Create a specifier for the chosen open network depending on the type of test.
+        NetworkSpecifier wns = createNetworkSpecifier(testNetwork);
         if (wns == null) return false;
 
-        // Step 3: Create a network request with specifier.
+        // Step: Create a network request with specifier.
         mNetworkRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI)
                 .setNetworkSpecifier(wns)
                 .removeCapability(NET_CAPABILITY_INTERNET)
                 .build();
 
-        // Step 4: Send the network request
+        // Step: Send the network request
         if (DBG) Log.v(TAG, "Request network using " + mNetworkRequest);
         mNetworkCallback = new CallbackUtils.NetworkCallback(CALLBACK_TIMEOUT_MS);
         mListener.onTestMsgReceived(
@@ -148,10 +165,17 @@
         mConnectivityManager.requestNetwork(mNetworkRequest, mNetworkCallback,
                 NETWORK_REQUEST_TIMEOUT_MS);
 
-        // Step 5: Wait for the network available/unavailable callback.
-        if (mNetworkSpecifierType == NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID) {
-            mListener.onTestMsgReceived(
-                    mContext.getString(R.string.wifi_status_network_wait_for_unavailable));
+        // Step: Wait for the network available/unavailable callback.
+        if (mNetworkSpecifierType == NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID
+                || mNetworkSpecifierType == NETWORK_SPECIFIER_INVALID_CREDENTIAL) {
+            if (mNetworkSpecifierType == NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID) {
+                mListener.onTestMsgReceived(
+                        mContext.getString(R.string.wifi_status_network_wait_for_unavailable));
+            } else {
+                mListener.onTestMsgReceived(
+                        mContext.getString(R.string
+                                .wifi_status_network_wait_for_unavailable_invalid_credential));
+            }
             if (DBG) Log.v(TAG, "Waiting for network unavailable callback");
             boolean cbStatusForUnavailable = mNetworkCallback.waitForUnavailable();
             if (!cbStatusForUnavailable) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
index 53bcc4a..53bb220 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
@@ -19,6 +19,9 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.wifi.WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS;
 
+import static com.android.cts.verifier.wifi.TestUtils.SCAN_RESULT_TYPE_OPEN;
+import static com.android.cts.verifier.wifi.TestUtils.SCAN_RESULT_TYPE_PSK;
+
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -37,9 +40,11 @@
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.wifi.BaseTestCase;
 import com.android.cts.verifier.wifi.CallbackUtils;
+import com.android.cts.verifier.wifi.TestUtils;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -68,13 +73,20 @@
 
     private final boolean mSetBssid;
     private final boolean mSetRequiresAppInteraction;
+    private final boolean mSimulateConnectionFailure;
 
     public NetworkSuggestionTestCase(Context context, boolean setBssid,
-                                     boolean setRequiresAppInteraction) {
+            boolean setRequiresAppInteraction) {
+        this(context, setBssid, setRequiresAppInteraction, false);
+    }
+
+    public NetworkSuggestionTestCase(Context context, boolean setBssid,
+            boolean setRequiresAppInteraction, boolean simulateConnectionFailure) {
         super(context);
         mExecutorService = Executors.newSingleThreadScheduledExecutor();
         mSetBssid = setBssid;
         mSetRequiresAppInteraction = setRequiresAppInteraction;
+        mSimulateConnectionFailure = simulateConnectionFailure;
     }
 
     // Create a network specifier based on the test type.
@@ -87,6 +99,12 @@
         if (mSetRequiresAppInteraction) {
             builder.setIsAppInteractionRequired(true);
         }
+        // Use a random password to simulate connection failure.
+        if (TestUtils.isScanResultForWpa2Network(scanResult)) {
+            builder.setWpa2Passphrase(mTestUtils.generateRandomPassphrase());
+        } else if (TestUtils.isScanResultForWpa3Network(scanResult)) {
+            builder.setWpa3Passphrase(mTestUtils.generateRandomPassphrase());
+        }
         return builder.build();
     }
 
@@ -96,50 +114,73 @@
         }
     }
 
+    private static class ConnectionStatusListener implements
+            WifiManager.SuggestionConnectionStatusListener {
+        private final CountDownLatch mCountDownLatch;
+        public WifiNetworkSuggestion wifiNetworkSuggestion = null;
+        public int failureReason = -1;
+
+        ConnectionStatusListener(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onConnectionStatus(
+                WifiNetworkSuggestion wifiNetworkSuggestion, int failureReason) {
+            this.wifiNetworkSuggestion = wifiNetworkSuggestion;
+            this.failureReason = failureReason;
+            mCountDownLatch.countDown();
+        }
+    }
+
     @Override
     protected boolean executeTest() throws InterruptedException {
-        // Step 1: Scan and find any open network around.
-        if (DBG) Log.v(TAG, "Scan and find an open network");
-        ScanResult openNetwork = mTestUtils.startScanAndFindAnyOpenNetworkInResults();
-        if (openNetwork == null) {
+        // Step: Scan and find any open network around.
+        if (DBG) Log.v(TAG, "Scan and find a network");
+        ScanResult testNetwork = mTestUtils.startScanAndFindAnyMatchingNetworkInResults(
+                mSimulateConnectionFailure ? SCAN_RESULT_TYPE_PSK : SCAN_RESULT_TYPE_OPEN);
+        if (testNetwork == null) {
             setFailureReason(mContext.getString(R.string.wifi_status_scan_failure));
             return false;
         }
 
-        // Step 1.a (Optional): Register for the post connection broadcast.
+        // Step (Optional): Register for the post connection broadcast.
         final CountDownLatch countDownLatchForPostConnectionBcast = new CountDownLatch(1);
-        if (mSetRequiresAppInteraction) {
-            IntentFilter intentFilter =
-                    new IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
-            // Post connection broadcast receiver.
-            mBroadcastReceiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (DBG) Log.v(TAG, "Broadcast onReceive " + intent);
-                    if (!intent.getAction().equals(
-                            WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
-                        return;
-                    }
-                    if (DBG) Log.v(TAG, "Post connection broadcast received");
-                    countDownLatchForPostConnectionBcast.countDown();
+        IntentFilter intentFilter =
+                new IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
+        // Post connection broadcast receiver.
+        mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (DBG) Log.v(TAG, "Broadcast onReceive " + intent);
+                if (!intent.getAction().equals(
+                        WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
+                    return;
                 }
-            };
-            // Register the receiver for post connection broadcast.
-            mContext.registerReceiver(mBroadcastReceiver, intentFilter);
-        }
+                if (DBG) Log.v(TAG, "Post connection broadcast received");
+                countDownLatchForPostConnectionBcast.countDown();
+            }
+        };
+        // Register the receiver for post connection broadcast.
+        mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+        final CountDownLatch countDownLatchForConnectionStatusListener = new CountDownLatch(1);
+        ConnectionStatusListener connectionStatusListener =
+                new ConnectionStatusListener(countDownLatchForConnectionStatusListener);
+        mWifiManager.addSuggestionConnectionStatusListener(
+                Executors.newSingleThreadExecutor(), connectionStatusListener);
 
-        // Step 1.b: Register network callback to wait for connection state.
+        // Step: Register network callback to wait for connection state.
         mNetworkRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI)
                 .build();
         mNetworkCallback = new CallbackUtils.NetworkCallback(CALLBACK_TIMEOUT_MS);
         mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
 
-        // Step 2: Create a suggestion for the chosen open network depending on the type of test.
-        WifiNetworkSuggestion networkSuggestion = createNetworkSuggestion(openNetwork);
+        // Step: Create a suggestion for the chosen open network depending on the type of test.
+        WifiNetworkSuggestion networkSuggestion = createNetworkSuggestion(testNetwork);
         mNetworkSuggestions.add(networkSuggestion);
 
-        // Step 4: Add a network suggestions.
+        // Step: Add a network suggestions.
         if (DBG) Log.v(TAG, "Adding suggestion");
         mListener.onTestMsgReceived(mContext.getString(R.string.wifi_status_suggestion_add));
         if (mWifiManager.addNetworkSuggestions(mNetworkSuggestions)
@@ -147,8 +188,14 @@
             setFailureReason(mContext.getString(R.string.wifi_status_suggestion_add_failure));
             return false;
         }
+        if (DBG) Log.v(TAG, "Getting suggestion");
+        List<WifiNetworkSuggestion> retrievedSuggestions = mWifiManager.getNetworkSuggestions();
+        if (!Objects.equals(mNetworkSuggestions, retrievedSuggestions)) {
+            setFailureReason(mContext.getString(R.string.wifi_status_suggestion_get_failure));
+            return false;
+        }
 
-        // Step 5: Trigger scans periodically to trigger network selection quicker.
+        // Step: Trigger scans periodically to trigger network selection quicker.
         if (DBG) Log.v(TAG, "Triggering scan periodically");
         mExecutorService.scheduleAtFixedRate(() -> {
             if (!mWifiManager.startScan()) {
@@ -156,33 +203,49 @@
             }
         }, 0, PERIODIC_SCAN_INTERVAL_MS, TimeUnit.MILLISECONDS);
 
-        // Step 6: Wait for connection.
-        if (DBG) Log.v(TAG, "Waiting for connection");
-        mListener.onTestMsgReceived(mContext.getString(
-                R.string.wifi_status_suggestion_wait_for_connect));
-        Pair<Boolean, Network> cbStatusForAvailable = mNetworkCallback.waitForAvailable();
-        if (!cbStatusForAvailable.first) {
-            Log.e(TAG, "Failed to get network available callback");
-            setFailureReason(mContext.getString(R.string.wifi_status_network_cb_timeout));
-            return false;
-        }
-        mListener.onTestMsgReceived(
-                mContext.getString(R.string.wifi_status_suggestion_connect));
-
-        // Step 7: Ensure that we connected to the suggested network (optionally, the correct
-        // BSSID).
-        if (!mTestUtils.isConnected("\"" + openNetwork.SSID + "\"",
-                // TODO: This might fail if there are other BSSID's for the same network & the
-                //  device decided to connect/roam to a different BSSID. We don't turn off roaming
-                //  for suggestions.
-                mSetBssid ? openNetwork.BSSID : null)) {
-            Log.e(TAG, "Failed to connected to a wrong network");
-            setFailureReason(mContext.getString(R.string.wifi_status_connected_to_other_network));
-            return false;
+        // Step: Wait for connection/unavailable.
+        if (!mSimulateConnectionFailure) {
+            if (DBG) Log.v(TAG, "Waiting for connection");
+            mListener.onTestMsgReceived(mContext.getString(
+                    R.string.wifi_status_suggestion_wait_for_connect));
+            Pair<Boolean, Network> cbStatusForAvailable = mNetworkCallback.waitForAvailable();
+            if (!cbStatusForAvailable.first) {
+                Log.e(TAG, "Failed to get network available callback");
+                setFailureReason(mContext.getString(R.string.wifi_status_network_cb_timeout));
+                return false;
+            }
+            mListener.onTestMsgReceived(
+                    mContext.getString(R.string.wifi_status_suggestion_connect));
+        } else {
+            if (DBG) Log.v(TAG, "Ensure no connection");
+            mListener.onTestMsgReceived(mContext.getString(
+                    R.string.wifi_status_suggestion_ensure_no_connect));
+            Pair<Boolean, Network> cbStatusForAvailable = mNetworkCallback.waitForAvailable();
+            if (cbStatusForAvailable.first) {
+                Log.e(TAG, "Unexpectedly got network available callback");
+                setFailureReason(mContext.getString(R.string.wifi_status_network_available_error));
+                return false;
+            }
+            mListener.onTestMsgReceived(
+                    mContext.getString(R.string.wifi_status_suggestion_not_connected));
         }
 
+        // Step: Ensure that we connected to the suggested network (optionally, the correct BSSID).
+        if (!mSimulateConnectionFailure) {
+            if (!mTestUtils.isConnected("\"" + testNetwork.SSID + "\"",
+                    // TODO: This might fail if there are other BSSID's for the same network & the
+                    //  device decided to connect/roam to a different BSSID. We don't turn off
+                    //  roaming for suggestions.
+                    mSetBssid ? testNetwork.BSSID : null)) {
+                Log.e(TAG, "Failed to connected to the network");
+                setFailureReason(
+                        mContext.getString(R.string.wifi_status_connected_to_other_network));
+                return false;
+            }
+        }
+
+        // Step (Optional): Ensure we received the post connect broadcast.
         if (mSetRequiresAppInteraction) {
-            // Step 7 (Optional): Ensure we received the post connect broadcast.
             if (DBG) Log.v(TAG, "Wait for post connection broadcast");
             mListener.onTestMsgReceived(
                     mContext.getString(
@@ -197,8 +260,35 @@
             mListener.onTestMsgReceived(
                     mContext.getString(R.string.wifi_status_suggestion_post_connect_bcast));
         }
+        // Step (Optional): Ensure we received the connection status listener.
+        if (mSimulateConnectionFailure) {
+            if (DBG) Log.v(TAG, "Wait for connection status listener");
+            mListener.onTestMsgReceived(
+                    mContext.getString(
+                            R.string.wifi_status_suggestion_wait_for_connection_status));
+            if (!countDownLatchForConnectionStatusListener.await(
+                    CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                Log.e(TAG, "Failed to receive connection status");
+                setFailureReason(mContext.getString(
+                        R.string.wifi_status_suggestion_connection_status_failure));
+                return false;
+            }
+            if (DBG) Log.v(TAG, "Received connection status");
+            if (!Objects.equals(connectionStatusListener.wifiNetworkSuggestion, networkSuggestion)
+                    || connectionStatusListener.failureReason
+                    != WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION) {
+                Log.e(TAG, "Received wrong connection status for "
+                        + connectionStatusListener.wifiNetworkSuggestion
+                        + " with reason: " + connectionStatusListener.failureReason);
+                setFailureReason(mContext.getString(
+                        R.string.wifi_status_suggestion_connection_status_failure));
+                return false;
+            }
+            mListener.onTestMsgReceived(
+                    mContext.getString(R.string.wifi_status_suggestion_connection_status));
+        }
 
-        // Step 8: Remove the suggestions from the app.
+        // Step: Remove the suggestions from the app.
         if (DBG) Log.v(TAG, "Removing suggestion");
         mListener.onTestMsgReceived(mContext.getString(R.string.wifi_status_suggestion_remove));
         if (mWifiManager.removeNetworkSuggestions(mNetworkSuggestions)
@@ -207,15 +297,19 @@
             return false;
         }
 
-        // Step 9: Ensure we don't disconnect immediately on suggestion removal.
-        mListener.onTestMsgReceived(
-                mContext.getString(R.string.wifi_status_suggestion_wait_for_disconnect));
-        if (DBG) Log.v(TAG, "Ensuring we don't disconnect immediately");
-        boolean cbStatusForLost = mNetworkCallback.waitForLost();
-        if (cbStatusForLost) {
-            Log.e(TAG, "Disconnected from the network immediately");
-            setFailureReason(mContext.getString(R.string.wifi_status_suggestion_disconnected));
-            return false;
+        // Step: Ensure we disconnect immediately on suggestion removal.
+        if (!mSimulateConnectionFailure) {
+            mListener.onTestMsgReceived(
+                    mContext.getString(R.string.wifi_status_suggestion_wait_for_disconnect));
+            if (DBG) Log.v(TAG, "Ensuring we disconnect immediately");
+            boolean cbStatusForLost = mNetworkCallback.waitForLost();
+            if (!cbStatusForLost) {
+                setFailureReason(
+                        mContext.getString(R.string.wifi_status_suggestion_not_disconnected));
+                return false;
+            }
+            mListener.onTestMsgReceived(
+                    mContext.getString(R.string.wifi_status_suggestion_disconnected));
         }
 
         // All done!
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/NullWebViewUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/NullWebViewUtils.java
index 3153adb..7b27924 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/NullWebViewUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/NullWebViewUtils.java
@@ -16,73 +16,20 @@
 
 package com.android.compatibility.common.util;
 
-import android.content.Context;
 import android.content.pm.PackageManager;
 
 /**
  * Utilities to enable the android.webkit.* CTS tests (and others that rely on a functioning
- * android.webkit.WebView implementation) to determine whether a functioning WebView is present
- * on the device or not.
- *
- * Test cases that require android.webkit.* classes should wrap their first usage of WebView in a
- * try catch block, and pass any exception that is thrown to
- * NullWebViewUtils.determineIfWebViewAvailable. The return value of
- * NullWebViewUtils.isWebViewAvailable will then determine if the test should expect to be able to
- * use a WebView.
+ * android.webkit.WebView implementation) to determine whether there should be a WebView
+ * implementation on the device.
  */
 public class NullWebViewUtils {
 
-    private static boolean sWebViewUnavailable;
-
     /**
-     * @param context Current Activity context, used to query the PackageManager.
-     * @param t       An exception thrown by trying to invoke android.webkit.* APIs.
-     */
-    public static void determineIfWebViewAvailable(Context context, Throwable t) {
-        sWebViewUnavailable = !hasWebViewFeature(context) && checkCauseWasUnsupportedOperation(t);
-    }
-
-    /**
-     * After calling determineIfWebViewAvailable, this returns whether a WebView is available on the
-     * device and wheter the test can rely on it.
-     * @return True iff. PackageManager determined that there is no WebView on the device and the
-     *         exception thrown from android.webkit.* was UnsupportedOperationException.
+     * @return true if the device is supposed to have a WebView implementation.
      */
     public static boolean isWebViewAvailable() {
-        return !sWebViewUnavailable;
+        return FeatureUtil.hasSystemFeature(PackageManager.FEATURE_WEBVIEW);
     }
 
-    private static boolean hasWebViewFeature(Context context) {
-        // Query the system property that determins if there is a functional WebView on the device.
-        PackageManager pm = context.getPackageManager();
-        return pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW);
-    }
-
-    private static boolean checkCauseWasUnsupportedOperation(Throwable t) {
-        if (t == null) return false;
-        while (t.getCause() != null) {
-            t = t.getCause();
-        }
-        return t instanceof UnsupportedOperationException;
-    }
-
-    /**
-     * Some CTS tests (by design) first use android.webkit.* from a background thread. This helper
-     * allows the test to catch the UnsupportedOperationException from that background thread, and
-     * then query the result from the test main thread.
-     */
-    public static class NullWebViewFromThreadExceptionHandler
-            implements Thread.UncaughtExceptionHandler {
-        private Throwable mPendingException;
-
-        @Override
-        public void uncaughtException(Thread t, Throwable e) {
-            mPendingException = e;
-        }
-
-        public boolean isWebViewAvailable(Context context) {
-            return hasWebViewFeature(context) ||
-                    !checkCauseWasUnsupportedOperation(mPendingException);
-        }
-    }
 }
diff --git a/hostsidetests/appcompat/compatchanges/Android.bp b/hostsidetests/appcompat/compatchanges/Android.bp
index ca0867b..85fb5bb 100644
--- a/hostsidetests/appcompat/compatchanges/Android.bp
+++ b/hostsidetests/appcompat/compatchanges/Android.bp
@@ -28,4 +28,13 @@
         "vts",
         "general-tests",
     ],
+    java_resources: [":cts-global-compat-config"],
 }
+
+global_compat_config {
+    name: "cts-global-compat-config",
+    filename: "cts_all_compat_config.xml",
+}
+
+
+
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
new file mode 100644
index 0000000..f9bdc22
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appcompat;
+
+ import static com.google.common.truth.Truth.assertThat;
+
+
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.List;
+ import java.util.Objects;
+ import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+ import java.util.stream.Collectors;
+
+ import android.compat.cts.CompatChangeGatingTestCase;
+
+ import org.w3c.dom.Document;
+ import org.w3c.dom.Element;
+ import org.w3c.dom.NamedNodeMap;
+ import org.w3c.dom.Node;
+ import org.w3c.dom.NodeList;
+
+ import javax.xml.parsers.DocumentBuilder;
+ import javax.xml.parsers.DocumentBuilderFactory;
+
+public final class CompatChangesValidConfigTest extends CompatChangeGatingTestCase {
+
+    private static class Change {
+        private static final Pattern CHANGE_REGEX = Pattern.compile("^ChangeId\\((?<changeId>[0-9]+)"
+                                                + "(; name=(?<changeName>[^;]+))?"
+                                                + "(; enableAfterTargetSdk=(?<targetSdk>[0-9]+))?"
+                                                + "(; (?<disabled>disabled))?"
+                                                + "(; packageOverrides=(?<overrides>[^\\)]+))?"
+                                                + "\\)");
+        long changeId;
+        String changeName;
+        int targetSdk;
+        boolean disabled;
+        boolean hasOverrides;
+
+        private Change(long changeId, String changeName, int targetSdk, boolean disabled, boolean hasOverrides) {
+            this.changeId = changeId;
+            this.changeName = changeName;
+            this.targetSdk = targetSdk;
+            this.disabled = disabled;
+            this.hasOverrides = hasOverrides;
+        }
+
+        static Change fromString(String line) {
+            long changeId = 0;
+            String changeName;
+            int targetSdk = 0;
+            boolean disabled = false;
+            boolean hasOverrides = false;
+
+            Matcher matcher = CHANGE_REGEX.matcher(line);
+            if (!matcher.matches()) {
+                throw new RuntimeException("Could not match line " + line);
+            }
+
+            try {
+                changeId = Long.parseLong(matcher.group("changeId"));
+            } catch (NumberFormatException e) {
+                throw new RuntimeException("No or invalid changeId!", e);
+            }
+            changeName = matcher.group("changeName");
+            String targetSdkAsString = matcher.group("targetSdk");
+            if (targetSdkAsString != null) {
+                try {
+                    targetSdk = Integer.parseInt(targetSdkAsString);
+                } catch (NumberFormatException e) {
+                    throw new RuntimeException("Invalid targetSdk for change!", e);
+                }
+            }
+            if (matcher.group("disabled") != null) {
+                disabled = true;
+            }
+            if (matcher.group("overrides") != null) {
+                hasOverrides = true;
+            }
+            return new Change(changeId, changeName, targetSdk, disabled, hasOverrides);
+        }
+
+        static Change fromNode(Node node) {
+            Element element = (Element) node;
+            long changeId = Long.parseLong(element.getAttribute("id"));
+            String changeName = element.getAttribute("name");
+            int targetSdk = 0;
+            if (element.hasAttribute("enableAfterTargetSdk")) {
+                targetSdk = Integer.parseInt(element.getAttribute("enableAfterTargetSdk"));
+            }
+            boolean disabled = false;
+            if (element.hasAttribute("disabled")) {
+                disabled = true;
+            }
+            boolean hasOverrides = false;
+            return new Change(changeId, changeName, targetSdk, disabled, hasOverrides);
+        }
+        @Override
+        public int hashCode() {
+            return Objects.hash(changeId, changeName, targetSdk, disabled, hasOverrides);
+        }
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (other == null || !(other instanceof Change)) {
+                return false;
+            }
+            Change that = (Change) other;
+            return this.changeId == that.changeId
+                && Objects.equals(this.changeName, that.changeName)
+                && this.targetSdk == that.targetSdk
+                && this.disabled == that.disabled
+                && this.hasOverrides == that.hasOverrides;
+        }
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("ChangeId(" + changeId);
+            if (changeName != null && !changeName.isEmpty()) {
+                sb.append("; name=" + changeName);
+            }
+            if (targetSdk != 0) {
+                sb.append("; enableAfterTargetSdk=" + targetSdk);
+            }
+            if (disabled) {
+                sb.append("; disabled");
+            }
+            if (hasOverrides) {
+                sb.append("; packageOverrides={something}");
+            }
+            sb.append(")");
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Get the on device compat config.
+     */
+    private List<Change> getOnDeviceCompatConfig() throws Exception {
+        String config = runCommand("dumpsys platform_compat");
+        return Arrays.stream(config.split("\n"))
+                .map(Change::fromString)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Parse the expected (i.e. defined in platform) config xml.
+     */
+    private List<Change> getExpectedCompatConfig() throws Exception {
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        DocumentBuilder db = dbf.newDocumentBuilder();
+        Document dom = db.parse(getClass().getResourceAsStream("/cts_all_compat_config.xml"));
+        Element root = dom.getDocumentElement();
+        NodeList changeNodes = root.getElementsByTagName("compat-change");
+        List<Change> changes = new ArrayList<>();
+        for (int nodeIdx = 0; nodeIdx < changeNodes.getLength(); ++nodeIdx) {
+            changes.add(Change.fromNode(changeNodes.item(nodeIdx)));
+        }
+        return changes;
+    }
+
+    /**
+     * Check that there are no overrides.
+     */
+    public void testNoOverrides() throws Exception {
+        for (Change c : getOnDeviceCompatConfig()) {
+            assertThat(c.hasOverrides).isFalse();
+        }
+    }
+
+    /**
+     * Check that the on device config contains all the expected change ids defined in the platform.
+     * The device may contain extra changes, but none may be removed.
+     */
+    public void testDeviceContainsExpectedConfig() throws Exception {
+        assertThat(getOnDeviceCompatConfig()).containsAllIn(getExpectedCompatConfig());
+    }
+
+}
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index 81fa0d3..81a053e 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -5,6 +5,7 @@
 per-file AdoptableHostTest.java = jsharkey@google.com
 per-file ApexSignatureVerificationTest.java = dariofreni@google.com
 per-file ApplicationVisibilityTest.java = toddke@google.com
+per-file AppDataIsolationTests.java = rickywai@google.com
 per-file AppOpsTest.java = moltmann@google.com
 per-file AppSecurityTests.java = cbrubaker@google.com
 per-file AuthBoundKeyTest.java = cbrubaker@google.com
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
index 633d1ac..3406bd1 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
@@ -16,7 +16,10 @@
 
 package android.appsecurity.cts;
 
-import static org.junit.Assert.assertNotNull;
+import static android.appsecurity.cts.Utils.waitForBootCompleted;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
@@ -32,6 +35,8 @@
 public class AppDataIsolationTests extends BaseAppSecurityTest {
 
     private static final String APPA_APK = "CtsAppDataIsolationAppA.apk";
+    private static final String APP_SHARED_A_APK = "CtsAppDataIsolationAppSharedA.apk";
+    private static final String APP_DIRECT_BOOT_A_APK = "CtsAppDataIsolationAppDirectBootA.apk";
     private static final String APPA_PKG = "com.android.cts.appdataisolation.appa";
     private static final String APPA_CLASS =
             "com.android.cts.appdataisolation.appa.AppATests";
@@ -46,28 +51,46 @@
             "testAppACurProfileDataAccessible";
     private static final String APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE =
             "testAppARefProfileDataNotAccessible";
+    private static final String APPA_METHOD_UNLOCK_DEVICE_AND_VERIFY_CE_DE_EXIST =
+            "testAppAUnlockDeviceAndVerifyCeDeDataExist";
+    private static final String APPA_METHOD_CANNOT_ACCESS_APPB_DIR = "testCannotAccessAppBDataDir";
+
+    private static final String APPA_METHOD_TEST_UNLOCK_DEVICE =
+            "testUnlockDevice";
 
     private static final String APPB_APK = "CtsAppDataIsolationAppB.apk";
+    private static final String APP_SHARED_B_APK = "CtsAppDataIsolationAppSharedB.apk";
     private static final String APPB_PKG = "com.android.cts.appdataisolation.appb";
     private static final String APPB_CLASS =
             "com.android.cts.appdataisolation.appb.AppBTests";
     private static final String APPB_METHOD_CANNOT_ACCESS_APPA_DIR = "testCannotAccessAppADataDir";
+    private static final String APPB_METHOD_CAN_ACCESS_APPA_DIR = "testCanAccessAppADataDir";
+
+    private static final String FBE_MODE_NATIVE = "native";
+    private static final String FBE_MODE_EMULATED = "emulated";
 
     @Before
     public void setUp() throws Exception {
         Utils.prepareSingleUser(getDevice());
         getDevice().uninstallPackage(APPA_PKG);
+        getDevice().uninstallPackage(APPB_PKG);
     }
 
     @After
     public void tearDown() throws Exception {
         getDevice().uninstallPackage(APPA_PKG);
+        getDevice().uninstallPackage(APPB_PKG);
     }
 
     private void forceStopPackage(String packageName) throws Exception {
         getDevice().executeShellCommand("am force-stop " + packageName);
     }
 
+    private void reboot() throws Exception {
+        getDevice().reboot();
+        waitForBootCompleted(getDevice());
+    }
+
     @Test
     public void testAppAbleToAccessItsDataAfterForceStop() throws Exception {
         // Install AppA and verify no data stored
@@ -93,7 +116,123 @@
         runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
         runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
         runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE);
+    }
 
+    @Test
+    public void testAppAbleToAccessItsDataAfterReboot() throws Exception {
+        // Install AppA and verify no data stored
+        new InstallMultiple().addApk(APPA_APK).run();
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_DOES_NOT_EXIST);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_DOES_NOT_EXIST);
+
+        // Create data in CE and DE storage
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_CE_DE_DATA);
+
+        // Verify CE and DE storage contains data, cur profile is accessible and ref profile is
+        // not accessible
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_EXISTS);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE);
+
+        // Reboot and verify CE and DE storage contains data, cur profile is accessible and
+        // ref profile is not accessible
+        reboot();
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_EXISTS);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE);
+    }
+
+    private boolean isFbeModeEmulated() throws Exception {
+        String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
+        if (mode.equals(FBE_MODE_EMULATED)) {
+            return true;
+        } else if (mode.equals(FBE_MODE_NATIVE)) {
+            return false;
+        }
+        fail("Unknown FBE mode: " + mode);
+        return false;
+    }
+
+    @Test
+    public void testDirectBootModeWorks() throws Exception {
+        // Install AppA and verify no data stored
+        new InstallMultiple().addApk(APP_DIRECT_BOOT_A_APK).run();
+        new InstallMultiple().addApk(APPB_APK).run();
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_DOES_NOT_EXIST);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_DOES_NOT_EXIST);
+
+        // Create data in CE and DE storage
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_CE_DE_DATA);
+
+        // Verify CE and DE storage contains data, cur profile is accessible and ref profile is
+        // not accessible
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_EXISTS);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE);
+
+        try {
+            // Setup screenlock
+            getDevice().executeShellCommand("settings put global require_password_to_decrypt 0");
+            getDevice().executeShellCommand("locksettings set-disabled false");
+            getDevice().executeShellCommand("locksettings set-pin 12345");
+
+            // Give enough time for vold to update keys
+            Thread.sleep(15000);
+
+            // Follow DirectBootHostTest, reboot system into known state with keys ejected
+            if (isFbeModeEmulated()) {
+                final String res = getDevice().executeShellCommand("sm set-emulate-fbe true");
+                assertThat(res).contains("Emulation not supported");
+                getDevice().waitForDeviceNotAvailable(30000);
+                getDevice().waitForDeviceOnline(120000);
+            } else {
+                getDevice().rebootUntilOnline();
+            }
+            waitForBootCompleted(getDevice());
+
+            // Verify DE data is still readable and writeable, while CE data is not accessible
+            runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
+            runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_DOES_NOT_EXIST);
+            runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
+            runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE);
+            // Verify cannot access other apps data
+            runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CANNOT_ACCESS_APPB_DIR);
+
+            // Unlock device and verify CE DE data still exist, without killing the process, as
+            // test process usually will be killed after the test
+            runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_UNLOCK_DEVICE_AND_VERIFY_CE_DE_EXIST);
+
+            // Reboot and verify CE and DE storage contains data, cur profile is accessible and
+            // ref profile is not accessible
+            runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_EXISTS);
+            runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
+            runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
+            runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE);
+        } finally {
+            try {
+                // Always try to unlock first, then clear screenlock setting
+                try {
+                    runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_UNLOCK_DEVICE);
+                } catch (Exception e) {}
+                getDevice().executeShellCommand("locksettings clear --old 12345");
+                getDevice().executeShellCommand("locksettings set-disabled true");
+                getDevice().executeShellCommand(
+                        "settings delete global require_password_to_decrypt");
+            } finally {
+                // Get ourselves back into a known-good state
+                if (isFbeModeEmulated()) {
+                    getDevice().executeShellCommand("sm set-emulate-fbe false");
+                    getDevice().waitForDeviceNotAvailable(30000);
+                    getDevice().waitForDeviceOnline();
+                } else {
+                    getDevice().rebootUntilOnline();
+                }
+                getDevice().waitForDeviceAvailable();
+            }
+        }
     }
 
     @Test
@@ -118,4 +257,12 @@
 
         runDeviceTests(APPB_PKG, APPB_CLASS, APPB_METHOD_CANNOT_ACCESS_APPA_DIR);
     }
+
+    @Test
+    public void testSharedAppAbleToAccessOtherAppDataDir() throws Exception {
+        new InstallMultiple().addApk(APP_SHARED_A_APK).run();
+        new InstallMultiple().addApk(APP_SHARED_B_APK).run();
+
+        runDeviceTests(APPB_PKG, APPB_CLASS, APPB_METHOD_CAN_ACCESS_APPA_DIR);
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java
index 9687e97..1232edd 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java
@@ -48,7 +48,7 @@
         assertNotNull(mBuildHelper);
         assertNull(
                 getDevice().installPackage(mBuildHelper.getTestFile(DEVICE_IDENTIFIER_APK), false,
-                        false));
+                        true));
     }
 
     @Override
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
index 848aeb8..e415ec7 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
@@ -17,7 +17,7 @@
     defaults: ["cts_support_defaults"],
     srcs: ["common/src/**/*.java", "AppA/src/**/*.java"],
     sdk_version: "current",
-    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng"],
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator"],
     libs: ["android.test.base.stubs"],
     // tag this module as a cts test artifact
     test_suites: [
@@ -33,6 +33,46 @@
 }
 
 android_test_helper_app {
+    name: "CtsAppDataIsolationAppSharedA",
+    defaults: ["cts_support_defaults"],
+    srcs: ["common/src/**/*.java", "AppA/src/**/*.java"],
+    sdk_version: "current",
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator"],
+    libs: ["android.test.base.stubs"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    dex_preopt: {
+        enabled: false,
+    },
+    manifest: "AppA/AndroidManifest_shared.xml",
+}
+
+android_test_helper_app {
+    name: "CtsAppDataIsolationAppDirectBootA",
+    defaults: ["cts_support_defaults"],
+    srcs: ["common/src/**/*.java", "AppA/src/**/*.java"],
+    sdk_version: "current",
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator"],
+    libs: ["android.test.base.stubs"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    dex_preopt: {
+        enabled: false,
+    },
+    manifest: "AppA/AndroidManifest_directboot.xml",
+}
+
+android_test_helper_app {
     name: "CtsAppDataIsolationAppB",
     defaults: ["cts_support_defaults"],
     srcs: ["common/src/**/*.java", "AppB/src/**/*.java"],
@@ -51,3 +91,23 @@
     },
     manifest: "AppB/AndroidManifest.xml",
 }
+
+android_test_helper_app {
+    name: "CtsAppDataIsolationAppSharedB",
+    defaults: ["cts_support_defaults"],
+    srcs: ["common/src/**/*.java", "AppB/src/**/*.java"],
+    sdk_version: "current",
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng"],
+    libs: ["android.test.base.stubs"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    dex_preopt: {
+        enabled: false,
+    },
+    manifest: "AppB/AndroidManifest_shared.xml",
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_directboot.xml b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_directboot.xml
new file mode 100644
index 0000000..40ba10c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_directboot.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+       package="com.android.cts.appdataisolation.appa">
+
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+    <application android:directBootAware="true">
+        <uses-library android:name="android.test.runner" />
+        <receiver android:name=".DummyReceiver"
+                  android:directBootAware="true"
+                  android:exported="true">
+        </receiver>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.appdataisolation.appa"
+                     android:label="Test app data isolation."/>
+</manifest>
diff --git a/tests/appsearch/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_shared.xml
similarity index 65%
copy from tests/appsearch/AndroidManifest.xml
copy to hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_shared.xml
index 7eac46b..da23a82 100644
--- a/tests/appsearch/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_shared.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -14,13 +14,15 @@
   ~ 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="com.android.cts.appsearch" >
-    <application android:label="CtsAppSearchTestCases">
-        <uses-library android:name="android.test.runner"/>
+       package="com.android.cts.appdataisolation.appa"
+       android:sharedUserId="com.android.cts.appdataisolation.shareduid">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
     </application>
+
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.appsearch"
-                     android:label="CtsAppSearchTestCases"/>
+                     android:targetPackage="com.android.cts.appdataisolation.appa"
+                     android:label="Test app data isolation."/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
index 5cdc753..493f198 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
@@ -16,32 +16,46 @@
 
 package com.android.cts.appdataisolation.appa;
 
+import static com.android.cts.appdataisolation.common.FileUtils.assertDirDoesNotExist;
+import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsAccessible;
+import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsNotAccessible;
+import static com.android.cts.appdataisolation.common.FileUtils.assertFileDoesNotExist;
+import static com.android.cts.appdataisolation.common.FileUtils.assertFileExists;
+import static com.android.cts.appdataisolation.common.FileUtils.touchFile;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.os.RemoteException;
+import android.support.test.uiautomator.UiDevice;
+import android.view.KeyEvent;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.cts.appdataisolation.common.FileUtils;
-
 import org.junit.Before;
 import org.junit.Test;
 
-import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /*
  * This class is a helper class for AppDataIsolationTests to assert and check data stored in app.
  */
 @SmallTest
 public class AppATests {
 
+    private static final String APPB_PKG = "com.android.cts.appdataisolation.appb";
+
     private final static String CE_DATA_FILE_NAME = "ce_data_file";
     private final static String DE_DATA_FILE_NAME = "de_data_file";
 
-    private static final String JAVA_FILE_PERMISSION_DENIED_MSG =
-            "open failed: EACCES (Permission denied)";
-    private static final String JAVA_FILE_NOT_FOUND_MSG =
-            "open failed: ENOENT (No such file or directory)";
-
     private Context mContext;
+    private UiDevice mDevice;
     private String mCePath;
     private String mDePath;
 
@@ -50,51 +64,114 @@
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mCePath = mContext.getApplicationInfo().dataDir;
         mDePath = mContext.getApplicationInfo().deviceProtectedDataDir;
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
     }
 
     @Test
-    public void testCreateCeDeAppData() throws IOException {
-        FileUtils.assertFileDoesNotExist(mCePath, CE_DATA_FILE_NAME);
-        FileUtils.assertFileDoesNotExist(mCePath, DE_DATA_FILE_NAME);
-        FileUtils.assertFileDoesNotExist(mDePath, CE_DATA_FILE_NAME);
-        FileUtils.assertFileDoesNotExist(mDePath, DE_DATA_FILE_NAME);
+    public void testCreateCeDeAppData() throws Exception {
+        assertFileDoesNotExist(mCePath, CE_DATA_FILE_NAME);
+        assertFileDoesNotExist(mCePath, DE_DATA_FILE_NAME);
+        assertFileDoesNotExist(mDePath, CE_DATA_FILE_NAME);
+        assertFileDoesNotExist(mDePath, DE_DATA_FILE_NAME);
 
-        FileUtils.touchFile(mCePath, CE_DATA_FILE_NAME);
-        FileUtils.touchFile(mDePath, DE_DATA_FILE_NAME);
+        touchFile(mCePath, CE_DATA_FILE_NAME);
+        touchFile(mDePath, DE_DATA_FILE_NAME);
 
-        FileUtils.assertFileExists(mCePath, CE_DATA_FILE_NAME);
-        FileUtils.assertFileDoesNotExist(mCePath, DE_DATA_FILE_NAME);
-        FileUtils.assertFileDoesNotExist(mDePath, CE_DATA_FILE_NAME);
-        FileUtils.assertFileExists(mDePath, DE_DATA_FILE_NAME);
+        assertFileExists(mCePath, CE_DATA_FILE_NAME);
+        assertFileDoesNotExist(mCePath, DE_DATA_FILE_NAME);
+        assertFileDoesNotExist(mDePath, CE_DATA_FILE_NAME);
+        assertFileExists(mDePath, DE_DATA_FILE_NAME);
     }
 
     @Test
     public void testAppACeDataExists() {
-        FileUtils.assertFileExists(mCePath, CE_DATA_FILE_NAME);
+        assertFileExists(mCePath, CE_DATA_FILE_NAME);
     }
 
     @Test
     public void testAppACeDataDoesNotExist() {
-        FileUtils.assertFileDoesNotExist(mCePath, CE_DATA_FILE_NAME);
+        assertFileDoesNotExist(mCePath, CE_DATA_FILE_NAME);
     }
 
     @Test
     public void testAppADeDataExists() {
-        FileUtils.assertFileExists(mDePath, DE_DATA_FILE_NAME);
+        assertFileExists(mDePath, DE_DATA_FILE_NAME);
     }
 
     @Test
     public void testAppADeDataDoesNotExist() {
-        FileUtils.assertFileDoesNotExist(mDePath, DE_DATA_FILE_NAME);
+        assertFileDoesNotExist(mDePath, DE_DATA_FILE_NAME);
     }
 
     @Test
     public void testAppACurProfileDataAccessible() {
-        FileUtils.assertDirIsAccessible("/data/misc/profiles/cur/0/" + mContext.getPackageName());
+        assertDirIsAccessible("/data/misc/profiles/cur/0/" + mContext.getPackageName());
     }
 
     @Test
     public void testAppARefProfileDataNotAccessible() {
-        FileUtils.assertDirIsNotAccessible("/data/misc/profiles/ref");
+        assertDirIsNotAccessible("/data/misc/profiles/ref");
+    }
+
+    @Test
+    public void testCannotAccessAppBDataDir() throws Exception {
+        ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(APPB_PKG,
+                0);
+        assertDirDoesNotExist(applicationInfo.dataDir);
+        assertDirDoesNotExist(applicationInfo.deviceProtectedDataDir);
+        assertDirDoesNotExist("/data/data/" + APPB_PKG);
+        assertDirDoesNotExist("/data/misc/profiles/cur/0/" + APPB_PKG);
+        assertDirIsNotAccessible("/data/misc/profiles/ref");
+    }
+
+    @Test
+    public void testUnlockDevice() throws Exception {
+        mDevice.wakeUp();
+        mDevice.waitForIdle();
+        mDevice.pressMenu();
+        mDevice.waitForIdle();
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_1);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_2);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_3);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_4);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_5);
+        mDevice.waitForIdle();
+        mDevice.pressEnter();
+        mDevice.waitForIdle();
+        mDevice.pressHome();
+        mDevice.waitForIdle();
+    }
+
+    @Test
+    public void testAppAUnlockDeviceAndVerifyCeDeDataExist() throws Exception {
+
+        final CountDownLatch unlocked = new CountDownLatch(1);
+        final CountDownLatch bootCompleted = new CountDownLatch(1);
+        final BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                switch(intent.getAction()) {
+                    case Intent.ACTION_USER_UNLOCKED:
+                        unlocked.countDown();
+                        break;
+                    case Intent.ACTION_BOOT_COMPLETED:
+                        bootCompleted.countDown();
+                        break;
+                }
+            }
+        };
+        mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+        mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+
+        testUnlockDevice();
+
+        assertTrue("User not unlocked", unlocked.await(1, TimeUnit.MINUTES));
+        assertTrue("No locked boot complete", bootCompleted.await(1, TimeUnit.MINUTES));
+
+        // The test app process should be still running, make sure CE DE now is available
+        testAppACeDataExists();
+        testAppADeDataExists();
+        testAppACurProfileDataAccessible();
+        testAppARefProfileDataNotAccessible();
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/DummyReceiver.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/DummyReceiver.java
new file mode 100644
index 0000000..cec7317
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/DummyReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appdataisolation.appa;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+// Dummy receiver to make this app as direct boot aware.
+public class DummyReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+
+    }
+}
diff --git a/tests/appsearch/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/AndroidManifest_shared.xml
similarity index 65%
rename from tests/appsearch/AndroidManifest.xml
rename to hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/AndroidManifest_shared.xml
index 7eac46b..8e0ecd8 100644
--- a/tests/appsearch/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/AndroidManifest_shared.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -14,13 +14,15 @@
   ~ 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="com.android.cts.appsearch" >
-    <application android:label="CtsAppSearchTestCases">
-        <uses-library android:name="android.test.runner"/>
+       package="com.android.cts.appdataisolation.appb"
+       android:sharedUserId="com.android.cts.appdataisolation.shareduid">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
     </application>
+
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.appsearch"
-                     android:label="CtsAppSearchTestCases"/>
+                     android:targetPackage="com.android.cts.appdataisolation.appb"
+                     android:label="Test app data isolation."/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java
index 84eea95..2c1dab2 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java
@@ -16,6 +16,11 @@
 
 package com.android.cts.appdataisolation.appb;
 
+import static com.android.cts.appdataisolation.common.FileUtils.assertDirDoesNotExist;
+import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsAccessible;
+import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsNotAccessible;
+import static com.android.cts.appdataisolation.common.FileUtils.assertFileIsAccessible;
+
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -23,21 +28,16 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.cts.appdataisolation.common.FileUtils;
-
 import org.junit.Before;
 import org.junit.Test;
 
+import java.io.IOException;
+
 @SmallTest
 public class AppBTests {
 
     private static final String APPA_PKG = "com.android.cts.appdataisolation.appa";
 
-    private static final String JAVA_FILE_PERMISSION_DENIED_MSG =
-            "open failed: EACCES (Permission denied)";
-    private static final String JAVA_FILE_NOT_FOUND_MSG =
-            "open failed: ENOENT (No such file or directory)";
-
     private Context mContext;
 
     @Before
@@ -49,10 +49,21 @@
     public void testCannotAccessAppADataDir() throws NameNotFoundException {
         ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(APPA_PKG,
                 0);
-        FileUtils.assertDirDoesNotExist(applicationInfo.dataDir);
-        FileUtils.assertDirDoesNotExist(applicationInfo.deviceProtectedDataDir);
-        FileUtils.assertDirDoesNotExist("/data/data/" + APPA_PKG);
-        FileUtils.assertDirDoesNotExist("/data/misc/profiles/cur/0/" + APPA_PKG);
-        FileUtils.assertDirIsNotAccessible("/data/misc/profiles/ref");
+        assertDirDoesNotExist(applicationInfo.dataDir);
+        assertDirDoesNotExist(applicationInfo.deviceProtectedDataDir);
+        assertDirDoesNotExist("/data/data/" + APPA_PKG);
+        assertDirDoesNotExist("/data/misc/profiles/cur/0/" + APPA_PKG);
+        assertDirIsNotAccessible("/data/misc/profiles/ref");
+    }
+
+    @Test
+    public void testCanAccessAppADataDir() throws NameNotFoundException, IOException {
+        ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(APPA_PKG,
+                0);
+        assertDirIsAccessible(applicationInfo.dataDir);
+        assertDirIsAccessible(applicationInfo.deviceProtectedDataDir);
+        assertDirIsAccessible("/data/data/" + APPA_PKG);
+        assertFileIsAccessible("/data/misc/profiles/cur/0/" + APPA_PKG + "/primary.prof");
+        assertDirIsNotAccessible("/data/misc/profiles/ref");
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/OWNERS b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/OWNERS
new file mode 100644
index 0000000..e39db39
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 315013
+rickywai@google.com
+alanstokes@google.com
+zezeozue@google.com
+nandana@google.com
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
index 890174c..6772714 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
@@ -66,6 +66,12 @@
         assertFileDoesNotExist(path, "FILE_DOES_NOT_EXIST");
     }
 
+    public static void assertFileIsAccessible(String path) throws IOException {
+        try (FileInputStream is = new FileInputStream(path)) {
+            is.read();
+        }
+    }
+
     public static void assertFileDoesNotExist(String path, String name) {
         // Make sure parent dir exists
         File directory = new File(path);
diff --git a/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/AndroidManifest.xml
index bbdcd1e..b683ba1 100644
--- a/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/AndroidManifest.xml
@@ -19,6 +19,8 @@
         package="android.appsecurity.cts.deviceids">
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
 
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
     <application/>
 
     <instrumentation
diff --git a/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/src/android/appsecurity/cts/deviceids/DeviceIdentifierAppOpTest.java b/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/src/android/appsecurity/cts/deviceids/DeviceIdentifierAppOpTest.java
index 71cd7d2..0534895 100644
--- a/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/src/android/appsecurity/cts/deviceids/DeviceIdentifierAppOpTest.java
+++ b/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/src/android/appsecurity/cts/deviceids/DeviceIdentifierAppOpTest.java
@@ -20,7 +20,10 @@
 import static junit.framework.Assert.fail;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
 
@@ -43,9 +46,9 @@
 
     @Test
     public void testAccessToDeviceIdentifiersWithAppOp() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
         TelephonyManager telephonyManager =
-                (TelephonyManager) InstrumentationRegistry.getContext().getSystemService(
-                        Context.TELEPHONY_SERVICE);
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         try {
             assertEquals(String.format(DEVICE_ID_WITH_APPOP_ERROR_MESSAGE, "getDeviceId"),
                     ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
@@ -70,6 +73,21 @@
             assertEquals(String.format(DEVICE_ID_WITH_APPOP_ERROR_MESSAGE, "Build#getSerial"),
                     ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial),
                     Build.getSerial());
+            if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+                SubscriptionManager subscriptionManager =
+                        (SubscriptionManager) context.getSystemService(
+                                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+                int subId = subscriptionManager.getDefaultSubscriptionId();
+                if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    SubscriptionInfo expectedSubInfo =
+                            ShellIdentityUtils.invokeMethodWithShellPermissions(subscriptionManager,
+                                    (sm) -> sm.getActiveSubscriptionInfo(subId));
+                    SubscriptionInfo actualSubInfo = subscriptionManager.getActiveSubscriptionInfo(
+                            subId);
+                    assertEquals(String.format(DEVICE_ID_WITH_APPOP_ERROR_MESSAGE, "getIccId"),
+                            expectedSubInfo.getIccId(), actualSubInfo.getIccId());
+                }
+            }
         } catch (SecurityException e) {
             fail("An app with the READ_DEVICE_IDENTIFIERS app op set to allowed must be able to "
                     + "access the device identifiers; caught SecurityException instead: "
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/WebViewTestActivity.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/WebViewTestActivity.java
index aae2b65..5ee2ea1 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/WebViewTestActivity.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/WebViewTestActivity.java
@@ -29,13 +29,13 @@
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        try {
-            super.onCreate(savedInstanceState);
-            mWebView = new WebView(this);
-            setContentView(mWebView);
-        } catch (Exception e) {
-            NullWebViewUtils.determineIfWebViewAvailable(this, e);
-        }
+        super.onCreate(savedInstanceState);
+
+        // Only create the WebView if the device is supposed to have a WebView implementation.
+        if (!NullWebViewUtils.isWebViewAvailable()) return;
+
+        mWebView = new WebView(this);
+        setContentView(mWebView);
     }
 
     public WebView getWebView() {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationHiddenParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationHiddenParentTest.java
index aba1e59..9e5e8d3 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationHiddenParentTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationHiddenParentTest.java
@@ -28,8 +28,10 @@
     private DevicePolicyManager mParentDevicePolicyManager;
     private PackageManager mPackageManager;
 
-    private static final String SYSTEM_PACKAGE_TO_HIDE = "com.google.android.youtube";
-    private static final String NON_SYSTEM_PACKAGE_TO_HIDE = "com.android.cts.permissionapp";
+    private static final String SYSTEM_PACKAGE_TO_HIDE = "com.android.keychain";
+    private static final String NON_SYSTEM_NON_INSTALLED_PACKAGE = "com.android.cts.permissionapp";
+    private static final String NON_SYSTEM_INSTALLED_PACKAGE =
+            "com.android.cts.deviceandprofileowner";
 
     @Override
     protected void setUp() throws Exception {
@@ -43,6 +45,13 @@
         assertThat(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).isTrue();
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        mParentDevicePolicyManager.setApplicationHidden(ADMIN_RECEIVER_COMPONENT,
+                SYSTEM_PACKAGE_TO_HIDE, false);
+        super.tearDown();
+    }
+
     public void testSetApplicationHidden_systemPackage()
             throws PackageManager.NameNotFoundException {
         assertThat(mPackageManager.getPackageInfo(SYSTEM_PACKAGE_TO_HIDE, 0)).isNotNull();
@@ -64,15 +73,51 @@
     public void testSetApplicationHidden_nonSystemPackage() {
         assertThrows(IllegalArgumentException.class, () -> {
             mParentDevicePolicyManager.setApplicationHidden(ADMIN_RECEIVER_COMPONENT,
-                    NON_SYSTEM_PACKAGE_TO_HIDE, true);
+                    NON_SYSTEM_NON_INSTALLED_PACKAGE, true);
             mParentDevicePolicyManager.isApplicationHidden(ADMIN_RECEIVER_COMPONENT,
-                    NON_SYSTEM_PACKAGE_TO_HIDE);
+                    NON_SYSTEM_NON_INSTALLED_PACKAGE);
         });
         assertThrows(IllegalArgumentException.class, () -> {
             mParentDevicePolicyManager.setApplicationHidden(ADMIN_RECEIVER_COMPONENT,
-                    NON_SYSTEM_PACKAGE_TO_HIDE, false);
+                    NON_SYSTEM_NON_INSTALLED_PACKAGE, false);
             mParentDevicePolicyManager.isApplicationHidden(ADMIN_RECEIVER_COMPONENT,
-                    NON_SYSTEM_PACKAGE_TO_HIDE);
+                    NON_SYSTEM_NON_INSTALLED_PACKAGE);
         });
     }
+
+    public void testSetApplicationHidden_nonSystemPackageStackTrace() {
+        StackTraceElement[] stackTrace1 = new StackTraceElement[0];
+        StackTraceElement[] stackTrace2 = new StackTraceElement[0];
+        String message1 = "";
+        String message2 = "";
+
+        // Scenario 1: Non-system non-installed package
+        try {
+            mParentDevicePolicyManager.setApplicationHidden(ADMIN_RECEIVER_COMPONENT,
+                    NON_SYSTEM_NON_INSTALLED_PACKAGE, true);
+        } catch (IllegalArgumentException e) {
+            stackTrace1 = e.getStackTrace();
+            message1 = e.getMessage();
+        }
+
+        // Scenario 2: Non-system installed package
+        try {
+            mParentDevicePolicyManager.setApplicationHidden(ADMIN_RECEIVER_COMPONENT,
+                    NON_SYSTEM_INSTALLED_PACKAGE, true);
+        } catch (IllegalArgumentException e) {
+            stackTrace2 = e.getStackTrace();
+            message2 = e.getMessage();
+        }
+
+        // Ensure the messages and stack traces of both scenarios are equal
+        assertThat(message1).isEqualTo(message2);
+        assertThat(stackTrace1.length).isEqualTo(stackTrace2.length);
+        for (int i = 0; i < stackTrace1.length; i++) {
+            if (stackTrace1[i].getClassName().equals(this.getClass().getName())) {
+                continue;
+            }
+            assertThat(stackTrace1[i].toString()).isEqualTo(stackTrace2[i].toString());
+        }
+    }
+
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdentifiersTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdentifiersTest.java
index ebaf592..855958c 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdentifiersTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdentifiersTest.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Build;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
@@ -69,6 +71,19 @@
             assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "Build#getSerial"),
                     ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial),
                     Build.getSerial());
+            SubscriptionManager subscriptionManager =
+                    (SubscriptionManager) mContext.getSystemService(
+                            Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+            int subId = subscriptionManager.getDefaultSubscriptionId();
+            if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                SubscriptionInfo expectedSubInfo =
+                        ShellIdentityUtils.invokeMethodWithShellPermissions(subscriptionManager,
+                                (sm) -> sm.getActiveSubscriptionInfo(subId));
+                SubscriptionInfo actualSubInfo = subscriptionManager.getActiveSubscriptionInfo(
+                        subId);
+                assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getIccId"),
+                        expectedSubInfo.getIccId(), actualSubInfo.getIccId());
+            }
         } catch (SecurityException e) {
             fail("The profile owner with the READ_PHONE_STATE permission must be able to access "
                     + "the device IDs: " + e);
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java
index e5ea597..2370cd0 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Build;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
@@ -65,6 +67,19 @@
             assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "Build#getSerial"),
                     ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial),
                     Build.getSerial());
+            SubscriptionManager subscriptionManager =
+                    (SubscriptionManager) mContext.getSystemService(
+                            Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+            int subId = subscriptionManager.getDefaultSubscriptionId();
+            if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                SubscriptionInfo expectedSubInfo =
+                        ShellIdentityUtils.invokeMethodWithShellPermissions(subscriptionManager,
+                                (sm) -> sm.getActiveSubscriptionInfo(subId));
+                SubscriptionInfo actualSubInfo = subscriptionManager.getActiveSubscriptionInfo(
+                        subId);
+                assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getIccId"),
+                        expectedSubInfo.getIccId(), actualSubInfo.getIccId());
+            }
         } catch (SecurityException e) {
             fail("The device owner with the READ_PHONE_STATE permission must be able to access "
                     + "the device IDs: " + e);
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java
index 8112fcb..6f13834 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java
@@ -254,11 +254,14 @@
 
     private boolean isPackageInstalledForUser(String packageName, UserHandle user) {
         try {
+            sUiAutomation.adoptShellPermissionIdentity(INTERACT_ACROSS_USERS_PERMISSION);
             mContext.createContextAsUser(user, /* flags= */ 0).getPackageManager()
                     .getPackageInfo(packageName, /* flags= */ 0);
             return true;
         } catch (PackageManager.NameNotFoundException e) {
             return false;
+        } finally {
+            sUiAutomation.dropShellPermissionIdentity();
         }
     }
 
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
index 5b561ad..5e563d4 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -18,6 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.launcherapps.simpleapp">
 
+    <uses-permission android:name="android.permission.READ_CALENDAR"/>
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
@@ -79,6 +80,10 @@
         <receiver android:name=".SimpleRemoteReceiver" android:process=":receiver"
                   android:exported="true">
         </receiver>
+        <provider android:name=".SimpleProvider" android:process=":remote"
+                  android:authorities="com.android.cts.launcherapps.simpleapp.provider"
+                  android:exported="false" >
+        </provider>
     </application>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleProvider.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleProvider.java
new file mode 100644
index 0000000..233ec5b
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.launcherapps.simpleapp;
+
+import static com.android.cts.launcherapps.simpleapp.SimpleService4.EXIT_CODE;
+import static com.android.cts.launcherapps.simpleapp.SimpleService4.METHOD_EXIT;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+public class SimpleProvider extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection,
+            String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values,
+            String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        if (METHOD_EXIT.equals(method)) {
+            System.exit(EXIT_CODE);
+        }
+        return null;
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleService4.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleService4.java
index 21677e4..6ae368c 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleService4.java
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleService4.java
@@ -17,6 +17,8 @@
 package com.android.cts.launcherapps.simpleapp;
 
 import android.app.Service;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -41,6 +43,8 @@
     private static final String EXTRA_ACTION = "action";
     private static final String EXTRA_MESSENGER = "messenger";
     private static final String EXTRA_PROCESS_NAME = "process";
+    private static final String STUB_PROVIDER_AUTHORITY =
+            "com.android.cts.launcherapps.simpleapp.provider";
 
     private static final int ACTION_NONE = 0;
     private static final int ACTION_FINISH = 1;
@@ -48,11 +52,16 @@
     private static final int ACTION_ANR = 3;
     private static final int ACTION_NATIVE_CRASH = 4;
     private static final int ACTION_KILL = 5;
-    private static final int EXIT_CODE = 123;
+    private static final int ACTION_ACQUIRE_STABLE_PROVIDER = 6;
+    private static final int ACTION_KILL_PROVIDER = 7;
     private static final int CRASH_SIGNAL = OsConstants.SIGSEGV;
 
+    static final String METHOD_EXIT = "exit";
+    static final int EXIT_CODE = 123;
+
     private static final int CMD_PID = 1;
     private Handler mHandler;
+    private ContentProviderClient mProviderClient;
 
     @Override
     public void onCreate() {
@@ -113,6 +122,17 @@
                 case ACTION_KILL:
                     Process.sendSignal(Process.myPid(), OsConstants.SIGKILL);
                     break; // Shoudln't reachable
+                case ACTION_ACQUIRE_STABLE_PROVIDER:
+                    ContentResolver cr = getContentResolver();
+                    mProviderClient = cr.acquireContentProviderClient(
+                            STUB_PROVIDER_AUTHORITY);
+                    break;
+                case ACTION_KILL_PROVIDER:
+                    try {
+                        mProviderClient.call(METHOD_EXIT, null, null);
+                    } catch (RemoteException e) {
+                    }
+                    break;
                 case ACTION_NONE:
                 default:
                     break;
diff --git a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
index b379c5f..9eb5241 100644
--- a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
@@ -319,4 +319,20 @@
                    + "and it must be empty or start with one of the following "
                    + "prefixes: " + whitelistedPathPrefixExample, pathIsWhitelisted);
     }
+
+    /**
+     * Test that the kernel enables fs-verity and its built-in signature support.
+     */
+    @CddTest(requirement="9.10")
+    public void testConfigFsVerity() throws Exception {
+        if (PropertyUtil.getFirstApiLevel(mDevice) < 30 &&
+                PropertyUtil.getPropertyInt(mDevice, "ro.apk_verity.mode") != 2) {
+            return;
+        }
+        assertTrue("Linux kernel must have fs-verity enabled: CONFIG_FS_VERITY=y",
+                configSet.contains("CONFIG_FS_VERITY=y"));
+        assertTrue("Linux kernel must have fs-verity's builtin signature enabled: "
+                + "CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y",
+                configSet.contains("CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y"));
+    }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
index cdaffd1..753ee07 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
@@ -541,7 +541,7 @@
         assertThat(getProperty("ro.product.brand")).isEqualTo(atom.getBrand());
         assertThat(getProperty("ro.product.name")).isEqualTo(atom.getProduct());
         assertThat(getProperty("ro.product.device")).isEqualTo(atom.getDevice());
-        assertThat(getProperty("ro.build.version.release")).isEqualTo(atom.getVersionRelease());
+        assertThat(getProperty("ro.build.version.release_or_codename")).isEqualTo(atom.getVersionRelease());
         assertThat(getProperty("ro.build.id")).isEqualTo(atom.getId());
         assertThat(getProperty("ro.build.version.incremental"))
             .isEqualTo(atom.getVersionIncremental());
diff --git a/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java b/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
index fae440b..7ea47aa 100644
--- a/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
+++ b/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
@@ -71,6 +71,10 @@
 
     @UiThreadTest
     public void testCookieManagerBlockingUiThread() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
         // Instant app can only have https connection.
         CtsTestServer server = new CtsTestServer(mActivity, true);
         final String url = server.getCookieUrl("death.html");
@@ -87,17 +91,10 @@
                 Log.i(TAG, "done setting cookie before creating webview");
             }
         });
-        NullWebViewUtils.NullWebViewFromThreadExceptionHandler h =
-                new NullWebViewUtils.NullWebViewFromThreadExceptionHandler();
 
-        background.setUncaughtExceptionHandler(h);
         background.start();
         background.join();
 
-        if (!h.isWebViewAvailable(mActivity)) {
-            return;
-        }
-
         // Now create WebView and test that setting the cookie beforehand really worked.
         mActivity.createAndAttachWebView();
         WebView webView = mActivity.getWebView();
@@ -165,6 +162,10 @@
 
     @UiThreadTest
     public void testStrictModeNotViolatedOnStartup() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
         StrictMode.ThreadPolicy oldThreadPolicy = StrictMode.getThreadPolicy();
         StrictMode.VmPolicy oldVmPolicy = StrictMode.getVmPolicy();
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
@@ -195,23 +196,8 @@
     }
 
     private void createWebViewAndNavigate() {
-        try {
-            mActivity.createAndAttachWebView();
-        } catch (Throwable t) {
-            NullWebViewUtils.determineIfWebViewAvailable(mActivity, t);
-            if (NullWebViewUtils.isWebViewAvailable()) {
-                // Rethrow t if WebView is available (because then we failed in some way that
-                // indicates that the device supports WebView but couldn't load it for some reason).
-                throw t;
-            } else {
-                // No WebView available - bail out!
-                return;
-            }
-        }
-
-        // WebView is available, so try to call some WebView APIs to ensure they don't cause
-        // strictmode violations
-
+        // Try to call some WebView APIs to ensure they don't cause strictmode violations
+        mActivity.createAndAttachWebView();
         WebViewSyncLoader syncLoader = new WebViewSyncLoader(mActivity.getWebView());
         syncLoader.loadUrlAndWaitForCompletion("about:blank");
         syncLoader.loadUrlAndWaitForCompletion("");
@@ -220,8 +206,9 @@
 
     @UiThreadTest
     public void testGetWebViewLooperOnUiThread() {
-        PackageManager pm = mActivity.getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) return;
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
 
         createAndCheckWebViewLooper();
     }
@@ -231,8 +218,9 @@
      * This ensures WebView.getWebViewLooper() is not implemented as 'return Looper.myLooper();'.
      */
     public void testGetWebViewLooperCreatedOnUiThreadFromInstrThread() {
-        PackageManager pm = mActivity.getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) return;
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
 
         // Create the WebView on the UI thread and then ensure webview.getWebViewLooper() returns
         // the UI thread.
@@ -250,8 +238,9 @@
      */
     public void testGetWebViewLooperCreatedOnBackgroundThreadFromInstThread()
             throws InterruptedException {
-        PackageManager pm = mActivity.getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) return;
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
 
         // Create a WebView on a background thread, check it from the UI thread
         final WebView webviewHolder[] = new WebView[1];
diff --git a/tests/BlobStore/AndroidTest.xml b/tests/BlobStore/AndroidTest.xml
index 7ec3304..f74e2d6 100644
--- a/tests/BlobStore/AndroidTest.xml
+++ b/tests/BlobStore/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsBlobStoreTestCases.apk" />
@@ -26,6 +27,11 @@
         <option name="test-file-name" value="CtsBlobStoreTestHelperDiffSig.apk" />
         <option name="test-file-name" value="CtsBlobStoreTestHelperDiffSig2.apk" />
     </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="teardown-command" value="cmd blob_store idle-maintenance" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.cts.blob" />
     </test>
diff --git a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
index 4633807..ded2f52 100644
--- a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
+++ b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
@@ -80,8 +80,6 @@
     private Context mContext;
     private BlobStoreManager mBlobStoreManager;
 
-    private final ArrayList<Long> mCreatedSessionIds = new ArrayList<>();
-
     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
 
     @Before
@@ -89,17 +87,6 @@
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mBlobStoreManager = (BlobStoreManager) mContext.getSystemService(
                 Context.BLOB_STORE_SERVICE);
-        clearAllBlobsData();
-    }
-
-    @After
-    public void tearDown() {
-        clearAllBlobsData();
-    }
-
-    private void clearAllBlobsData() {
-        executeCmd("cmd blob_store clear-all-sessions");
-        executeCmd("cmd blob_store clear-all-blobs");
     }
 
     @Test
@@ -107,7 +94,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
             assertThat(mBlobStoreManager.openSession(sessionId)).isNotNull();
         } finally {
@@ -156,7 +143,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
             // Verify that session can be opened.
             assertThat(mBlobStoreManager.openSession(sessionId)).isNotNull();
@@ -174,7 +161,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
                 blobData.writeToSession(session, 0, blobData.getFileSize());
@@ -196,7 +183,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
                 assertThat(session).isNotNull();
@@ -222,7 +209,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -247,7 +234,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             // Verify session can be opened for read/write.
@@ -283,7 +270,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -309,7 +296,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -332,7 +319,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -358,7 +345,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -381,7 +368,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -407,7 +394,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -434,7 +421,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -463,7 +450,7 @@
         final TestServiceConnection connection2 = bindToHelperService(HELPER_PKG2);
         final TestServiceConnection connection3 = bindToHelperService(HELPER_PKG3);
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -501,7 +488,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -537,7 +524,7 @@
         final TestServiceConnection connection2 = bindToHelperService(HELPER_PKG2);
         final TestServiceConnection connection3 = bindToHelperService(HELPER_PKG3);
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -579,7 +566,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -610,7 +597,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -647,7 +634,7 @@
         final DummyBlobData blobData = new DummyBlobData(mContext);
         blobData.prepare();
         try {
-            final long sessionId = createSession(blobData.getBlobHandle());
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -714,7 +701,7 @@
                 .queryStatsForUid(UUID_DEFAULT, Process.myUid());
 
         // Create a session and write some data.
-        final long sessionId = createSession(blobData.getBlobHandle());
+        final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
         assertThat(sessionId).isGreaterThan(0L);
         try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
             blobData.writeToSession(session, 0, partialFileSize);
@@ -972,16 +959,4 @@
             mContext.unbindService(this);
         }
     }
-
-    private long createSession(BlobHandle blobHandle) throws Exception {
-        final long sessionId = mBlobStoreManager.createSession(blobHandle);
-        mCreatedSessionIds.add(sessionId);
-        return sessionId;
-    }
-
-    private String executeCmd(String cmd) {
-        final String result = runShellCommand(cmd).trim();
-        Log.d(TAG, "Output of <" + cmd + ">: <" + result + ">");
-        return result;
-    }
 }
diff --git a/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java b/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
index 8586f2d..549419c 100644
--- a/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
+++ b/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
@@ -17,12 +17,14 @@
 package android.app.cts;
 
 import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
 import android.app.ApplicationExitInfo;
 import android.app.Instrumentation;
 import android.app.cts.android.app.cts.tools.WatchUidRunner;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
@@ -86,6 +88,8 @@
     private static final int ACTION_ANR = 3;
     private static final int ACTION_NATIVE_CRASH = 4;
     private static final int ACTION_KILL = 5;
+    private static final int ACTION_ACQUIRE_STABLE_PROVIDER = 6;
+    private static final int ACTION_KILL_PROVIDER = 7;
     private static final int EXIT_CODE = 123;
     private static final int CRASH_SIGNAL = OsConstants.SIGSEGV;
 
@@ -266,6 +270,11 @@
     // Start the target package
     private void startService(int commandCode, String serviceName, boolean waitForGone,
             boolean other) {
+        startService(commandCode, serviceName, waitForGone, true, other);
+    }
+
+    private void startService(int commandCode, String serviceName, boolean waitForGone,
+            boolean waitForIdle, boolean other) {
         Intent intent = new Intent(EXIT_ACTION);
         intent.setClassName(STUB_PACKAGE_NAME, serviceName);
         intent.putExtra(EXTRA_ACTION, commandCode);
@@ -274,7 +283,9 @@
         UserHandle user = other ? mOtherUserHandle : mCurrentUserHandle;
         WatchUidRunner watcher = other ? mOtherUidWatcher : mWatcher;
         mContext.startServiceAsUser(intent, user);
-        watcher.waitFor(WatchUidRunner.CMD_IDLE, null);
+        if (waitForIdle) {
+            watcher.waitFor(WatchUidRunner.CMD_IDLE, null);
+        }
         if (waitForGone) {
             waitForGone(watcher);
         }
@@ -493,6 +504,11 @@
         // Remove old records to avoid interference with the test.
         clearHistoricalExitInfo();
 
+        // Enable a compat feature
+        executeShellCmd("am compat enable " + PackageManager.FILTER_APPLICATION_QUERY
+                + " " + STUB_PACKAGE_NAME);
+        mInstrumentation.getUiAutomation().grantRuntimePermission(
+                STUB_PACKAGE_NAME, android.Manifest.permission.READ_CALENDAR);
         long now = System.currentTimeMillis();
 
         // Start a process and do nothing
@@ -506,8 +522,10 @@
                         android.Manifest.permission.REAL_GET_TASKS);
         assertTrue(meminfo != null && meminfo.length == 1);
 
-        // kill background processes
-        executeShellCmd("am kill " + STUB_PACKAGE_NAME);
+        // Disable the compat feature
+        executeShellCmd("am compat disable " + PackageManager.FILTER_APPLICATION_QUERY
+                + " " + STUB_PACKAGE_NAME);
+
         waitForGone(mWatcher);
         long now2 = System.currentTimeMillis();
 
@@ -520,13 +538,43 @@
 
         ApplicationExitInfo info = list.get(0);
         verify(info, mStubPackagePid, mStubPackageUid, STUB_PACKAGE_NAME,
-                ApplicationExitInfo.REASON_OTHER, null, "kill background", now, now2);
+                ApplicationExitInfo.REASON_OTHER, null, "PlatformCompat overrides", now, now2);
 
         // Also verify that we get the expected meminfo
         assertEquals(meminfo[0].getTotalPss(), info.getPss());
         assertEquals(meminfo[0].getTotalRss(), info.getRss());
     }
 
+    public void testPermissionChange() throws Exception {
+        // Remove old records to avoid interference with the test.
+        clearHistoricalExitInfo();
+
+        // Grant the read calendar permission
+        mInstrumentation.getUiAutomation().grantRuntimePermission(
+                STUB_PACKAGE_NAME, android.Manifest.permission.READ_CALENDAR);
+        long now = System.currentTimeMillis();
+
+        // Start a process and do nothing
+        startService(ACTION_FINISH, STUB_SERVICE_NAME, false, false);
+
+        // Revoke the read calendar permission
+        mInstrumentation.getUiAutomation().revokeRuntimePermission(
+                STUB_PACKAGE_NAME, android.Manifest.permission.READ_CALENDAR);
+        waitForGone(mWatcher);
+        long now2 = System.currentTimeMillis();
+
+        List<ApplicationExitInfo> list = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                STUB_PACKAGE_NAME, mStubPackagePid, 1,
+                mActivityManager::getHistoricalProcessExitReasons,
+                android.Manifest.permission.DUMP);
+
+        assertTrue(list != null && list.size() == 1);
+
+        ApplicationExitInfo info = list.get(0);
+        verify(info, mStubPackagePid, mStubPackageUid, STUB_PACKAGE_NAME,
+                ApplicationExitInfo.REASON_PERMISSION_CHANGE, null, null, now, now2);
+    }
+
     public void testCrash() throws Exception {
         // Remove old records to avoid interference with the test.
         clearHistoricalExitInfo();
@@ -571,6 +619,76 @@
                 ApplicationExitInfo.REASON_CRASH_NATIVE, null, null, now, now2);
     }
 
+    public void testUserRequested() throws Exception {
+        // Remove old records to avoid interference with the test.
+        clearHistoricalExitInfo();
+
+        long now = System.currentTimeMillis();
+
+        // Start a process and do nothing
+        startService(ACTION_NONE, STUB_SERVICE_NAME, false, false);
+
+        // Force stop the test package
+        executeShellCmd("am force-stop " + STUB_PACKAGE_NAME);
+
+        // Wait the process gone
+        waitForGone(mWatcher);
+
+        long now2 = System.currentTimeMillis();
+        List<ApplicationExitInfo> list = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                STUB_PACKAGE_NAME, mStubPackagePid, 1,
+                mActivityManager::getHistoricalProcessExitReasons,
+                android.Manifest.permission.DUMP);
+
+        assertTrue(list != null && list.size() == 1);
+        verify(list.get(0), mStubPackagePid, mStubPackageUid, STUB_PACKAGE_NAME,
+                ApplicationExitInfo.REASON_USER_REQUESTED, null, null, now, now2);
+    }
+
+    public void testDependencyDied() throws Exception {
+        // Remove old records to avoid interference with the test.
+        clearHistoricalExitInfo();
+
+        // Start a process and acquire the provider
+        startService(ACTION_ACQUIRE_STABLE_PROVIDER, STUB_SERVICE_NAME, false, false);
+
+        final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+        long now = System.currentTimeMillis();
+        final long timeout = now + WAITFOR_MSEC;
+        int providerPid = -1;
+        while (now < timeout && providerPid < 0) {
+            sleep(1000);
+            List<RunningAppProcessInfo> list = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    am, (m) -> m.getRunningAppProcesses(),
+                    android.Manifest.permission.REAL_GET_TASKS);
+            for (RunningAppProcessInfo info: list) {
+                if (info.processName.equals(STUB_REMOTE_ROCESS_NAME)) {
+                    providerPid = info.pid;
+                    break;
+                }
+            }
+            now = System.currentTimeMillis();
+        }
+        assertTrue(providerPid > 0);
+
+        now = System.currentTimeMillis();
+        // Now let the provider exit itself
+        startService(ACTION_KILL_PROVIDER, STUB_SERVICE_NAME, false, false, false);
+
+        // Wait for both of the processes gone
+        waitForGone(mWatcher);
+        final long now2 = System.currentTimeMillis();
+
+        List<ApplicationExitInfo> list = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                STUB_PACKAGE_NAME, mStubPackagePid, 1,
+                mActivityManager::getHistoricalProcessExitReasons,
+                android.Manifest.permission.DUMP);
+
+        assertTrue(list != null && list.size() == 1);
+        verify(list.get(0), mStubPackagePid, mStubPackageUid, STUB_PACKAGE_NAME,
+                ApplicationExitInfo.REASON_DEPENDENCY_DIED, null, null, now, now2);
+    }
+
     public void testMultipleProcess() throws Exception {
         // Remove old records to avoid interference with the test.
         clearHistoricalExitInfo();
@@ -738,9 +856,31 @@
         verify(list.get(1), mStubPackageOtherUserPid, mStubPackageOtherUid, STUB_ROCESS_NAME,
                 ApplicationExitInfo.REASON_SIGNALED, OsConstants.SIGKILL, null, now2, now3);
 
-        int otherUserId = mOtherUserId;
+        // Get the full user permission in order to start service as other user
+        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+                android.Manifest.permission.INTERACT_ACROSS_USERS,
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        // Start the process in a secondary user and do nothing
+        startService(ACTION_NONE, STUB_SERVICE_NAME, false, true);
+        // drop the permissions
+        mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+
+        long now6 = System.currentTimeMillis();
         // Stop the test user
         assertTrue(stopUser(mOtherUserId, true, true));
+        // Wait for being killed
+        waitForGone(mOtherUidWatcher);
+
+        long now7 = System.currentTimeMillis();
+        list = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                STUB_PACKAGE_NAME, 0, 1, mOtherUserId,
+                this::getHistoricalProcessExitReasonsAsUser,
+                android.Manifest.permission.DUMP,
+                android.Manifest.permission.INTERACT_ACROSS_USERS);
+        verify(list.get(0), mStubPackageOtherUserPid, mStubPackageOtherUid, STUB_ROCESS_NAME,
+                ApplicationExitInfo.REASON_USER_STOPPED, null, null, now6, now7);
+
+        int otherUserId = mOtherUserId;
         // Now remove the other user
         removeUser(mOtherUserId);
         mOtherUidWatcher.finish();
diff --git a/tests/app/src/android/app/cts/BaseTileServiceTest.java b/tests/app/src/android/app/cts/BaseTileServiceTest.java
index 011f215..dd163ea 100644
--- a/tests/app/src/android/app/cts/BaseTileServiceTest.java
+++ b/tests/app/src/android/app/cts/BaseTileServiceTest.java
@@ -54,8 +54,7 @@
     protected Context mContext;
 
     final static String DUMP_COMMAND =
-            "dumpsys activity service com.android.systemui/.SystemUIService dependency "
-                    + "DumpController qstilehost";
+            "dumpsys activity service com.android.systemui/.SystemUIService QSTileHost";
 
     // Time between checks for state we expect.
     protected static final long CHECK_DELAY = 250;
diff --git a/tests/appsearch/AndroidTest.xml b/tests/appsearch/AndroidTest.xml
deleted file mode 100644
index a86e339..0000000
--- a/tests/appsearch/AndroidTest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<configuration description="Config for CTS AppSearch test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="framework" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsAppSearchTestCases.apk" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="com.android.cts.appsearch" />
-    </test>
-</configuration>
diff --git a/tests/appsearch/OWNERS b/tests/appsearch/OWNERS
deleted file mode 100644
index cf3ad8a..0000000
--- a/tests/appsearch/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 755061
-adorokhine@google.com
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java
deleted file mode 100644
index 25ee503..0000000
--- a/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.appsearch;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchEmail;
-import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.AppSearchSchema;
-import android.app.appsearch.AppSearchSchema.PropertyConfig;
-import android.content.Context;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.google.common.collect.ImmutableList;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class AppSearchManagerTest {
-    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
-    private final AppSearchManager mAppSearch = mContext.getSystemService(AppSearchManager.class);
-
-    @Test
-    public void testGetService() {
-        assertThat(mContext.getSystemService(Context.APP_SEARCH_SERVICE)).isNotNull();
-        assertThat(mContext.getSystemService(AppSearchManager.class)).isNotNull();
-        assertThat(mAppSearch).isNotNull();
-    }
-
-    @Test
-    public void testSetSchema() {
-        AppSearchSchema emailSchema = AppSearchSchema.newBuilder("Email")
-                .addProperty(AppSearchSchema.newPropertyBuilder("subject")
-                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .build()
-                ).addProperty(AppSearchSchema.newPropertyBuilder("body")
-                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .build()
-                ).build();
-        mAppSearch.setSchema(emailSchema);
-    }
-
-    @Test
-    public void testPutDocuments() throws Exception {
-        // Schema registration
-        mAppSearch.setSchema(AppSearchEmail.SCHEMA);
-
-        // Index a document
-        AppSearchEmail email = new AppSearchEmail.Builder("uri1")
-                .setFrom("from@example.com")
-                .setTo("to1@example.com", "to2@example.com")
-                .setSubject("testPut example")
-                .setBody("This is the body of the testPut email")
-                .build();
-
-        AppSearchBatchResult result = mAppSearch.putDocuments(ImmutableList.of(email));
-        assertThat(result.isSuccess()).isTrue();
-        assertThat(result.getResults()).containsExactly("uri1", null);
-        assertThat(result.getFailures()).isEmpty();
-    }
-}
diff --git a/tests/camera/AndroidTest.xml b/tests/camera/AndroidTest.xml
index 30ee159..62124d1 100644
--- a/tests/camera/AndroidTest.xml
+++ b/tests/camera/AndroidTest.xml
@@ -15,6 +15,10 @@
 -->
 <configuration description="Config for CTS Camera test cases">
     <option name="test-suite-tag" value="cts" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+      <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+      <option name="screen-always-on" value="on" />
+    </target_preparer>
     <option name="config-descriptor:metadata" key="component" value="camera" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
diff --git a/tests/camera/api25test/AndroidTest.xml b/tests/camera/api25test/AndroidTest.xml
index b431952..d561f6c 100644
--- a/tests/camera/api25test/AndroidTest.xml
+++ b/tests/camera/api25test/AndroidTest.xml
@@ -15,6 +15,10 @@
 -->
 <configuration description="Config for CTS Camera API 25 test cases">
     <option name="test-suite-tag" value="cts" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+      <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+      <option name="screen-always-on" value="on" />
+    </target_preparer>
     <option name="config-descriptor:metadata" key="component" value="camera" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index 6751a1a..8a06a46 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -478,6 +478,10 @@
         public static final String KEY_DISPLAY_ID = "display_id";
     }
 
+    public static class LaunchingActivity {
+        public static final String KEY_FINISH_BEFORE_LAUNCH = "finish_before_launch";
+    }
+
     private static ComponentName component(String className) {
         return component(Components.class, className);
     }
diff --git a/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/LaunchingActivity.java b/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/LaunchingActivity.java
index 87924aa..03d2b43 100644
--- a/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/LaunchingActivity.java
+++ b/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/LaunchingActivity.java
@@ -16,6 +16,8 @@
 
 package android.server.wm.app;
 
+import static android.server.wm.app.Components.LaunchingActivity.KEY_FINISH_BEFORE_LAUNCH;
+
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
@@ -31,6 +33,10 @@
         super.onCreate(savedInstanceState);
 
         final Intent intent = getIntent();
+        if (intent != null && intent.getExtras() != null
+                && intent.getExtras().getBoolean(KEY_FINISH_BEFORE_LAUNCH)) {
+            finish();
+        }
         if (savedInstanceState == null && intent != null) {
             launchActivityFromExtras(intent.getExtras());
         }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
index 0b711b0..e9c3f63 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
@@ -23,13 +23,13 @@
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.app.Components.TRANSLUCENT_ACTIVITY;
 import static android.server.wm.app.Components.TestActivity.COMMAND_NAVIGATE_UP_TO;
+import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
 import static android.server.wm.app.Components.TestActivity.EXTRA_INTENT;
 import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
-import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
 import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
 import static android.server.wm.second.Components.SECOND_ACTIVITY;
-
 import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
@@ -41,6 +41,7 @@
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.CommandSession.ActivitySession;
+import android.server.wm.app.Components;
 import android.server.wm.intent.Activities;
 
 import androidx.test.rule.ActivityTestRule;
@@ -130,6 +131,26 @@
                 TEST_ACTIVITY);
     }
 
+    @Test
+    public void testStartActivityFromFinishingActivity() {
+        // launch TEST_ACTIVITY from LAUNCHING_ACTIVITY
+        getLaunchActivityBuilder()
+                .setTargetActivity(TEST_ACTIVITY)
+                .setFinishBeforeLaunch(true)
+                .execute();
+
+        // launch LAUNCHING_ACTIVITY again
+        getLaunchActivityBuilder()
+                .setTargetActivity(LAUNCHING_ACTIVITY)
+                .setUseInstrumentation()
+                .execute();
+
+        // make sure TEST_ACTIVITY is still on top and resumed
+        mWmState.computeState(TEST_ACTIVITY);
+        mWmState.assertResumedActivity("Test Activity should be remained on top and resumed",
+                TEST_ACTIVITY);
+    }
+
     /**
      * Ensures you can start an {@link Activity} from a non {@link Activity}
      * {@link android.content.Context} when the target sdk is between N and O Mr1.
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index d49f11f..24033a2 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -83,6 +83,7 @@
 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
+import static android.server.wm.app.Components.LaunchingActivity.KEY_FINISH_BEFORE_LAUNCH;
 import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
 import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
@@ -1919,6 +1920,7 @@
         private boolean mMultipleTask;
         private boolean mAllowMultipleInstances = true;
         private boolean mLaunchTaskBehind;
+        private boolean mFinishBeforeLaunch;
         private int mDisplayId = INVALID_DISPLAY;
         private int mWindowingMode = -1;
         private int mActivityType = ACTIVITY_TYPE_UNDEFINED;
@@ -1988,6 +1990,11 @@
             return this;
         }
 
+        public LaunchActivityBuilder setFinishBeforeLaunch(boolean finishBeforeLaunch) {
+            mFinishBeforeLaunch = finishBeforeLaunch;
+            return this;
+        }
+
         public ComponentName getTargetActivity() {
             return mTargetActivity;
         }
@@ -2162,6 +2169,9 @@
             if (mReorderToFront) {
                 commandBuilder.append(" --ez " + KEY_REORDER_TO_FRONT + " true");
             }
+            if (mFinishBeforeLaunch) {
+                commandBuilder.append(" --ez " + KEY_FINISH_BEFORE_LAUNCH + " true");
+            }
             if (mDisplayId != INVALID_DISPLAY) {
                 commandBuilder.append(" --ei " + KEY_DISPLAY_ID + " ").append(mDisplayId);
             }
diff --git a/tests/media/src/android/mediav2/cts/MuxerUnitTest.java b/tests/media/src/android/mediav2/cts/MuxerUnitTest.java
index 6942393..b648ca2 100644
--- a/tests/media/src/android/mediav2/cts/MuxerUnitTest.java
+++ b/tests/media/src/android/mediav2/cts/MuxerUnitTest.java
@@ -259,7 +259,6 @@
         }
 
         @Test
-        @Ignore("TODO(b/146423844)")
         public void testIfAddTrackSucceedsAfterStart() throws IOException {
             MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
             try {
@@ -356,7 +355,6 @@
         }
 
         @Test
-        @Ignore("TODO(b/146423844)")
         public void testIdempotentStart() throws IOException {
             MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
             MediaFormat format = new MediaFormat();
@@ -491,7 +489,6 @@
         }
 
         @Test
-        @Ignore("TODO(b/146423844)")
         public void testSimpleStartStopMuxer() throws IOException {
             MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
             MediaFormat format = new MediaFormat();
@@ -877,7 +874,6 @@
         }
 
         @Test
-        @Ignore("TODO(b/146423844)")
         public void testSimpleStartStopMuxer() {
             assertTrue(nativeTestSimpleStartStop(mOutLoc));
         }
diff --git a/tests/appsearch/Android.bp b/tests/suspendapps/Android.bp
similarity index 60%
copy from tests/appsearch/Android.bp
copy to tests/suspendapps/Android.bp
index 1f70dc4..7cdcad2 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/suspendapps/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,21 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-android_test {
-    name: "CtsAppSearchTestCases",
-    defaults: ["cts_defaults"],
-    static_libs: [
-      "androidx.test.ext.junit",
-      "androidx.test.rules",
-      "compatibility-device-util-axt",
-    ],
+filegroup {
+    name: "CtsSuspendHelpersConstants",
     srcs: [
-      "src/**/*.java",
+        "test-apps/**/*.java",
     ],
-    test_suites: [
-      "cts",
-      "vts",
-      "general-tests",
-    ],
-    platform_apis: true,
 }
diff --git a/tests/suspendapps/OWNERS b/tests/suspendapps/OWNERS
new file mode 100644
index 0000000..7455b17
--- /dev/null
+++ b/tests/suspendapps/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36137
+suprabh@google.com
+varunshah@google.com
diff --git a/tests/appsearch/Android.bp b/tests/suspendapps/permission/Android.bp
similarity index 69%
copy from tests/appsearch/Android.bp
copy to tests/suspendapps/permission/Android.bp
index 1f70dc4..b7ce222 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/suspendapps/permission/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -13,20 +13,23 @@
 // limitations under the License.
 
 android_test {
-    name: "CtsAppSearchTestCases",
+    name: "CtsSuspendAppsPermissionTestCases",
     defaults: ["cts_defaults"],
-    static_libs: [
-      "androidx.test.ext.junit",
-      "androidx.test.rules",
-      "compatibility-device-util-axt",
-    ],
+
     srcs: [
-      "src/**/*.java",
+        "src/**/*.java",
+        ":CtsSuspendHelpersConstants",
     ],
+
+    static_libs: [
+        "androidx.test.rules",
+    ],
+
+    sdk_version: "system_current",
+
     test_suites: [
-      "cts",
-      "vts",
-      "general-tests",
+        "cts",
+        "vts",
+        "general-tests",
     ],
-    platform_apis: true,
 }
diff --git a/tests/suspendapps/permission/AndroidManifest.xml b/tests/suspendapps/permission/AndroidManifest.xml
new file mode 100755
index 0000000..01b334a
--- /dev/null
+++ b/tests/suspendapps/permission/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.suspendapps.permission.cts">
+
+    <!-- This test checks that PM.setPackagesSuspended cannot be called without having
+    android.permission.SUSPEND_APPS or being an admin -->
+
+    <application android:label="Suspend Apps Permission CTS">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.suspendapps.permission.cts"
+                     android:label="Suspend Apps Permission CTS"/>
+</manifest>
diff --git a/tests/suspendapps/permission/AndroidTest.xml b/tests/suspendapps/permission/AndroidTest.xml
new file mode 100644
index 0000000..fb593aa
--- /dev/null
+++ b/tests/suspendapps/permission/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<configuration description="Config for CTS Suspend Apps test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework"/>
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsSuspendAppsPermissionTestCases.apk" />
+        <option name="test-file-name" value="CtsSuspendTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.suspendapps.permission.cts" />
+        <option name="runtime-hint" value="10s" />
+    </test>
+
+</configuration>
diff --git a/tests/suspendapps/permission/src/android/suspendapps/permission/cts/NegativePermissionsTest.java b/tests/suspendapps/permission/src/android/suspendapps/permission/cts/NegativePermissionsTest.java
new file mode 100644
index 0000000..0b8b9e5
--- /dev/null
+++ b/tests/suspendapps/permission/src/android/suspendapps/permission/cts/NegativePermissionsTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.suspendapps.permission.cts;
+
+import static android.content.pm.PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+import static android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS;
+import static android.content.pm.PackageManager.RESTRICTION_NONE;
+
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.SuspendDialogInfo;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.suspendapps.suspendtestapp.Constants;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Callable;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NegativePermissionsTest {
+    private static final String TAG = NegativePermissionsTest.class.getSimpleName();
+    private static final String[] PACKAGES_TO_SUSPEND = new String[] {
+            Constants.PACKAGE_NAME,
+            Constants.ANDROID_PACKAGE_NAME_2,
+    };
+
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        mPackageManager = context.getPackageManager();
+    }
+
+    private void assertSecurityException(Callable callable, String tag) {
+        try {
+            callable.call();
+        } catch (SecurityException e) {
+            // Passed.
+            return;
+        } catch (Exception e) {
+            Log.e(TAG, "Unexpected exception while calling [" + tag + "]", e);
+            fail("Unexpected exception while calling [" + tag + "]: " + e.getMessage());
+        }
+        fail("Call [" + tag + "] succeeded without permissions");
+    }
+
+    @Test
+    public void setPackagesSuspended() {
+        assertSecurityException(
+                () -> mPackageManager.setPackagesSuspended(PACKAGES_TO_SUSPEND, true, null, null,
+                        (SuspendDialogInfo) null), "setPackagesSuspended:true");
+        assertSecurityException(
+                () -> mPackageManager.setPackagesSuspended(PACKAGES_TO_SUSPEND, false, null, null,
+                        (SuspendDialogInfo) null), "setPackagesSuspended:false");
+    }
+
+    @Test
+    public void setPackagesSuspended_deprecated() {
+        assertSecurityException(
+                () -> mPackageManager.setPackagesSuspended(PACKAGES_TO_SUSPEND, true, null, null,
+                        (String) null), "setPackagesSuspended:true");
+        assertSecurityException(
+                () -> mPackageManager.setPackagesSuspended(PACKAGES_TO_SUSPEND, false, null, null,
+                        (String) null), "setPackagesSuspended:false");
+    }
+
+    @Test
+    public void setDistractingPackageRestrictions() {
+        assertSecurityException(
+                () -> mPackageManager.setDistractingPackageRestrictions(PACKAGES_TO_SUSPEND,
+                        RESTRICTION_HIDE_FROM_SUGGESTIONS),
+                "setDistractingPackageRestrictions:HIDE_FROM_SUGGESTIONS");
+        assertSecurityException(
+                () -> mPackageManager.setDistractingPackageRestrictions(PACKAGES_TO_SUSPEND,
+                        RESTRICTION_HIDE_FROM_SUGGESTIONS | RESTRICTION_HIDE_NOTIFICATIONS),
+                "setDistractingPackageRestrictions:HIDE_FROM_SUGGESTIONS|HIDE_NOTIFICATIONS");
+        assertSecurityException(
+                () -> mPackageManager.setDistractingPackageRestrictions(PACKAGES_TO_SUSPEND,
+                        RESTRICTION_HIDE_NOTIFICATIONS),
+                "setDistractingPackageRestrictions:HIDE_NOTIFICATIONS");
+        assertSecurityException(
+                () -> mPackageManager.setDistractingPackageRestrictions(PACKAGES_TO_SUSPEND,
+                        RESTRICTION_NONE), "setDistractingPackageRestrictions:NONE");
+    }
+}
diff --git a/tests/suspendapps/test-apps/SuspendTestApp/Android.bp b/tests/suspendapps/test-apps/SuspendTestApp/Android.bp
new file mode 100644
index 0000000..c6b3016
--- /dev/null
+++ b/tests/suspendapps/test-apps/SuspendTestApp/Android.bp
@@ -0,0 +1,53 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsSuspendTestApp",
+    defaults: ["cts_support_defaults"],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    sdk_version: "current",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
+
+// -----------------------------------------------
+
+android_test_helper_app {
+    name: "CtsSuspendTestApp2",
+    defaults: ["cts_support_defaults"],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    sdk_version: "current",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+
+    aaptflags: [
+        "--rename-manifest-package com.android.suspendapps.suspendtestapp2",
+    ],
+}
diff --git a/tests/suspendapps/test-apps/SuspendTestApp/AndroidManifest.xml b/tests/suspendapps/test-apps/SuspendTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..d8ab015
--- /dev/null
+++ b/tests/suspendapps/test-apps/SuspendTestApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.suspendapps.suspendtestapp">
+
+    <application android:label="Suspend Test App">
+        <activity android:name=".SuspendTestActivity"
+                  android:exported="true" />
+        <receiver android:name=".SuspendTestReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
+                <action android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/suspendapps/test-apps/SuspendTestApp/src/com/android/suspendapps/suspendtestapp/Constants.java b/tests/suspendapps/test-apps/SuspendTestApp/src/com/android/suspendapps/suspendtestapp/Constants.java
new file mode 100644
index 0000000..a4dbd26
--- /dev/null
+++ b/tests/suspendapps/test-apps/SuspendTestApp/src/com/android/suspendapps/suspendtestapp/Constants.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.suspendapps.suspendtestapp;
+
+public interface Constants {
+    String PACKAGE_NAME = "com.android.suspendapps.suspendtestapp";
+    String ANDROID_PACKAGE_NAME_2 = "com.android.suspendapps.suspendtestapp2";
+    String INSTRUMENTATION_PACKAGE = "android.suspendapps.cts";
+}
diff --git a/tests/suspendapps/test-apps/SuspendTestApp/src/com/android/suspendapps/suspendtestapp/SuspendTestActivity.java b/tests/suspendapps/test-apps/SuspendTestApp/src/com/android/suspendapps/suspendtestapp/SuspendTestActivity.java
new file mode 100644
index 0000000..a3a6879
--- /dev/null
+++ b/tests/suspendapps/test-apps/SuspendTestApp/src/com/android/suspendapps/suspendtestapp/SuspendTestActivity.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.suspendapps.suspendtestapp;
+
+import static com.android.suspendapps.suspendtestapp.Constants.INSTRUMENTATION_PACKAGE;
+import static com.android.suspendapps.suspendtestapp.Constants.PACKAGE_NAME;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class SuspendTestActivity extends Activity {
+    private static final String TAG = SuspendTestActivity.class.getSimpleName();
+    private static final String ACTION_FINISH_TEST_ACTIVITY =
+            PACKAGE_NAME + ".action.FINISH_TEST_ACTIVITY";
+
+    public static final String ACTION_REPORT_TEST_ACTIVITY_STOPPED =
+            PACKAGE_NAME + ".action.REPORT_TEST_ACTIVITY_STOPPED";
+    public static final String ACTION_REPORT_TEST_ACTIVITY_STARTED =
+            PACKAGE_NAME + ".action.REPORT_TEST_ACTIVITY_STARTED";
+
+    private boolean mReportStartStop;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Log.d(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+        if (ACTION_FINISH_TEST_ACTIVITY.equals(getIntent().getAction())) {
+            finish();
+        } else {
+            mReportStartStop = true;
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        Log.d(TAG, "onStart");
+        super.onStart();
+        if (mReportStartStop) {
+            final Intent reportStart = new Intent(ACTION_REPORT_TEST_ACTIVITY_STARTED)
+                    .putExtras(getIntent())
+                    .setPackage(INSTRUMENTATION_PACKAGE);
+            sendBroadcast(reportStart);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        Log.d(TAG, "onStop");
+        super.onStop();
+        if (mReportStartStop) {
+            final Intent reportStop = new Intent(ACTION_REPORT_TEST_ACTIVITY_STOPPED)
+                    .setPackage(INSTRUMENTATION_PACKAGE);
+            sendBroadcast(reportStop);
+        }
+    }
+}
diff --git a/tests/suspendapps/test-apps/SuspendTestApp/src/com/android/suspendapps/suspendtestapp/SuspendTestReceiver.java b/tests/suspendapps/test-apps/SuspendTestApp/src/com/android/suspendapps/suspendtestapp/SuspendTestReceiver.java
new file mode 100644
index 0000000..db89271
--- /dev/null
+++ b/tests/suspendapps/test-apps/SuspendTestApp/src/com/android/suspendapps/suspendtestapp/SuspendTestReceiver.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.suspendapps.suspendtestapp;
+
+import static com.android.suspendapps.suspendtestapp.Constants.INSTRUMENTATION_PACKAGE;
+import static com.android.suspendapps.suspendtestapp.Constants.PACKAGE_NAME;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class SuspendTestReceiver extends BroadcastReceiver {
+    private static final String TAG = SuspendTestReceiver.class.getSimpleName();
+
+    public static final String EXTRA_SUSPENDED = PACKAGE_NAME + ".extra.SUSPENDED";
+    public static final String ACTION_GET_SUSPENDED_STATE =
+            PACKAGE_NAME + ".action.GET_SUSPENDED_STATE";
+    public static final String EXTRA_SUSPENDED_APP_EXTRAS =
+            PACKAGE_NAME + ".extra.SUSPENDED_APP_EXTRAS";
+    public static final String ACTION_REPORT_MY_PACKAGE_SUSPENDED =
+            PACKAGE_NAME + ".action.REPORT_MY_PACKAGE_SUSPENDED";
+    public static final String ACTION_REPORT_MY_PACKAGE_UNSUSPENDED =
+            PACKAGE_NAME + ".action.REPORT_MY_PACKAGE_UNSUSPENDED";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final PackageManager packageManager = context.getPackageManager();
+        Log.d(TAG, "Received action " + intent.getAction());
+        final Bundle appExtras;
+        switch (intent.getAction()) {
+            case ACTION_GET_SUSPENDED_STATE:
+                final Bundle result = new Bundle();
+                final boolean suspended = packageManager.isPackageSuspended();
+                appExtras = packageManager.getSuspendedPackageAppExtras();
+                result.putBoolean(EXTRA_SUSPENDED, suspended);
+                result.putBundle(EXTRA_SUSPENDED_APP_EXTRAS, appExtras);
+                setResult(0, null, result);
+                break;
+            case Intent.ACTION_MY_PACKAGE_SUSPENDED:
+                appExtras = intent.getBundleExtra(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS);
+                final Intent reportSuspendIntent = new Intent(ACTION_REPORT_MY_PACKAGE_SUSPENDED)
+                        .putExtra(EXTRA_SUSPENDED_APP_EXTRAS, appExtras)
+                        .setPackage(INSTRUMENTATION_PACKAGE);
+                context.sendBroadcast(reportSuspendIntent);
+                break;
+            case Intent.ACTION_MY_PACKAGE_UNSUSPENDED:
+                final Intent reportUnsuspendIntent =
+                        new Intent(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED)
+                        .setPackage(INSTRUMENTATION_PACKAGE)
+                        .putExtra(Intent.EXTRA_PACKAGE_NAME, context.getPackageName());
+                context.sendBroadcast(reportUnsuspendIntent);
+                break;
+            default:
+                Log.e(TAG, "Unknown action: " + intent.getAction());
+        }
+    }
+}
diff --git a/tests/appsearch/Android.bp b/tests/suspendapps/test-apps/TestDeviceAdmin/Android.bp
similarity index 60%
copy from tests/appsearch/Android.bp
copy to tests/suspendapps/test-apps/TestDeviceAdmin/Android.bp
index 1f70dc4..36fcc32 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/suspendapps/test-apps/TestDeviceAdmin/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,21 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-android_test {
-    name: "CtsAppSearchTestCases",
-    defaults: ["cts_defaults"],
-    static_libs: [
-      "androidx.test.ext.junit",
-      "androidx.test.rules",
-      "compatibility-device-util-axt",
-    ],
-    srcs: [
-      "src/**/*.java",
-    ],
+android_test_helper_app {
+    name: "CtsSuspendTestDeviceAdmin",
+    defaults: ["cts_support_defaults"],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "current",
+
     test_suites: [
-      "cts",
-      "vts",
-      "general-tests",
+        "cts",
+        "vts",
+        "general-tests",
     ],
-    platform_apis: true,
 }
diff --git a/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml b/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml
new file mode 100644
index 0000000..3368398
--- /dev/null
+++ b/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.suspendapps.testdeviceadmin" >
+
+    <application android:label="CTS Device Admin" android:testOnly="true">
+        <receiver
+            android:name=".TestDeviceAdmin"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data
+                android:name="android.app.device_admin"
+                android:resource="@xml/device_admin"/>
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+            </intent-filter>
+        </receiver>
+        <receiver
+            android:name=".TestCommsReceiver"
+            android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/suspendapps/test-apps/TestDeviceAdmin/res/xml/device_admin.xml b/tests/suspendapps/test-apps/TestDeviceAdmin/res/xml/device_admin.xml
new file mode 100644
index 0000000..bde48c4
--- /dev/null
+++ b/tests/suspendapps/test-apps/TestDeviceAdmin/res/xml/device_admin.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-policies>
+        <limit-password />
+        <watch-login />
+        <reset-password />
+        <force-lock />
+        <wipe-data />
+        <expire-password />
+        <encrypted-storage />
+        <disable-camera />
+        <disable-keyguard-features />
+    </uses-policies>
+</device-admin>
diff --git a/tests/suspendapps/test-apps/TestDeviceAdmin/src/com/android/suspendapps/testdeviceadmin/TestCommsReceiver.java b/tests/suspendapps/test-apps/TestDeviceAdmin/src/com/android/suspendapps/testdeviceadmin/TestCommsReceiver.java
new file mode 100644
index 0000000..d864775
--- /dev/null
+++ b/tests/suspendapps/test-apps/TestDeviceAdmin/src/com/android/suspendapps/testdeviceadmin/TestCommsReceiver.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.suspendapps.testdeviceadmin;
+
+import static android.app.Activity.RESULT_OK;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.Arrays;
+
+public class TestCommsReceiver extends BroadcastReceiver {
+    private static final String TAG = TestCommsReceiver.class.getSimpleName();
+
+    public static final String PACKAGE_NAME = "com.android.suspendapps.testdeviceadmin";
+    public static final String ACTION_UNSUSPEND = PACKAGE_NAME + ".action.UNSUSPEND";
+    public static final String ACTION_SUSPEND = PACKAGE_NAME + ".action.SUSPEND";
+    public static final String ACTION_BLOCK_UNINSTALL = PACKAGE_NAME + ".action.BLOCK_UNINSTALL";
+    public static final String ACTION_UNBLOCK_UNINSTALL =
+            PACKAGE_NAME + ".action.UNBLOCK_UNINSTALL";
+    public static final String ACTION_ADD_USER_RESTRICTION =
+            PACKAGE_NAME + ".action.ADD_USER_RESTRICTION";
+    public static final String ACTION_CLEAR_USER_RESTRICTION =
+            PACKAGE_NAME + ".action.CLEAR_USER_RESTRICTION";
+    public static final String EXTRA_USER_RESTRICTION = PACKAGE_NAME + ".extra.USER_RESTRICTION";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.d(TAG, "Received request " + intent.getAction());
+
+        final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        final ComponentName deviceAdmin = new ComponentName(PACKAGE_NAME,
+                TestDeviceAdmin.class.getName());
+        final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        final String userRestriction = intent.getStringExtra(EXTRA_USER_RESTRICTION);
+        boolean suspend = false;
+        boolean block = false;
+        switch (intent.getAction()) {
+            case ACTION_SUSPEND:
+                suspend = true;
+                // Intentional fall-through
+            case ACTION_UNSUSPEND:
+                if (packageName == null) {
+                    Log.e(TAG, "Need package to complete suspend/unsuspend request");
+                    break;
+                }
+                final String[] errored = dpm.setPackagesSuspended(deviceAdmin,
+                        new String[]{packageName}, suspend);
+                if (errored.length > 0) {
+                    Log.e(TAG, "Could not update packages: " + Arrays.toString(errored)
+                            + " for request: " + intent.getAction());
+                    break;
+                }
+                setResultCode(RESULT_OK);
+                break;
+            case ACTION_BLOCK_UNINSTALL:
+                block = true;
+                // Intentional fall-through
+            case ACTION_UNBLOCK_UNINSTALL:
+                if (packageName == null) {
+                    Log.e(TAG, "Need package to complete block/unblock uninstall request");
+                    break;
+                }
+                dpm.setUninstallBlocked(deviceAdmin, packageName, block);
+                setResultCode(RESULT_OK);
+                break;
+            case ACTION_ADD_USER_RESTRICTION:
+                if (userRestriction == null) {
+                    Log.e(TAG, "No user restriction provided to set");
+                    break;
+                }
+                dpm.addUserRestriction(deviceAdmin, userRestriction);
+                setResultCode(RESULT_OK);
+                break;
+            case ACTION_CLEAR_USER_RESTRICTION:
+                if (userRestriction == null) {
+                    Log.e(TAG, "No user restriction provided to clear");
+                    break;
+                }
+                dpm.clearUserRestriction(deviceAdmin, userRestriction);
+                setResultCode(RESULT_OK);
+                break;
+        }
+    }
+}
diff --git a/tests/suspendapps/test-apps/TestDeviceAdmin/src/com/android/suspendapps/testdeviceadmin/TestDeviceAdmin.java b/tests/suspendapps/test-apps/TestDeviceAdmin/src/com/android/suspendapps/testdeviceadmin/TestDeviceAdmin.java
new file mode 100644
index 0000000..400eef6
--- /dev/null
+++ b/tests/suspendapps/test-apps/TestDeviceAdmin/src/com/android/suspendapps/testdeviceadmin/TestDeviceAdmin.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.suspendapps.testdeviceadmin;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class TestDeviceAdmin extends DeviceAdminReceiver {
+}
diff --git a/tests/appsearch/Android.bp b/tests/suspendapps/tests/Android.bp
similarity index 69%
rename from tests/appsearch/Android.bp
rename to tests/suspendapps/tests/Android.bp
index 1f70dc4..15a239e 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/suspendapps/tests/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -13,20 +13,25 @@
 // limitations under the License.
 
 android_test {
-    name: "CtsAppSearchTestCases",
+    name: "CtsSuspendAppsTestCases",
     defaults: ["cts_defaults"],
-    static_libs: [
-      "androidx.test.ext.junit",
-      "androidx.test.rules",
-      "compatibility-device-util-axt",
-    ],
+
     srcs: [
-      "src/**/*.java",
+        "src/**/*.java",
+        ":CtsSuspendHelpersConstants",
     ],
-    test_suites: [
-      "cts",
-      "vts",
-      "general-tests",
+
+    static_libs: [
+        "ub-uiautomator",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
     ],
+
     platform_apis: true,
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
 }
diff --git a/tests/suspendapps/tests/AndroidManifest.xml b/tests/suspendapps/tests/AndroidManifest.xml
new file mode 100755
index 0000000..61dd2f2
--- /dev/null
+++ b/tests/suspendapps/tests/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.suspendapps.cts">
+
+    <application android:label="CTS Suspend Apps Test">
+        <activity android:name=".SuspendedDetailsActivity"
+                  android:permission="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS">
+            <intent-filter>
+                <action android:name="android.intent.action.SHOW_SUSPENDED_APP_DETAILS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <receiver android:name=".UnsuspendReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY" />
+            </intent-filter>
+        </receiver>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.suspendapps.cts"
+                     android:label="CTS Suspend Apps Test"/>
+</manifest>
diff --git a/tests/suspendapps/tests/AndroidTest.xml b/tests/suspendapps/tests/AndroidTest.xml
new file mode 100644
index 0000000..70d2aaf
--- /dev/null
+++ b/tests/suspendapps/tests/AndroidTest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<configuration description="Config for CTS Suspend Apps test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework"/>
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- TODO (b/150741315): Change when the module is ready for secondary user -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="CtsSuspendAppsTestCases.apk" />
+        <option name="test-file-name" value="CtsSuspendTestApp.apk" />
+        <option name="test-file-name" value="CtsSuspendTestApp2.apk" />
+        <option name="test-file-name" value="CtsSuspendTestDeviceAdmin.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- This is done to bring the app out of the stopped state -->
+        <option name="run-command" value="am start -a com.android.suspendapps.suspendtestapp.action.FINISH_TEST_ACTIVITY com.android.suspendapps.suspendtestapp/.SuspendTestActivity" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.suspendapps.cts" />
+        <option name="runtime-hint" value="30s" />
+    </test>
+
+</configuration>
diff --git a/tests/suspendapps/tests/res/drawable/ic_settings.xml b/tests/suspendapps/tests/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..138a680
--- /dev/null
+++ b/tests/suspendapps/tests/res/drawable/ic_settings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorAccent">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M21.4 14.2l-1.94-1.45c.03-.25 .04 -.5 .04 -.76s-.01-.51-.04-.76L21.4 9.8c.42-.31
+.52 -.94 .24 -1.41l-1.6-2.76c-.28-.48-.88-.7-1.36-.5l-2.14 .91
+c-.48-.37-1.01-.68-1.57-.92l-.27-2.2c-.06-.52-.56-.92-1.11-.92h-3.18c-.55 0-1.05
+.4 -1.11 .92 l-.26 2.19c-.57 .24 -1.1 .55 -1.58 .92 l-2.14-.91c-.48-.2-1.08 .02
+-1.36 .5 l-1.6 2.76c-.28 .48 -.18 1.1 .24 1.42l1.94 1.45c-.03 .24 -.04 .49 -.04
+.75 s.01 .51 .04 .76 L2.6 14.2c-.42 .31 -.52 .94 -.24 1.41l1.6 2.76c.28 .48 .88
+.7 1.36 .5 l2.14-.91c.48 .37 1.01 .68 1.57 .92 l.27 2.19c.06 .53 .56 .93 1.11
+.93 h3.18c.55 0 1.04-.4 1.11-.92l.27-2.19c.56-.24 1.09-.55 1.57-.92l2.14 .91
+c.48 .2 1.08-.02 1.36-.5l1.6-2.76c.28-.48 .18 -1.1-.24-1.42zM12 15.5c-1.93
+0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"/>
+</vector>
diff --git a/tests/suspendapps/tests/res/values/strings.xml b/tests/suspendapps/tests/res/values/strings.xml
new file mode 100755
index 0000000..a19edbc
--- /dev/null
+++ b/tests/suspendapps/tests/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_title">App is paused</string>
+    <string name="dialog_message">You cannot use %1$s anymore today</string>
+    <string name="more_details_button_text">Obscure text</string>
+    <string name="unsuspend_button_text">Escape hatch</string>
+</resources>
diff --git a/tests/suspendapps/tests/src/android/suspendapps/cts/AppCommunicationReceiver.java b/tests/suspendapps/tests/src/android/suspendapps/cts/AppCommunicationReceiver.java
new file mode 100644
index 0000000..ec07d3c
--- /dev/null
+++ b/tests/suspendapps/tests/src/android/suspendapps/cts/AppCommunicationReceiver.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.suspendapps.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wrapper around {@link BroadcastReceiver} to listen for communication from the test app.
+ */
+public class AppCommunicationReceiver extends BroadcastReceiver {
+    private static final String TAG = AppCommunicationReceiver.class.getSimpleName();
+    private Context mContext;
+    private boolean mRegistered;
+    private SynchronousQueue<Intent> mIntentQueue = new SynchronousQueue<>();
+
+    AppCommunicationReceiver(Context context) {
+        this.mContext = context;
+    }
+
+    void register(Handler handler, String... actions) {
+        mRegistered = true;
+        final IntentFilter intentFilter = new IntentFilter();
+        for (String action : actions) {
+            intentFilter.addAction(action);
+        }
+        mContext.registerReceiver(this, intentFilter, null, handler);
+    }
+
+    void unregister() {
+        if (mRegistered) {
+            mRegistered = false;
+            mContext.unregisterReceiver(this);
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.d(TAG, "AppCommunicationReceiver#onReceive: " + intent.getAction());
+        try {
+            mIntentQueue.offer(intent, 5, TimeUnit.SECONDS);
+        } catch (InterruptedException ie) {
+            throw new RuntimeException("Receiver thread interrupted", ie);
+        }
+    }
+
+    Intent pollForIntent(long secondsToWait) {
+        if (!mRegistered) {
+            throw new IllegalStateException("Receiver not registered");
+        }
+        final Intent intent;
+        try {
+            intent = mIntentQueue.poll(secondsToWait, TimeUnit.SECONDS);
+        } catch (InterruptedException ie) {
+            throw new RuntimeException("Interrupted while waiting for app broadcast", ie);
+        }
+        return intent;
+    }
+
+    void drainPendingBroadcasts() {
+        while (pollForIntent(5) != null) {
+            // Repeat until no incoming intent.
+        }
+    }
+}
diff --git a/tests/suspendapps/tests/src/android/suspendapps/cts/Constants.java b/tests/suspendapps/tests/src/android/suspendapps/cts/Constants.java
new file mode 100644
index 0000000..6604df4
--- /dev/null
+++ b/tests/suspendapps/tests/src/android/suspendapps/cts/Constants.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.suspendapps.cts;
+
+import com.android.suspendapps.testdeviceadmin.TestCommsReceiver;
+import com.android.suspendapps.testdeviceadmin.TestDeviceAdmin;
+
+public interface Constants {
+    String PACKAGE_NAME = "android.suspendapps.cts";
+    String ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED =
+            PACKAGE_NAME + ".action.REPORT_MORE_DETAILS_ACTIVITY_STARTED";
+    String ACTION_REPORT_PACKAGE_UNSUSPENDED_MANUALLY =
+            PACKAGE_NAME + ".action.REPORT_PACKAGE_UNSUSPENDED_MANUALLY";
+    String EXTRA_RECEIVED_PACKAGE_NAME = PACKAGE_NAME + ".extra.RECEIVED_PACKAGE_NAME";
+    String TEST_APP_PACKAGE_NAME = com.android.suspendapps.suspendtestapp.Constants.PACKAGE_NAME;
+    String TEST_APP_2_PACKAGE_NAME =
+            com.android.suspendapps.suspendtestapp.Constants.ANDROID_PACKAGE_NAME_2;
+    String[] TEST_PACKAGE_ARRAY = new String[] {TEST_APP_PACKAGE_NAME};
+    String[] ALL_TEST_PACKAGES = new String[] {TEST_APP_PACKAGE_NAME, TEST_APP_2_PACKAGE_NAME};
+    String DEVICE_ADMIN_PACKAGE = TestCommsReceiver.PACKAGE_NAME;
+    String DEVICE_ADMIN_COMPONENT =
+            DEVICE_ADMIN_PACKAGE + "/" + TestDeviceAdmin.class.getName();
+}
diff --git a/tests/suspendapps/tests/src/android/suspendapps/cts/DistractingPackageTest.java b/tests/suspendapps/tests/src/android/suspendapps/cts/DistractingPackageTest.java
new file mode 100644
index 0000000..01b7f5a
--- /dev/null
+++ b/tests/suspendapps/tests/src/android/suspendapps/cts/DistractingPackageTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.suspendapps.cts;
+
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.pm.PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+import static android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS;
+import static android.suspendapps.cts.Constants.ALL_TEST_PACKAGES;
+import static android.suspendapps.cts.Constants.DEVICE_ADMIN_COMPONENT;
+import static android.suspendapps.cts.Constants.TEST_APP_PACKAGE_NAME;
+import static android.suspendapps.cts.Constants.TEST_PACKAGE_ARRAY;
+import static android.suspendapps.cts.SuspendTestUtils.addAndAssertProfileOwner;
+import static android.suspendapps.cts.SuspendTestUtils.createSingleKeyBundle;
+import static android.suspendapps.cts.SuspendTestUtils.removeDeviceAdmin;
+import static android.suspendapps.cts.SuspendTestUtils.requestDpmAction;
+
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.ACTION_ADD_USER_RESTRICTION;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.ACTION_BLOCK_UNINSTALL;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.ACTION_UNBLOCK_UNINSTALL;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.EXTRA_USER_RESTRICTION;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.FeatureUtil;
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DistractingPackageTest {
+    private Context mContext;
+    private PackageManager mPackageManager;
+    private Handler mHandler;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mPackageManager = mContext.getPackageManager();
+        mHandler = new Handler(Looper.getMainLooper());
+    }
+
+    private int getCurrentUserId() {
+        final String result = SystemUtil.runShellCommand("am get-current-user",
+                output -> !output.isEmpty());
+        return Integer.parseInt(result.trim());
+    }
+
+    private void setDistractionFlagsAndAssertResult(String[] packagesToRestrict,
+            int distractionFlags, @NonNull String[] expectedToFail) throws Exception {
+        final String[] failed = SystemUtil.callWithShellPermissionIdentity(
+                () -> mPackageManager.setDistractingPackageRestrictions(packagesToRestrict,
+                        distractionFlags));
+        if (failed == null || failed.length != expectedToFail.length
+                || !ArrayUtils.containsAll(failed, expectedToFail)) {
+            fail("setDistractingPackageRestrictions failure: failed packages: " + Arrays.toString(
+                    failed) + "; expected to fail: " + Arrays.toString(expectedToFail));
+        }
+    }
+
+    @Test
+    public void testShouldHideFromSuggestions() throws Exception {
+        final int currentUserId = getCurrentUserId();
+        final LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+        assertFalse("shouldHideFromSuggestions true before setting the flag",
+                launcherApps.shouldHideFromSuggestions(TEST_APP_PACKAGE_NAME,
+                        UserHandle.of(currentUserId)));
+        setDistractionFlagsAndAssertResult(TEST_PACKAGE_ARRAY, RESTRICTION_HIDE_FROM_SUGGESTIONS,
+                ArrayUtils.emptyArray(String.class));
+        assertTrue("shouldHideFromSuggestions false after setting the flag",
+                launcherApps.shouldHideFromSuggestions(TEST_APP_PACKAGE_NAME,
+                        UserHandle.of(currentUserId)));
+    }
+
+    @Test
+    public void testCannotRestrictWhenUninstallBlocked() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        addAndAssertProfileOwner();
+        assertTrue("Block uninstall request failed", requestDpmAction(ACTION_BLOCK_UNINSTALL,
+                createSingleKeyBundle(EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME), mHandler));
+        setDistractionFlagsAndAssertResult(ALL_TEST_PACKAGES, RESTRICTION_HIDE_NOTIFICATIONS,
+                TEST_PACKAGE_ARRAY);
+    }
+
+    private void assertCannotRestrictUnderUserRestriction(String userRestriction) throws Exception {
+        addAndAssertProfileOwner();
+        final Bundle extras = createSingleKeyBundle(EXTRA_USER_RESTRICTION, userRestriction);
+        assertTrue("Request to add restriction" + userRestriction + " failed",
+                requestDpmAction(ACTION_ADD_USER_RESTRICTION, extras, mHandler));
+        setDistractionFlagsAndAssertResult(ALL_TEST_PACKAGES, RESTRICTION_HIDE_FROM_SUGGESTIONS,
+                ALL_TEST_PACKAGES);
+    }
+
+    @Test
+    public void testCannotRestrictUnderDisallowAppsControl() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        assertCannotRestrictUnderUserRestriction(UserManager.DISALLOW_APPS_CONTROL);
+    }
+
+    @Test
+    public void testCannotRestrictUnderDisallowUninstallApps() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        assertCannotRestrictUnderUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        if (dpm.isAdminActive(ComponentName.unflattenFromString(DEVICE_ADMIN_COMPONENT))) {
+            final Bundle extras = createSingleKeyBundle(EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME);
+            requestDpmAction(ACTION_UNBLOCK_UNINSTALL, extras, mHandler);
+            removeDeviceAdmin();
+        }
+        setDistractionFlagsAndAssertResult(ALL_TEST_PACKAGES, PackageManager.RESTRICTION_NONE,
+                ArrayUtils.emptyArray(String.class));
+    }
+}
diff --git a/tests/suspendapps/tests/src/android/suspendapps/cts/DualSuspendTests.java b/tests/suspendapps/tests/src/android/suspendapps/cts/DualSuspendTests.java
new file mode 100644
index 0000000..92f1927
--- /dev/null
+++ b/tests/suspendapps/tests/src/android/suspendapps/cts/DualSuspendTests.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.suspendapps.cts;
+
+import static android.suspendapps.cts.Constants.TEST_APP_PACKAGE_NAME;
+import static android.suspendapps.cts.SuspendTestUtils.addAndAssertProfileOwner;
+import static android.suspendapps.cts.SuspendTestUtils.createSingleKeyBundle;
+import static android.suspendapps.cts.SuspendTestUtils.removeDeviceAdmin;
+import static android.suspendapps.cts.SuspendTestUtils.requestDpmAction;
+import static com.android.suspendapps.suspendtestapp.SuspendTestReceiver.ACTION_REPORT_MY_PACKAGE_SUSPENDED;
+import static com.android.suspendapps.suspendtestapp.SuspendTestReceiver.ACTION_REPORT_MY_PACKAGE_UNSUSPENDED;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.ACTION_SUSPEND;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.ACTION_UNSUSPEND;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.FeatureUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class DualSuspendTests {
+    private Context mContext;
+    private Handler mReceiverHandler;
+    private AppCommunicationReceiver mAppCommsReceiver;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mReceiverHandler = new Handler(Looper.getMainLooper());
+        mAppCommsReceiver = new AppCommunicationReceiver(mContext);
+        assumeTrue("Skipping test that requires device admin",
+                FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        addAndAssertProfileOwner();
+    }
+
+    private boolean setSuspendViaDPM(boolean suspend) throws Exception {
+        return requestDpmAction(suspend ? ACTION_SUSPEND : ACTION_UNSUSPEND,
+                createSingleKeyBundle(Intent.EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME),
+                mReceiverHandler);
+    }
+
+    @Test
+    public void testMyPackageSuspended() throws Exception {
+        mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED);
+        SuspendTestUtils.suspend(null, null, null);
+        Intent receivedIntent = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive intent from app for first suspend", receivedIntent);
+        assertEquals(ACTION_REPORT_MY_PACKAGE_SUSPENDED, receivedIntent.getAction());
+        assertTrue("Suspend via dpm failed", setSuspendViaDPM(true));
+        receivedIntent = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive intent from app for second suspend", receivedIntent);
+        assertEquals(ACTION_REPORT_MY_PACKAGE_SUSPENDED, receivedIntent.getAction());
+    }
+
+    @Test
+    public void testMyPackageUnsuspended() throws Exception {
+        mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_UNSUSPENDED);
+        SuspendTestUtils.suspend(null, null, null);
+        assertTrue("Suspend via dpm failed", setSuspendViaDPM(true));
+        mAppCommsReceiver.drainPendingBroadcasts();
+        SuspendTestUtils.unsuspendAll();
+        Intent receivedIntent = mAppCommsReceiver.pollForIntent(5);
+        if (receivedIntent != null) {
+            fail("Unexpected intent " + receivedIntent.getAction() + " received");
+        }
+        assertTrue("Unsuspend via dpm failed", setSuspendViaDPM(false));
+        receivedIntent = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive intent after second unsuspend", receivedIntent);
+        assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, receivedIntent.getAction());
+    }
+
+    @Test
+    public void testIsPackageSuspended() throws Exception {
+        final PackageManager pm = mContext.getPackageManager();
+        assertFalse(pm.isPackageSuspended(TEST_APP_PACKAGE_NAME));
+        SuspendTestUtils.suspend(null, null, null);
+        assertTrue("Suspend via dpm failed", setSuspendViaDPM(true));
+        assertTrue("Package should be suspended by both",
+                pm.isPackageSuspended(TEST_APP_PACKAGE_NAME));
+        SuspendTestUtils.unsuspendAll();
+        assertTrue("Package should be suspended by dpm",
+                pm.isPackageSuspended(TEST_APP_PACKAGE_NAME));
+        SuspendTestUtils.suspend(null, null, null);
+        assertTrue("Unsuspend via dpm failed", setSuspendViaDPM(false));
+        assertTrue("Package should be suspended by shell",
+                pm.isPackageSuspended(TEST_APP_PACKAGE_NAME));
+        SuspendTestUtils.unsuspendAll();
+        assertFalse("Package should be suspended by neither",
+                pm.isPackageSuspended(TEST_APP_PACKAGE_NAME));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mAppCommsReceiver.unregister();
+        SuspendTestUtils.unsuspendAll();
+        setSuspendViaDPM(false);
+        removeDeviceAdmin();
+    }
+}
diff --git a/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java b/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java
new file mode 100644
index 0000000..21024f9
--- /dev/null
+++ b/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java
@@ -0,0 +1,652 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.suspendapps.cts;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND;
+import static android.os.UserManager.DISALLOW_APPS_CONTROL;
+import static android.os.UserManager.DISALLOW_UNINSTALL_APPS;
+import static android.suspendapps.cts.Constants.ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED;
+import static android.suspendapps.cts.Constants.ACTION_REPORT_PACKAGE_UNSUSPENDED_MANUALLY;
+import static android.suspendapps.cts.Constants.ALL_TEST_PACKAGES;
+import static android.suspendapps.cts.Constants.DEVICE_ADMIN_COMPONENT;
+import static android.suspendapps.cts.Constants.DEVICE_ADMIN_PACKAGE;
+import static android.suspendapps.cts.Constants.EXTRA_RECEIVED_PACKAGE_NAME;
+import static android.suspendapps.cts.Constants.TEST_APP_PACKAGE_NAME;
+import static android.suspendapps.cts.Constants.TEST_PACKAGE_ARRAY;
+import static android.suspendapps.cts.SuspendTestUtils.addAndAssertProfileOwner;
+import static android.suspendapps.cts.SuspendTestUtils.createSingleKeyBundle;
+import static android.suspendapps.cts.SuspendTestUtils.removeDeviceAdmin;
+import static android.suspendapps.cts.SuspendTestUtils.requestDpmAction;
+
+import static com.android.suspendapps.suspendtestapp.SuspendTestActivity.ACTION_REPORT_TEST_ACTIVITY_STARTED;
+import static com.android.suspendapps.suspendtestapp.SuspendTestActivity.ACTION_REPORT_TEST_ACTIVITY_STOPPED;
+import static com.android.suspendapps.suspendtestapp.SuspendTestReceiver.ACTION_REPORT_MY_PACKAGE_SUSPENDED;
+import static com.android.suspendapps.suspendtestapp.SuspendTestReceiver.ACTION_REPORT_MY_PACKAGE_UNSUSPENDED;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.ACTION_ADD_USER_RESTRICTION;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.ACTION_BLOCK_UNINSTALL;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.ACTION_SUSPEND;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.ACTION_UNBLOCK_UNINSTALL;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.ACTION_UNSUSPEND;
+import static com.android.suspendapps.testdeviceadmin.TestCommsReceiver.EXTRA_USER_RESTRICTION;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.SuspendDialogInfo;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.suspendapps.cts.R;
+import android.util.ArrayMap;
+
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.FeatureUtil;
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.suspendapps.suspendtestapp.SuspendTestActivity;
+import com.android.suspendapps.suspendtestapp.SuspendTestReceiver;
+
+import libcore.util.EmptyArray;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SuspendPackagesTest {
+    private static final String TEST_APP_LABEL = "Suspend Test App";
+
+    private Context mContext;
+    private PackageManager mPackageManager;
+    private AppOpsManager mAppOpsManager;
+    private Handler mReceiverHandler;
+    private AppCommunicationReceiver mAppCommsReceiver;
+    private UiDevice mUiDevice;
+
+    /** Do not use with {@link #mAppCommsReceiver} in the same test as both use the same handler. */
+    private Bundle requestAppAction(String action) throws InterruptedException {
+        final AtomicReference<Bundle> result = new AtomicReference<>();
+        final CountDownLatch receiverLatch = new CountDownLatch(1);
+        final ComponentName testReceiverComponent = new ComponentName(TEST_APP_PACKAGE_NAME,
+                SuspendTestReceiver.class.getCanonicalName());
+        final Intent broadcastIntent = new Intent(action)
+                .setComponent(testReceiverComponent)
+                .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendOrderedBroadcast(broadcastIntent, null, new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                result.set(getResultExtras(true));
+                receiverLatch.countDown();
+            }
+        }, mReceiverHandler, 0, null, null);
+
+        assertTrue("Test receiver timed out ", receiverLatch.await(5, TimeUnit.SECONDS));
+        return result.get();
+    }
+
+    private PersistableBundle getExtras(String keyPrefix, long lval, String sval, double dval) {
+        final PersistableBundle extras = new PersistableBundle(3);
+        extras.putLong(keyPrefix + ".LONG_VALUE", lval);
+        extras.putDouble(keyPrefix + ".DOUBLE_VALUE", dval);
+        extras.putString(keyPrefix + ".STRING_VALUE", sval);
+        return extras;
+    }
+
+    private void startTestAppActivity(@Nullable Bundle extras) {
+        final Intent testActivity = new Intent()
+                .setComponent(new ComponentName(TEST_APP_PACKAGE_NAME,
+                        SuspendTestActivity.class.getCanonicalName()))
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        if (extras != null) {
+            testActivity.putExtras(extras);
+        }
+        mContext.startActivity(testActivity);
+    }
+
+    private static boolean areSameExtras(BaseBundle expected, BaseBundle received) {
+        if (expected == null || received == null) {
+            return expected == received;
+        }
+        final Set<String> keys = expected.keySet();
+        if (keys.size() != received.keySet().size()) {
+            return false;
+        }
+        for (String key : keys) {
+            if (!Objects.equals(expected.get(key), received.get(key))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static void assertSameExtras(String message, BaseBundle expected, BaseBundle received) {
+        if (!areSameExtras(expected, received)) {
+            fail(message + ": [expected: " + expected + "; received: " + received + "]");
+        }
+    }
+
+    private void addAndAssertDeviceOwner() {
+        SystemUtil.runShellCommand("dpm set-device-owner --user cur " + DEVICE_ADMIN_COMPONENT,
+                output -> output.startsWith("Success"));
+    }
+
+    private void addAndAssertDeviceAdmin() {
+        SystemUtil.runShellCommand("dpm set-active-admin --user cur " + DEVICE_ADMIN_COMPONENT,
+                output -> output.startsWith("Success"));
+    }
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mPackageManager = mContext.getPackageManager();
+        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+        mReceiverHandler = new Handler(Looper.getMainLooper());
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mAppCommsReceiver = new AppCommunicationReceiver(mContext);
+    }
+
+    @Test
+    public void testIsPackageSuspended() throws Exception {
+        SuspendTestUtils.suspend(null, null, null);
+        assertTrue("isPackageSuspended is false",
+                mPackageManager.isPackageSuspended(TEST_APP_PACKAGE_NAME));
+    }
+
+    @Test
+    public void testSuspendedStateFromApp() throws Exception {
+        Bundle resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE);
+        assertFalse(resultFromApp.getBoolean(SuspendTestReceiver.EXTRA_SUSPENDED, true));
+        assertNull(resultFromApp.getBundle(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
+
+        final PersistableBundle appExtras = getExtras("testSuspendedStateFromApp", 20, "20", 0.2);
+        SuspendTestUtils.suspend(appExtras, null, null);
+
+        resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE);
+        assertTrue("resultFromApp:suspended is false",
+                resultFromApp.getBoolean(SuspendTestReceiver.EXTRA_SUSPENDED));
+        final Bundle receivedAppExtras =
+                resultFromApp.getBundle(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS);
+        assertSameExtras("Received app extras different to the ones supplied",
+                appExtras, receivedAppExtras);
+    }
+
+    @Test
+    public void testMyPackageSuspendedUnsuspended() throws Exception {
+        mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED,
+                ACTION_REPORT_MY_PACKAGE_UNSUSPENDED);
+        mAppCommsReceiver.drainPendingBroadcasts();
+        final PersistableBundle appExtras = getExtras("testMyPackageSuspendBroadcasts", 1, "1", .1);
+        SuspendTestUtils.suspend(appExtras, null, null);
+        Intent intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive any broadcast from test app", intentFromApp);
+        assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
+                ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
+        assertSameExtras("Received app extras different to the ones supplied", appExtras,
+                intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
+        SuspendTestUtils.unsuspendAll();
+        intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive any broadcast from test app", intentFromApp);
+        assertEquals("MY_PACKAGE_UNSUSPENDED delivery not reported",
+                ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction());
+    }
+
+    @Test
+    public void testUpdatingAppExtras() throws Exception {
+        mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED);
+        final PersistableBundle extras1 = getExtras("testMyPackageSuspendedOnChangingExtras", 1,
+                "1", 0.1);
+        SuspendTestUtils.suspend(extras1, null, null);
+        Intent intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive any broadcast from test app", intentFromApp);
+        assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
+                ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
+        assertSameExtras("Received app extras different to the ones supplied", extras1,
+                intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
+        final PersistableBundle extras2 = getExtras("testMyPackageSuspendedOnChangingExtras", 2,
+                "2", 0.2);
+        SuspendTestUtils.suspend(extras2, null, null);
+        intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive any broadcast from test app", intentFromApp);
+        assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
+                ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
+        assertSameExtras("Received app extras different to the updated extras", extras2,
+                intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
+    }
+
+    @Test
+    public void testCannotSuspendSelf() throws Exception {
+        final String[] self = new String[]{mContext.getPackageName()};
+        SuspendTestUtils.suspendAndAssertResult(self, null, null, null, self);
+    }
+
+    @Test
+    public void testActivityStoppedOnSuspend() throws Exception {
+        mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_TEST_ACTIVITY_STARTED,
+                ACTION_REPORT_TEST_ACTIVITY_STOPPED);
+        startTestAppActivity(null);
+        Intent intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive any broadcast from test app", intentFromApp);
+        assertEquals("Test activity start not reported",
+                ACTION_REPORT_TEST_ACTIVITY_STARTED, intentFromApp.getAction());
+        SuspendTestUtils.suspend(null, null, null);
+        intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive any broadcast from test app", intentFromApp);
+        assertEquals("Test activity stop not reported on suspending the test app",
+                ACTION_REPORT_TEST_ACTIVITY_STOPPED, intentFromApp.getAction());
+    }
+
+    @Test
+    public void testCanSuspendWhenProfileOwner() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        addAndAssertProfileOwner();
+        SuspendTestUtils.suspend(null, null, null);
+    }
+
+    @Test
+    public void testCanSuspendWhenDeviceOwner() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        addAndAssertDeviceOwner();
+        SuspendTestUtils.suspend(null, null, null);
+    }
+
+    private int getCurrentUserId() {
+        final String result = SystemUtil.runShellCommand("am get-current-user");
+        return Integer.parseInt(result.trim());
+    }
+
+    @Test
+    public void testLauncherCallback() throws Exception {
+        final PersistableBundle suppliedExtras = getExtras("testLauncherCallback", 2, "2", 0.2);
+        final int currentUserId = getCurrentUserId();
+        final AtomicReference<String> callBackErrors = new AtomicReference<>("");
+        final CountDownLatch oldCallbackLatch = new CountDownLatch(1);
+        final CountDownLatch newCallbackLatch = new CountDownLatch(1);
+        LauncherApps.Callback testCallback = new StubbedCallback() {
+            @Override
+            public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+                oldCallbackLatch.countDown();
+            }
+
+            @Override
+            public void onPackagesSuspended(String[] packageNames, UserHandle user,
+                    Bundle launcherExtras) {
+                final StringBuilder errorString = new StringBuilder();
+                if (!Arrays.equals(packageNames, TEST_PACKAGE_ARRAY)) {
+                    errorString.append("Received unexpected packageNames in onPackagesSuspended: ");
+                    errorString.append(Arrays.toString(packageNames));
+                    errorString.append(". ");
+                }
+                if (user.getIdentifier() != currentUserId) {
+                    errorString.append("Received wrong user " + user.getIdentifier()
+                            + ", current user: " + currentUserId + ". ");
+                }
+                if (!areSameExtras(launcherExtras, suppliedExtras)) {
+                    errorString.append("Unexpected launcherExtras, supplied: " + suppliedExtras
+                            + ", received: " + launcherExtras + ". ");
+                }
+                callBackErrors.set(errorString.toString());
+                newCallbackLatch.countDown();
+            }
+        };
+        final LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+        launcherApps.registerCallback(testCallback, mReceiverHandler);
+        SuspendTestUtils.suspend(null, suppliedExtras, null);
+        assertFalse("Old callback invoked", oldCallbackLatch.await(2, TimeUnit.SECONDS));
+        assertTrue("New callback not invoked", newCallbackLatch.await(2, TimeUnit.SECONDS));
+        final String errors = callBackErrors.get();
+        assertTrue("Callbacks did not complete as expected: " + errors, errors.isEmpty());
+        launcherApps.unregisterCallback(testCallback);
+    }
+
+    private void turnScreenOn() throws Exception {
+        if (!mUiDevice.isScreenOn()) {
+            mUiDevice.wakeUp();
+        }
+        SystemUtil.runShellCommandForNoOutput("wm dismiss-keyguard");
+    }
+
+    @Test
+    public void testInterceptorActivity_moreDetails() throws Exception {
+        turnScreenOn();
+        mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED,
+                ACTION_REPORT_TEST_ACTIVITY_STARTED);
+        final SuspendDialogInfo dialogInfo = new SuspendDialogInfo.Builder()
+                .setIcon(R.drawable.ic_settings)
+                .setTitle(R.string.dialog_title)
+                .setMessage(R.string.dialog_message)
+                .setNeutralButtonText(R.string.more_details_button_text)
+                .build();
+        SuspendTestUtils.suspend(null, null, dialogInfo);
+        startTestAppActivity(null);
+        // Test activity should not start
+        assertNull("No broadcast was expected from app", mAppCommsReceiver.pollForIntent(2));
+
+        // The dialog should have correct specifications
+        final String expectedTitle = mContext.getResources().getString(R.string.dialog_title);
+        final String expectedMessage = mContext.getResources().getString(R.string.dialog_message,
+                TEST_APP_LABEL);
+        final String expectedButtonText = mContext.getResources().getString(
+                R.string.more_details_button_text);
+
+        assertNotNull("Given dialog title: " + expectedTitle + " not shown",
+                mUiDevice.wait(Until.findObject(By.text(expectedTitle)), 5000));
+        assertNotNull("Given dialog message: " + expectedMessage + " not shown",
+                mUiDevice.findObject(By.text(expectedMessage)));
+        // Sometimes, button texts can have styles that override case (e.g. b/134033532)
+        final Pattern buttonTextIgnoreCase = Pattern.compile(Pattern.quote(expectedButtonText),
+                Pattern.CASE_INSENSITIVE);
+        final UiObject2 moreDetailsButton = mUiDevice.findObject(
+                By.clickable(true).text(buttonTextIgnoreCase));
+        assertNotNull(expectedButtonText + " button not shown", moreDetailsButton);
+
+        // Tapping on the neutral button should start the correct intent.
+        moreDetailsButton.click();
+        final Intent intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive any broadcast from test app", intentFromApp);
+        assertEquals("More details activity start not reported",
+                ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED, intentFromApp.getAction());
+        final String receivedPackageName = intentFromApp.getStringExtra(
+                EXTRA_RECEIVED_PACKAGE_NAME);
+        assertEquals("Wrong package name received by \'More Details\' activity",
+                TEST_APP_PACKAGE_NAME, receivedPackageName);
+    }
+
+    @Test
+    public void testInterceptorActivity_unsuspend() throws Exception {
+        turnScreenOn();
+        mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_PACKAGE_UNSUSPENDED_MANUALLY,
+                ACTION_REPORT_TEST_ACTIVITY_STARTED);
+        final SuspendDialogInfo dialogInfo = new SuspendDialogInfo.Builder()
+                .setIcon(R.drawable.ic_settings)
+                .setTitle(R.string.dialog_title)
+                .setMessage(R.string.dialog_message)
+                .setNeutralButtonText(R.string.unsuspend_button_text)
+                .setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND)
+                .build();
+        SuspendTestUtils.suspend(null, null, dialogInfo);
+        final Bundle extrasForStart = new Bundle(getExtras("unsuspend", 90, "sval", 0.9));
+        startTestAppActivity(extrasForStart);
+        // Test activity should not start. Not yet.
+        assertNull("No broadcast was expected from app", mAppCommsReceiver.pollForIntent(2));
+
+        final String expectedTitle = mContext.getResources().getString(R.string.dialog_title);
+        final String expectedButtonText = mContext.getResources().getString(
+                R.string.unsuspend_button_text);
+
+        assertNotNull("Given dialog title: " + expectedTitle + " not shown",
+                mUiDevice.wait(Until.findObject(By.text(expectedTitle)), 5000));
+
+        // Sometimes, button texts can have styles that override case (e.g. b/134033532)
+        final Pattern buttonTextIgnoreCase = Pattern.compile(Pattern.quote(expectedButtonText),
+                Pattern.CASE_INSENSITIVE);
+        final UiObject2 unsuspendButton = mUiDevice.findObject(
+                By.clickable(true).text(buttonTextIgnoreCase));
+        assertNotNull(expectedButtonText + " button not shown", unsuspendButton);
+
+        // Tapping on the neutral button should:
+        // 1. Unsuspend the test app
+        // 2. Launch the previously intercepted intent
+        // 3. Tell the suspending package that the test app was unsuspended
+        unsuspendButton.click();
+
+        final ArrayMap<String, Bundle> receivedActionExtras = new ArrayMap<>(2);
+        Intent intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Did not receive any broadcast from test app", intentFromApp);
+        receivedActionExtras.put(intentFromApp.getAction(), intentFromApp.getExtras());
+
+        intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("Expecting 2 broadcasts, received only 1", intentFromApp);
+        receivedActionExtras.put(intentFromApp.getAction(), intentFromApp.getExtras());
+
+        assertTrue("Test activity start not reported",
+                receivedActionExtras.containsKey(ACTION_REPORT_TEST_ACTIVITY_STARTED));
+        assertSameExtras("Did not receive extras supplied to startActivity on unsuspend",
+                extrasForStart, receivedActionExtras.get(ACTION_REPORT_TEST_ACTIVITY_STARTED));
+
+        assertTrue("PACKAGE_UNSUSPENDED_MANUALLY not reported",
+                receivedActionExtras.containsKey(ACTION_REPORT_PACKAGE_UNSUSPENDED_MANUALLY));
+        final Bundle extras = receivedActionExtras.get(ACTION_REPORT_PACKAGE_UNSUSPENDED_MANUALLY);
+        assertNotNull("No extras with ACTION_REPORT_PACKAGE_UNSUSPENDED_MANUALLY", extras);
+        assertEquals("Did not receive unsuspended package name", TEST_APP_PACKAGE_NAME,
+                extras.getString(EXTRA_RECEIVED_PACKAGE_NAME));
+
+        assertFalse("Test package still suspended",
+                mPackageManager.isPackageSuspended(TEST_APP_PACKAGE_NAME));
+
+        intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNull("Expecting only 2 broadcasts, received one more ", intentFromApp);
+    }
+
+    @Test
+    public void testTestAppsSuspendable() throws Exception {
+        final String[] unsuspendablePkgs = SystemUtil.callWithShellPermissionIdentity(() ->
+                mPackageManager.getUnsuspendablePackages(ALL_TEST_PACKAGES));
+        assertTrue("Normal test apps unsuspendable: " + Arrays.toString(unsuspendablePkgs),
+                unsuspendablePkgs.length == 0);
+    }
+
+    @Test
+    public void testDeviceAdminUnsuspendable() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        addAndAssertDeviceAdmin();
+        final String[] unsuspendablePkgs = SystemUtil.callWithShellPermissionIdentity(() ->
+                mPackageManager.getUnsuspendablePackages(new String[]{DEVICE_ADMIN_PACKAGE}));
+        assertTrue("Device admin suspendable", (unsuspendablePkgs.length == 1)
+                && DEVICE_ADMIN_PACKAGE.equals(unsuspendablePkgs[0]));
+    }
+
+    private void assertOpDisallowed(int op) throws Exception {
+        final int testUid = mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0);
+        final int mode = SystemUtil.callWithShellPermissionIdentity(
+                () -> mAppOpsManager.checkOpNoThrow(op, testUid, TEST_APP_PACKAGE_NAME));
+        assertNotEquals("Op " + AppOpsManager.opToName(op) + " allowed while package is suspended",
+                MODE_ALLOWED, mode);
+    }
+
+    @Test
+    public void testOpRecordAudioOnSuspend() throws Exception {
+        final int recordAudioOp = AppOpsManager.permissionToOpCode(
+                Manifest.permission.RECORD_AUDIO);
+        SuspendTestUtils.suspend(null, null, null);
+        assertOpDisallowed(recordAudioOp);
+    }
+
+    @Test
+    public void testOpCameraOnSuspend() throws Exception {
+        final int cameraOp = AppOpsManager.permissionToOpCode(Manifest.permission.CAMERA);
+        SuspendTestUtils.suspend(null, null, null);
+        assertOpDisallowed(cameraOp);
+    }
+
+    @Test
+    public void testCannotSuspendWhenUninstallBlocked() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        addAndAssertProfileOwner();
+        final Bundle extras = createSingleKeyBundle(EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME);
+        assertTrue("Block uninstall request failed", requestDpmAction(
+                ACTION_BLOCK_UNINSTALL, extras, mReceiverHandler));
+        SuspendTestUtils.suspendAndAssertResult(ALL_TEST_PACKAGES, null, null, null,
+                TEST_PACKAGE_ARRAY);
+    }
+
+    private void assertCannotSuspendUnderUserRestriction(String userRestriction) throws Exception {
+        addAndAssertProfileOwner();
+        final Bundle extras = createSingleKeyBundle(EXTRA_USER_RESTRICTION, userRestriction);
+        assertTrue("Request to add restriction" + userRestriction + " failed",
+                requestDpmAction(ACTION_ADD_USER_RESTRICTION, extras, mReceiverHandler));
+        SuspendTestUtils.suspendAndAssertResult(ALL_TEST_PACKAGES, null, null, null,
+                ALL_TEST_PACKAGES);
+    }
+
+    @Test
+    public void testCannotSuspendUnderDisallowAppsControl() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        assertCannotSuspendUnderUserRestriction(DISALLOW_APPS_CONTROL);
+    }
+
+    @Test
+    public void testCannotSuspendUnderDisallowUninstallApps() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        assertCannotSuspendUnderUserRestriction(DISALLOW_UNINSTALL_APPS);
+    }
+
+    private void assertDpmCanSuspendUnderUserRestriction(String userRestriction) throws Exception {
+        addAndAssertProfileOwner();
+        mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED);
+        mAppCommsReceiver.drainPendingBroadcasts();
+        Bundle extra = createSingleKeyBundle(EXTRA_USER_RESTRICTION, userRestriction);
+        assertTrue("Request to add restriction" + userRestriction + " failed",
+                requestDpmAction(ACTION_ADD_USER_RESTRICTION, extra, mReceiverHandler));
+        extra = createSingleKeyBundle(Intent.EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME);
+        assertTrue("Request to suspend via dpm failed",
+                requestDpmAction(ACTION_SUSPEND, extra, mReceiverHandler));
+        final Intent intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("No intent received from app", intentFromApp);
+        assertEquals(ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
+    }
+
+    @Test
+    public void testDpmCanSuspendUnderDisallowAppsControl() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        assertDpmCanSuspendUnderUserRestriction(DISALLOW_APPS_CONTROL);
+    }
+
+    @Test
+    public void testDpmCanSuspendUnderDisallowUninstallApps() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        assertDpmCanSuspendUnderUserRestriction(DISALLOW_UNINSTALL_APPS);
+    }
+
+    private void assertUnsuspendedOnUserRestriction(String userRestriction) throws Exception {
+        assertTrue("Test app not suspended before setting user restriction",
+                mPackageManager.isPackageSuspended(TEST_APP_PACKAGE_NAME));
+        mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_UNSUSPENDED);
+        mAppCommsReceiver.drainPendingBroadcasts();
+        addAndAssertProfileOwner();
+        final Bundle extras = createSingleKeyBundle(EXTRA_USER_RESTRICTION, userRestriction);
+        assertTrue("Request to add restriction " + userRestriction + " failed",
+                requestDpmAction(ACTION_ADD_USER_RESTRICTION, extras, mReceiverHandler));
+        final Intent intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("No intent received from app", intentFromApp);
+        assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction());
+    }
+
+    @Test
+    public void testUnsuspendedOnDisallowUninstallApps() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        SuspendTestUtils.suspend(null, null, null);
+        assertUnsuspendedOnUserRestriction(DISALLOW_UNINSTALL_APPS);
+    }
+
+    @Test
+    public void testUnsuspendedOnDisallowAppsControl() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        SuspendTestUtils.suspend(null, null, null);
+        assertUnsuspendedOnUserRestriction(DISALLOW_APPS_CONTROL);
+    }
+
+    @Test
+    public void testUnsuspendedOnUninstallBlocked() throws Exception {
+        assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+        SuspendTestUtils.suspendAndAssertResult(ALL_TEST_PACKAGES, null, null, null,
+                EmptyArray.STRING);
+        mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_UNSUSPENDED);
+        mAppCommsReceiver.drainPendingBroadcasts();
+        addAndAssertProfileOwner();
+        final Bundle extras = createSingleKeyBundle(EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME);
+        assertTrue("Block uninstall request failed", requestDpmAction(
+                ACTION_BLOCK_UNINSTALL, extras, mReceiverHandler));
+        final Intent intentFromApp = mAppCommsReceiver.pollForIntent(5);
+        assertNotNull("No intent received from app", intentFromApp);
+        assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction());
+        assertEquals(TEST_APP_PACKAGE_NAME, intentFromApp.getStringExtra(EXTRA_PACKAGE_NAME));
+        assertNull("Unexpected 2nd broadcast", mAppCommsReceiver.pollForIntent(5));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        if (dpm.isAdminActive(ComponentName.unflattenFromString(DEVICE_ADMIN_COMPONENT))) {
+            final Bundle extras = createSingleKeyBundle(EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME);
+            requestDpmAction(ACTION_UNBLOCK_UNINSTALL, extras, mReceiverHandler);
+            requestDpmAction(ACTION_UNSUSPEND, extras, mReceiverHandler);
+            removeDeviceAdmin();
+        }
+        SuspendTestUtils.unsuspendAll();
+        mAppCommsReceiver.unregister();
+    }
+
+    private abstract static class StubbedCallback extends LauncherApps.Callback {
+        @Override
+        public void onPackageRemoved(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageAdded(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageChanged(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
+        }
+
+        @Override
+        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+    }
+}
diff --git a/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendTestUtils.java b/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendTestUtils.java
new file mode 100644
index 0000000..f2d8572
--- /dev/null
+++ b/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendTestUtils.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.suspendapps.cts;
+
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+
+import static android.suspendapps.cts.Constants.ALL_TEST_PACKAGES;
+import static android.suspendapps.cts.Constants.DEVICE_ADMIN_COMPONENT;
+import static android.suspendapps.cts.Constants.DEVICE_ADMIN_PACKAGE;
+import static android.suspendapps.cts.Constants.TEST_PACKAGE_ARRAY;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.SuspendDialogInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PersistableBundle;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.internal.util.ArrayUtils;
+import com.android.suspendapps.testdeviceadmin.TestCommsReceiver;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class SuspendTestUtils {
+
+    private SuspendTestUtils() {
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    // Wrapping the suspend/unsuspend methods in another class is needed to avoid error in pre-Q
+    // devices. Direct methods of the test class are enumerated and visited by JUnit, and the
+    // Q+ class SuspendDialogInfo being a parameter type triggers an exception. See b/129913414.
+    static void suspend(PersistableBundle appExtras,
+            PersistableBundle launcherExtras, SuspendDialogInfo dialogInfo) throws Exception {
+        suspendAndAssertResult(TEST_PACKAGE_ARRAY, appExtras, launcherExtras, dialogInfo,
+                EmptyArray.STRING);
+    }
+
+    static void suspendAndAssertResult(String[] packagesToSuspend, PersistableBundle appExtras,
+            PersistableBundle launcherExtras, SuspendDialogInfo dialogInfo,
+            @NonNull String[] expectedToFail) throws Exception {
+        final PackageManager packageManager = getContext().getPackageManager();
+        final String[] failed = SystemUtil.callWithShellPermissionIdentity(
+                () -> packageManager.setPackagesSuspended(packagesToSuspend, true, appExtras,
+                        launcherExtras, dialogInfo));
+        if (failed == null || failed.length != expectedToFail.length
+                || !ArrayUtils.containsAll(failed, expectedToFail)) {
+            fail("setPackagesSuspended failure: failed packages: " + Arrays.toString(failed)
+                    + "; Expected to fail: " + Arrays.toString(expectedToFail));
+        }
+    }
+
+    static void unsuspendAll() throws Exception {
+        final PackageManager packageManager = getContext().getPackageManager();
+        final String[] unchangedPackages = SystemUtil.callWithShellPermissionIdentity(() ->
+                packageManager.setPackagesSuspended(ALL_TEST_PACKAGES, false, null, null,
+                        (SuspendDialogInfo) null));
+        assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0);
+    }
+
+    static Bundle createSingleKeyBundle(String key, String value) {
+        final Bundle extras = new Bundle(1);
+        extras.putString(key, value);
+        return extras;
+    }
+
+    static void addAndAssertProfileOwner() {
+        SystemUtil.runShellCommand("dpm set-profile-owner --user cur " + DEVICE_ADMIN_COMPONENT,
+                output -> output.startsWith("Success"));
+    }
+
+    static void removeDeviceAdmin() {
+        SystemUtil.runShellCommand("dpm remove-active-admin --user cur " + DEVICE_ADMIN_COMPONENT);
+    }
+
+    /**
+     * Uses broadcasts to request a specific action from device admin via {@link TestCommsReceiver}
+     *
+     * @return true if the request succeeded
+     */
+    static boolean requestDpmAction(String action, @Nullable Bundle extras, Handler resultHandler)
+            throws InterruptedException {
+        final Intent requestIntent = new Intent(action)
+                .setClassName(DEVICE_ADMIN_PACKAGE, TestCommsReceiver.class.getName())
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        if (extras != null) {
+            requestIntent.putExtras(extras);
+        }
+        final CountDownLatch resultLatch = new CountDownLatch(1);
+        final AtomicInteger result = new AtomicInteger(RESULT_CANCELED);
+        getContext().sendOrderedBroadcast(requestIntent, null,
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        result.set(getResultCode());
+                        resultLatch.countDown();
+                    }
+                }, resultHandler, RESULT_CANCELED, null, null);
+        assertTrue("Broadcast " + requestIntent.getAction() + " timed out",
+                resultLatch.await(10, TimeUnit.SECONDS));
+        return result.get() == RESULT_OK;
+    }
+}
diff --git a/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendedDetailsActivity.java b/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendedDetailsActivity.java
new file mode 100644
index 0000000..932a51b
--- /dev/null
+++ b/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendedDetailsActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.suspendapps.cts;
+
+import static android.suspendapps.cts.Constants.ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED;
+import static android.suspendapps.cts.Constants.EXTRA_RECEIVED_PACKAGE_NAME;
+import static android.suspendapps.cts.Constants.PACKAGE_NAME;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.util.Log;
+
+public class SuspendedDetailsActivity extends Activity {
+    private static final String TAG = SuspendedDetailsActivity.class.getSimpleName();
+
+    @Override
+    protected void onStart() {
+        Log.d(TAG, "onStart");
+        final String suspendedPackage =  getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        super.onStart();
+        final Intent reportStart = new Intent(ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED)
+                .putExtra(EXTRA_RECEIVED_PACKAGE_NAME, suspendedPackage)
+                .setPackage(PACKAGE_NAME);
+        sendBroadcast(reportStart);
+        finish();
+    }
+}
diff --git a/tests/suspendapps/tests/src/android/suspendapps/cts/UnsuspendReceiver.java b/tests/suspendapps/tests/src/android/suspendapps/cts/UnsuspendReceiver.java
new file mode 100644
index 0000000..8304ccc
--- /dev/null
+++ b/tests/suspendapps/tests/src/android/suspendapps/cts/UnsuspendReceiver.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.suspendapps.cts;
+
+import static android.suspendapps.cts.Constants.ACTION_REPORT_PACKAGE_UNSUSPENDED_MANUALLY;
+import static android.suspendapps.cts.Constants.EXTRA_RECEIVED_PACKAGE_NAME;
+import static android.suspendapps.cts.Constants.PACKAGE_NAME;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class UnsuspendReceiver extends BroadcastReceiver {
+    private static final String TAG = UnsuspendReceiver.class.getSimpleName();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        switch (intent.getAction()) {
+            case Intent.ACTION_PACKAGE_UNSUSPENDED_MANUALLY:
+                final String suspendedPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                final Intent reportReceipt = new Intent(ACTION_REPORT_PACKAGE_UNSUSPENDED_MANUALLY)
+                        .setPackage(PACKAGE_NAME)
+                        .putExtra(EXTRA_RECEIVED_PACKAGE_NAME, suspendedPackage);
+                context.sendBroadcast(reportReceipt);
+                break;
+            default:
+                Log.w(TAG, "Unknown action " + intent.getAction());
+                break;
+        }
+    }
+}
diff --git a/tests/tests/appenumeration/Android.bp b/tests/tests/appenumeration/Android.bp
index 2304cef..3bb83dc 100644
--- a/tests/tests/appenumeration/Android.bp
+++ b/tests/tests/appenumeration/Android.bp
@@ -26,6 +26,7 @@
         "compatibility-device-util-axt",
 	    "androidx.test.ext.junit",
         "hamcrest-library",
+	    "CtsAppEnumerationTestLib",
     ],
 
     srcs: ["src/**/*.java"],
diff --git a/tests/tests/appenumeration/AndroidManifest.xml b/tests/tests/appenumeration/AndroidManifest.xml
index 3d96163..9018766 100644
--- a/tests/tests/appenumeration/AndroidManifest.xml
+++ b/tests/tests/appenumeration/AndroidManifest.xml
@@ -18,7 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.appenumeration.cts">
 
-    <!-- Used to get details about apps that the test apps have visibility of -->
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application>
diff --git a/tests/tests/appenumeration/app/source/Android.bp b/tests/tests/appenumeration/app/source/Android.bp
index 1250634..01d26fa 100644
--- a/tests/tests/appenumeration/app/source/Android.bp
+++ b/tests/tests/appenumeration/app/source/Android.bp
@@ -12,156 +12,141 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+java_defaults {
+    name: "CtsAppEnumerationQueriesDefaults",
+    srcs: ["src/**/*.java"],
+    static_libs: ["CtsAppEnumerationTestLib"],
+    sdk_version: "test_current",
+}
+
 android_test_helper_app {
     name: "CtsAppEnumerationQueriesNothing",
     manifest: "AndroidManifest-queriesNothing.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationQueriesActivityViaAction",
     manifest: "AndroidManifest-queriesActivityAction.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationQueriesServiceViaAction",
     manifest: "AndroidManifest-queriesServiceAction.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationQueriesProviderViaAuthority",
     manifest: "AndroidManifest-queriesProviderAuthority.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationQueriesUnexportedActivityViaAction",
     manifest: "AndroidManifest-queriesUnexportedActivityAction.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationQueriesUnexportedServiceViaAction",
     manifest: "AndroidManifest-queriesUnexportedServiceAction.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationQueriesUnexportedProviderViaAuthority",
     manifest: "AndroidManifest-queriesUnexportedProviderAuthority.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationQueriesPackage",
     manifest: "AndroidManifest-queriesPackage.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationQueriesNothingTargetsQ",
     manifest: "AndroidManifest-queriesNothing-targetsQ.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationQueriesNothingHasPermission",
     manifest: "AndroidManifest-queriesNothing-hasPermission.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationSharedUidSource",
     manifest: "AndroidManifest-queriesNothing-sharedUser.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
\ No newline at end of file
diff --git a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
index 3fe95c7..01aef3c 100644
--- a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
+++ b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
@@ -16,9 +16,21 @@
 
 package android.appenumeration.cts.query;
 
+import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
+import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_PROVIDERS;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_SERVICES;
+import static android.appenumeration.cts.Constants.ACTION_SEND_RESULT;
+import static android.appenumeration.cts.Constants.ACTION_START_DIRECTLY;
+import static android.appenumeration.cts.Constants.ACTION_START_FOR_RESULT;
+import static android.appenumeration.cts.Constants.EXTRA_ERROR;
+import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
 import static android.content.Intent.EXTRA_RETURN_RESULT;
 
 import android.app.Activity;
+import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
@@ -38,41 +50,53 @@
     }
 
     private void handleIntent(Intent intent) {
-        RemoteCallback remoteCallback = intent.getParcelableExtra("remoteCallback");
-        final String action = intent.getAction();
-        final Intent queryIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
-        if ("android.appenumeration.cts.action.GET_PACKAGE_INFO".equals(action)) {
-            final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-            sendPackageInfo(remoteCallback, packageName);
-        } else if ("android.appenumeration.cts.action.START_FOR_RESULT".equals(action)) {
-            final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-            int requestCode = RESULT_FIRST_USER + callbacks.size();
-            callbacks.put(requestCode, remoteCallback);
-            startActivityForResult(
-                    new Intent("android.appenumeration.cts.action.SEND_RESULT").setComponent(
-                            new ComponentName(packageName, getClass().getCanonicalName())),
-                    requestCode);
-            // don't send anything... await result callback
-        } else if ("android.appenumeration.cts.action.SEND_RESULT".equals(action)) {
-            try {
-                setResult(RESULT_OK,
-                        getIntent().putExtra(
-                                Intent.EXTRA_RETURN_RESULT,
-                                getPackageManager().getPackageInfo(getCallingPackage(), 0)));
-            } catch (PackageManager.NameNotFoundException e) {
-                setResult(RESULT_FIRST_USER, new Intent().putExtra("error", e));
+        RemoteCallback remoteCallback = intent.getParcelableExtra(EXTRA_REMOTE_CALLBACK);
+        try {
+            final String action = intent.getAction();
+            final Intent queryIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+            if (ACTION_GET_PACKAGE_INFO.equals(action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                sendPackageInfo(remoteCallback, packageName);
+            } else if (ACTION_START_FOR_RESULT.equals(action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                int requestCode = RESULT_FIRST_USER + callbacks.size();
+                callbacks.put(requestCode, remoteCallback);
+                startActivityForResult(
+                        new Intent(ACTION_SEND_RESULT).setComponent(
+                                new ComponentName(packageName, getClass().getCanonicalName())),
+                        requestCode);
+                // don't send anything... await result callback
+            } else if (ACTION_SEND_RESULT.equals(action)) {
+                try {
+                    setResult(RESULT_OK,
+                            getIntent().putExtra(
+                                    Intent.EXTRA_RETURN_RESULT,
+                                    getPackageManager().getPackageInfo(getCallingPackage(), 0)));
+                } catch (PackageManager.NameNotFoundException e) {
+                    setResult(RESULT_FIRST_USER, new Intent().putExtra("error", e));
+                }
+                finish();
+            } else if (ACTION_QUERY_ACTIVITIES.equals(action)) {
+                sendQueryIntentActivities(remoteCallback, queryIntent);
+            } else if (ACTION_QUERY_SERVICES.equals(action)) {
+                sendQueryIntentServices(remoteCallback, queryIntent);
+            } else if (ACTION_QUERY_PROVIDERS.equals(action)) {
+                sendQueryIntentProviders(remoteCallback, queryIntent);
+            } else if (ACTION_START_DIRECTLY.equals(action)) {
+                try {
+                    startActivity(queryIntent);
+                    remoteCallback.sendResult(new Bundle());
+                } catch (ActivityNotFoundException e) {
+                    sendError(remoteCallback, e);
+                }
+                finish();
+            } else if (ACTION_GET_INSTALLED_PACKAGES.equals(action)) {
+                sendGetInstalledPackages(remoteCallback, queryIntent.getIntExtra(EXTRA_FLAGS, 0));
+            } else {
+                sendError(remoteCallback, new Exception("unknown action " + action));
             }
-            finish();
-        } else if ("android.appenumeration.cts.action.QUERY_INTENT_ACTIVITIES".equals(action)) {
-            sendQueryIntentActivities(remoteCallback, queryIntent);
-        } else if ("android.appenumeration.cts.action.QUERY_INTENT_SERVICES".equals(action)) {
-            sendQueryIntentServices(remoteCallback, queryIntent);
-        } else if ("android.appenumeration.cts.action.QUERY_INTENT_PROVIDERS".equals(action)) {
-            sendQueryIntentProviders(remoteCallback, queryIntent);
-        } else if ("android.appenumeration.cts.action.GET_INSTALLED_PACKAGES".equals(action)) {
-            sendGetInstalledPackages(remoteCallback, queryIntent.getIntExtra("flags", 0));
-        } else {
-            sendError(remoteCallback, new Exception("unknown action " + action));
+        } catch (Exception e) {
+            sendError(remoteCallback, e);
         }
     }
 
@@ -124,7 +148,7 @@
 
     private void sendError(RemoteCallback remoteCallback, Exception failure) {
         Bundle result = new Bundle();
-        result.putSerializable("error", failure);
+        result.putSerializable(EXTRA_ERROR, failure);
         remoteCallback.sendResult(result);
         finish();
     }
@@ -148,7 +172,7 @@
         super.onActivityResult(requestCode, resultCode, data);
         final RemoteCallback remoteCallback = callbacks.get(requestCode);
         if (resultCode != RESULT_OK) {
-            Exception e = (Exception) data.getSerializableExtra("error");
+            Exception e = (Exception) data.getSerializableExtra(EXTRA_ERROR);
             sendError(remoteCallback, e == null ? new Exception("Result was " + resultCode) : e);
             return;
         }
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
index fdcab79..82e356f 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
@@ -22,6 +22,7 @@
         <activity android:name="android.appenumeration.testapp.DummyActivity">
             <intent-filter>
                 <action android:name="android.appenumeration.action.ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
         <service android:name="android.appenumeration.testapp.DummyService">
@@ -42,23 +43,23 @@
             </intent-filter>
         </receiver>
 
-        <activity android:name="android.appenumeration.testapp.DummyActivity"
+        <activity android:name="android.appenumeration.testapp.DummyActivityNotExported"
                   android:exported="false">
             <intent-filter>
                 <action android:name="android.appenumeration.action.ACTIVITY_UNEXPORTED" />
             </intent-filter>
         </activity>
-        <service android:name="android.appenumeration.testapp.DummyService"
+        <service android:name="android.appenumeration.testapp.DummyServiceNotExported"
                  android:exported="false">
             <intent-filter>
                 <action android:name="android.appenumeration.action.SERVICE_UNEXPORTED" />
             </intent-filter>
         </service>
-        <provider android:name="android.appenumeration.testapp.DummyProvider"
+        <provider android:name="android.appenumeration.testapp.DummyProviderNotExported"
                   android:authorities="android.appenumeration.testapp.unexported"
                   android:exported="false" >
         </provider>
-        <receiver android:name="android.appenumeration.testapp.DummyReceiver"
+        <receiver android:name="android.appenumeration.testapp.DummyReceiverNotExported"
                   android:exported="false">
             <intent-filter>
                 <action android:name="android.appenumeration.action.BROADCAST_UNEXPORTED" />
diff --git a/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/DummyActivity.java b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/DummyActivity.java
new file mode 100644
index 0000000..834d583
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/DummyActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appenumeration.testapp;
+
+import static android.content.Intent.EXTRA_RETURN_RESULT;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.util.SparseArray;
+
+public class DummyActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        finish();
+    }
+}
\ No newline at end of file
diff --git a/tests/appsearch/Android.bp b/tests/tests/appenumeration/lib/Android.bp
similarity index 63%
copy from tests/appsearch/Android.bp
copy to tests/tests/appenumeration/lib/Android.bp
index 1f70dc4..1b53acc 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/tests/appenumeration/lib/Android.bp
@@ -12,21 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-android_test {
-    name: "CtsAppSearchTestCases",
-    defaults: ["cts_defaults"],
-    static_libs: [
-      "androidx.test.ext.junit",
-      "androidx.test.rules",
-      "compatibility-device-util-axt",
-    ],
-    srcs: [
-      "src/**/*.java",
-    ],
-    test_suites: [
-      "cts",
-      "vts",
-      "general-tests",
-    ],
-    platform_apis: true,
+java_library {
+    name: "CtsAppEnumerationTestLib",
+    srcs: ["src/**/*.java"],
 }
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
new file mode 100644
index 0000000..3c80da6
--- /dev/null
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appenumeration.cts;
+
+public class Constants {
+    public static final String PKG_BASE = "android.appenumeration.";
+
+    /** A package that queries for {@link #TARGET_NO_API} package */
+    public static final String QUERIES_PACKAGE = PKG_BASE + "queries.pkg";
+    /** Queries for the unexported authority in {@link #TARGET_FILTERS} provider */
+    public static final String QUERIES_UNEXPORTED_PROVIDER_AUTH =
+            PKG_BASE + "queries.provider.authority.unexported";
+    /** Queries for the unexported action in {@link #TARGET_FILTERS} service filter */
+    public static final String QUERIES_UNEXPORTED_SERVICE_ACTION =
+            PKG_BASE + "queries.service.action.unexported";
+    /** Queries for the unexported action in {@link #TARGET_FILTERS} activity filter */
+    public static final String QUERIES_UNEXPORTED_ACTIVITY_ACTION =
+            PKG_BASE + "queries.activity.action.unexported";
+    /** A package that queries for the authority in {@link #TARGET_FILTERS} provider */
+    public static final String QUERIES_PROVIDER_AUTH = PKG_BASE + "queries.provider.authority";
+    /** A package that queries for the action in {@link #TARGET_FILTERS} service filter */
+    public static final String QUERIES_SERVICE_ACTION = PKG_BASE + "queries.service.action";
+    /** A package that queries for the action in {@link #TARGET_FILTERS} activity filter */
+    public static final String QUERIES_ACTIVITY_ACTION = PKG_BASE + "queries.activity.action";
+    /** A package that has no queries but gets the QUERY_ALL_PACKAGES permission */
+    public static final String QUERIES_NOTHING_PERM = PKG_BASE + "queries.nothing.haspermission";
+    /** A package that has no queries tag or permissions but targets Q */
+    public static final String QUERIES_NOTHING_Q = PKG_BASE + "queries.nothing.q";
+    /** A package that has no queries tag or permission to query any specific packages */
+    public static final String QUERIES_NOTHING = PKG_BASE + "queries.nothing";
+    /** A package that queries nothing, but is part of a shared user */
+    public static final String QUERIES_NOTHING_SHARED_USER = PKG_BASE + "queries.nothing.shareduid";
+    /** A package that exposes nothing, but is part of a shared user */
+    public static final String TARGET_SHARED_USER = PKG_BASE + "noapi.shareduid";
+    /** A package that exposes itself via various intent filters (activities, services, etc.) */
+    public static final String TARGET_FILTERS = PKG_BASE + "filters";
+    /** A package that declares itself force queryable, making it visible to all other packages */
+    public static final String TARGET_FORCEQUERYABLE = PKG_BASE + "forcequeryable";
+    /** A package with no published API and so isn't queryable by anything but package name */
+    public static final String TARGET_NO_API = PKG_BASE + "noapi";
+
+    public static final String[] ALL_QUERIES_TARGETING_Q_PACKAGES = {
+            QUERIES_NOTHING,
+            QUERIES_NOTHING_PERM,
+            QUERIES_ACTIVITY_ACTION,
+            QUERIES_SERVICE_ACTION,
+            QUERIES_PROVIDER_AUTH,
+            QUERIES_UNEXPORTED_ACTIVITY_ACTION,
+            QUERIES_UNEXPORTED_SERVICE_ACTION,
+            QUERIES_UNEXPORTED_PROVIDER_AUTH,
+            QUERIES_PACKAGE,
+            QUERIES_NOTHING_SHARED_USER
+    };
+
+    public static final String ACTIVITY_CLASS_TEST = PKG_BASE + "cts.query.TestActivity";
+    public static final String ACTIVITY_CLASS_DUMMY_ACTIVITY = PKG_BASE + "testapp.DummyActivity";
+
+    public static final String ACTION_MANIFEST_ACTIVITY = PKG_BASE + "action.ACTIVITY";
+    public static final String ACTION_MANIFEST_SERVICE = PKG_BASE + "action.SERVICE";
+    public static final String ACTION_MANIFEST_PROVIDER = PKG_BASE + "action.PROVIDER";
+    public static final String ACTION_SEND_RESULT = PKG_BASE + "cts.action.SEND_RESULT";
+    public static final String ACTION_GET_PACKAGE_INFO = PKG_BASE + "cts.action.GET_PACKAGE_INFO";
+    public static final String ACTION_START_FOR_RESULT = PKG_BASE + "cts.action.START_FOR_RESULT";
+    public static final String ACTION_START_DIRECTLY = PKG_BASE + "cts.action.START_DIRECTLY";
+    public static final String ACTION_QUERY_ACTIVITIES =
+            PKG_BASE + "cts.action.QUERY_INTENT_ACTIVITIES";
+    public static final String ACTION_QUERY_SERVICES =
+            PKG_BASE + "cts.action.QUERY_INTENT_SERVICES";
+    public static final String ACTION_QUERY_PROVIDERS =
+            PKG_BASE + "cts.action.QUERY_INTENT_PROVIDERS";
+    public static final String ACTION_GET_INSTALLED_PACKAGES =
+            PKG_BASE + "cts.action.GET_INSTALLED_PACKAGES";
+
+    public static final String EXTRA_REMOTE_CALLBACK = "remoteCallback";
+    public static final String EXTRA_ERROR = "error";
+    public static final String EXTRA_FLAGS = "flags";
+}
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index 7654966..9beffff 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -16,6 +16,37 @@
 
 package android.appenumeration.cts;
 
+import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
+import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
+import static android.appenumeration.cts.Constants.ACTION_MANIFEST_ACTIVITY;
+import static android.appenumeration.cts.Constants.ACTION_MANIFEST_PROVIDER;
+import static android.appenumeration.cts.Constants.ACTION_MANIFEST_SERVICE;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_PROVIDERS;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_SERVICES;
+import static android.appenumeration.cts.Constants.ACTION_START_DIRECTLY;
+import static android.appenumeration.cts.Constants.ACTION_START_FOR_RESULT;
+import static android.appenumeration.cts.Constants.ACTIVITY_CLASS_DUMMY_ACTIVITY;
+import static android.appenumeration.cts.Constants.ACTIVITY_CLASS_TEST;
+import static android.appenumeration.cts.Constants.ALL_QUERIES_TARGETING_Q_PACKAGES;
+import static android.appenumeration.cts.Constants.EXTRA_ERROR;
+import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
+import static android.appenumeration.cts.Constants.QUERIES_ACTIVITY_ACTION;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PERM;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_Q;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SHARED_USER;
+import static android.appenumeration.cts.Constants.QUERIES_PACKAGE;
+import static android.appenumeration.cts.Constants.QUERIES_PROVIDER_AUTH;
+import static android.appenumeration.cts.Constants.QUERIES_SERVICE_ACTION;
+import static android.appenumeration.cts.Constants.QUERIES_UNEXPORTED_ACTIVITY_ACTION;
+import static android.appenumeration.cts.Constants.QUERIES_UNEXPORTED_PROVIDER_AUTH;
+import static android.appenumeration.cts.Constants.QUERIES_UNEXPORTED_SERVICE_ACTION;
+import static android.appenumeration.cts.Constants.TARGET_FILTERS;
+import static android.appenumeration.cts.Constants.TARGET_FORCEQUERYABLE;
+import static android.appenumeration.cts.Constants.TARGET_NO_API;
+import static android.appenumeration.cts.Constants.TARGET_SHARED_USER;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 
 import static org.hamcrest.Matchers.greaterThan;
@@ -24,6 +55,7 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
+import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
@@ -42,7 +74,6 @@
 
 import com.android.compatibility.common.util.SystemUtil;
 
-import org.hamcrest.Matchers;
 import org.hamcrest.core.IsNull;
 import org.junit.AfterClass;
 import org.junit.Assert;
@@ -62,57 +93,6 @@
 @RunWith(AndroidJUnit4.class)
 public class AppEnumerationTests {
 
-    private static final String PKG_BASE = "android.appenumeration.";
-
-    /** A package with no published API and so isn't queryable by anything but package name */
-    private static final String TARGET_NO_API = PKG_BASE + "noapi";
-    /** A package that declares itself force queryable, making it visible to all other packages */
-    private static final String TARGET_FORCEQUERYABLE = PKG_BASE + "forcequeryable";
-    /** A package that exposes itself via various intent filters (activities, services, etc.) */
-    private static final String TARGET_FILTERS = PKG_BASE + "filters";
-    /** A package that exposes nothing, but is part of a shared user */
-    private static final String TARGET_SHARED_USER = PKG_BASE + "noapi.shareduid";
-
-    /** A package that queries nothing, but is part of a shared user */
-    private static final String QUERIES_NOTHING_SHARED_USER =
-            PKG_BASE + "queries.nothing.shareduid";
-    /** A package that has no queries tag or permission to query any specific packages */
-    private static final String QUERIES_NOTHING = PKG_BASE + "queries.nothing";
-    /** A package that has no queries tag or permissions but targets Q */
-    private static final String QUERIES_NOTHING_Q = PKG_BASE + "queries.nothing.q";
-    /** A package that has no queries but gets the QUERY_ALL_PACKAGES permission */
-    private static final String QUERIES_NOTHING_PERM = PKG_BASE + "queries.nothing.haspermission";
-    /** A package that queries for the action in {@link #TARGET_FILTERS} activity filter */
-    private static final String QUERIES_ACTIVITY_ACTION = PKG_BASE + "queries.activity.action";
-    /** A package that queries for the action in {@link #TARGET_FILTERS} service filter */
-    private static final String QUERIES_SERVICE_ACTION = PKG_BASE + "queries.service.action";
-    /** A package that queries for the authority in {@link #TARGET_FILTERS} provider */
-    private static final String QUERIES_PROVIDER_AUTH = PKG_BASE + "queries.provider.authority";
-    /** Queries for the unexported action in {@link #TARGET_FILTERS} activity filter */
-    private static final String QUERIES_UNEXPORTED_ACTIVITY_ACTION =
-            PKG_BASE + "queries.activity.action.unexported";
-    /** Queries for the unexported action in {@link #TARGET_FILTERS} service filter */
-    private static final String QUERIES_UNEXPORTED_SERVICE_ACTION =
-            PKG_BASE + "queries.service.action.unexported";
-    /** Queries for the unexported authority in {@link #TARGET_FILTERS} provider */
-    private static final String QUERIES_UNEXPORTED_PROVIDER_AUTH =
-            PKG_BASE + "queries.provider.authority.unexported";
-    /** A package that queries for {@link #TARGET_NO_API} package */
-    private static final String QUERIES_PACKAGE = PKG_BASE + "queries.pkg";
-
-    private static final String[] ALL_QUERIES_TARGETING_Q_PACKAGES = {
-            QUERIES_NOTHING,
-            QUERIES_NOTHING_PERM,
-            QUERIES_ACTIVITY_ACTION,
-            QUERIES_SERVICE_ACTION,
-            QUERIES_PROVIDER_AUTH,
-            QUERIES_UNEXPORTED_ACTIVITY_ACTION,
-            QUERIES_UNEXPORTED_SERVICE_ACTION,
-            QUERIES_UNEXPORTED_PROVIDER_AUTH,
-            QUERIES_PACKAGE,
-            QUERIES_NOTHING_SHARED_USER
-    };
-
     private static Handler sResponseHandler;
     private static HandlerThread sResponseThread;
 
@@ -177,6 +157,29 @@
     }
 
     @Test
+    public void startExplicitly_cannotStartNonVisible() throws Exception {
+        assertNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
+        try {
+            startExplicitIntent(QUERIES_NOTHING, TARGET_FILTERS);
+            fail("Package cannot start a package it cannot see");
+        } catch (ActivityNotFoundException e) {
+            // hooray!
+        }
+    }
+
+    @Test
+    public void startExplicitly_canStartVisible() throws Exception {
+        assertVisible(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS);
+        startExplicitIntent(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS);
+    }
+
+    @Test
+    public void startImplicitly_canStartNonVisible() throws Exception {
+        assertNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
+        startImplicitIntent(QUERIES_NOTHING);
+    }
+
+    @Test
     public void queriesNothing_cannotSeeNonForceQueryable() throws Exception {
         assertNotVisible(QUERIES_NOTHING, TARGET_NO_API);
         assertNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
@@ -206,31 +209,31 @@
     @Test
     public void queriesNothing_cannotSeeFilters() throws Exception {
         assertNotQueryable(QUERIES_NOTHING, TARGET_FILTERS,
-                "android.appenumeration.action.ACTIVITY", this::queryIntentActivities);
+                ACTION_MANIFEST_ACTIVITY, this::queryIntentActivities);
         assertNotQueryable(QUERIES_NOTHING, TARGET_FILTERS,
-                "android.appenumeration.action.SERVICE", this::queryIntentServices);
+                ACTION_MANIFEST_SERVICE, this::queryIntentServices);
         assertNotQueryable(QUERIES_NOTHING, TARGET_FILTERS,
-                "android.appenumeration.action.PROVIDER", this::queryIntentProviders);
+                ACTION_MANIFEST_PROVIDER, this::queryIntentProviders);
     }
 
     @Test
     public void queriesActivityAction_canSeeFilters() throws Exception {
         assertQueryable(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS,
-                "android.appenumeration.action.ACTIVITY", this::queryIntentActivities);
+                ACTION_MANIFEST_ACTIVITY, this::queryIntentActivities);
         assertQueryable(QUERIES_SERVICE_ACTION, TARGET_FILTERS,
-                "android.appenumeration.action.SERVICE", this::queryIntentServices);
+                ACTION_MANIFEST_SERVICE, this::queryIntentServices);
         assertQueryable(QUERIES_PROVIDER_AUTH, TARGET_FILTERS,
-                "android.appenumeration.action.PROVIDER", this::queryIntentProviders);
+                ACTION_MANIFEST_PROVIDER, this::queryIntentProviders);
     }
 
     @Test
     public void queriesNothingHasPermission_canSeeFilters() throws Exception {
         assertQueryable(QUERIES_NOTHING_PERM, TARGET_FILTERS,
-                "android.appenumeration.action.ACTIVITY", this::queryIntentActivities);
+                ACTION_MANIFEST_ACTIVITY, this::queryIntentActivities);
         assertQueryable(QUERIES_NOTHING_PERM, TARGET_FILTERS,
-                "android.appenumeration.action.SERVICE", this::queryIntentServices);
+                ACTION_MANIFEST_SERVICE, this::queryIntentServices);
         assertQueryable(QUERIES_NOTHING_PERM, TARGET_FILTERS,
-                "android.appenumeration.action.PROVIDER", this::queryIntentProviders);
+                ACTION_MANIFEST_PROVIDER, this::queryIntentProviders);
     }
 
     @Test
@@ -367,50 +370,58 @@
     private PackageInfo getPackageInfo(String sourcePackageName, String targetPackageName)
             throws Exception {
         Bundle response = sendCommand(sourcePackageName, targetPackageName,
-                null /*queryIntent*/, PKG_BASE + "cts.action.GET_PACKAGE_INFO");
+                null /*queryIntent*/, ACTION_GET_PACKAGE_INFO);
         return response.getParcelable(Intent.EXTRA_RETURN_RESULT);
     }
 
     private PackageInfo startForResult(String sourcePackageName, String targetPackageName)
             throws Exception {
         Bundle response = sendCommand(sourcePackageName, targetPackageName,
-                null /*queryIntent*/, PKG_BASE + "cts.action.START_FOR_RESULT");
+                null /*queryIntent*/, ACTION_START_FOR_RESULT);
         return response.getParcelable(Intent.EXTRA_RETURN_RESULT);
     }
 
     private String[] queryIntentActivities(String sourcePackageName, Intent queryIntent)
             throws Exception {
-        Bundle response = sendCommand(sourcePackageName, null, queryIntent, PKG_BASE +
-                        "cts.action.QUERY_INTENT_ACTIVITIES");
+        Bundle response = sendCommand(sourcePackageName, null, queryIntent, ACTION_QUERY_ACTIVITIES);
         return response.getStringArray(Intent.EXTRA_RETURN_RESULT);
     }
 
     private String[] queryIntentServices(String sourcePackageName, Intent queryIntent)
             throws Exception {
-        Bundle response = sendCommand(sourcePackageName, null, queryIntent, PKG_BASE +
-                "cts.action.QUERY_INTENT_SERVICES");
+        Bundle response = sendCommand(sourcePackageName, null, queryIntent, ACTION_QUERY_SERVICES);
         return response.getStringArray(Intent.EXTRA_RETURN_RESULT);
     }
 
     private String[] queryIntentProviders(String sourcePackageName, Intent queryIntent)
             throws Exception {
-        Bundle response = sendCommand(sourcePackageName, null, queryIntent, PKG_BASE +
-                "cts.action.QUERY_INTENT_PROVIDERS");
+        Bundle response = sendCommand(sourcePackageName, null, queryIntent, ACTION_QUERY_PROVIDERS);
         return response.getStringArray(Intent.EXTRA_RETURN_RESULT);
     }
 
     private String[] getInstalledPackages(String sourcePackageNames, int flags) throws Exception {
-        Bundle response = sendCommand(sourcePackageNames, null, new Intent().putExtra("flags", flags),
-                "android.appenumeration.cts.action.GET_INSTALLED_PACKAGES");
+        Bundle response = sendCommand(sourcePackageNames, null, new Intent().putExtra(EXTRA_FLAGS,
+                flags), ACTION_GET_INSTALLED_PACKAGES);
         return response.getStringArray(Intent.EXTRA_RETURN_RESULT);
     }
 
+    private void startExplicitIntent(String sourcePackage, String targetPackage) throws Exception {
+        sendCommand(sourcePackage, targetPackage,
+                new Intent().setComponent(new ComponentName(targetPackage,
+                        ACTIVITY_CLASS_DUMMY_ACTIVITY)),
+                ACTION_START_DIRECTLY);
+    }
+
+    private void startImplicitIntent(String sourcePackage) throws Exception {
+        sendCommand(sourcePackage, TARGET_FILTERS, new Intent(ACTION_MANIFEST_ACTIVITY),
+                ACTION_START_DIRECTLY);
+    }
+
     private Bundle sendCommand(String sourcePackageName, @Nullable String targetPackageName,
             @Nullable Intent queryIntent, String action)
             throws Exception {
         final Intent intent = new Intent(action)
-                .setComponent(new ComponentName(
-                        sourcePackageName, PKG_BASE + "cts.query.TestActivity"))
+                .setComponent(new ComponentName(sourcePackageName, ACTIVITY_CLASS_TEST))
                 // data uri unique to each activity start to ensure actual launch and not just
                 // redisplay
                 .setData(Uri.parse("test://" + name.getMethodName()
@@ -432,15 +443,15 @@
                     latch.open();
                 },
                 sResponseHandler);
-        intent.putExtra("remoteCallback", callback);
+        intent.putExtra(EXTRA_REMOTE_CALLBACK, callback);
         InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
         if (!latch.block(TimeUnit.SECONDS.toMillis(10))) {
             throw new TimeoutException(
                     "Latch timed out while awiating a response from " + sourcePackageName);
         }
         final Bundle bundle = resultReference.get();
-        if (bundle != null && bundle.containsKey("error")) {
-            throw (Exception) Objects.requireNonNull(bundle.getSerializable("error"));
+        if (bundle != null && bundle.containsKey(EXTRA_ERROR)) {
+            throw (Exception) Objects.requireNonNull(bundle.getSerializable(EXTRA_ERROR));
         }
         return bundle;
     }
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
index 770f20d..ca6d98b 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
@@ -15,6 +15,7 @@
  */
 package android.car.cts;
 
+import static org.testng.Assert.assertThrows;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -251,9 +252,11 @@
         for (CarPropertyConfig cfg : configs) {
             if (cfg.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE
                     && cfg.getPropertyType() == Boolean.class) {
-                // Get the current value, and set a different value to the property and verify it.
+                // In R, there is no property which is writable for third-party apps.
                 for (int areaId : getAreaIdsHelper(cfg)) {
-                    assertTrue(setBooleanPropertyHelper(cfg.getPropertyId(), areaId));
+                    assertThrows(SecurityException.class,
+                            () -> mCarPropertyManager.setBooleanProperty(
+                                    cfg.getPropertyId(), areaId,true));
                 }
             }
         }
@@ -342,31 +345,6 @@
         assertEquals(currentEventUI, speedListenerUI.receivedEvent(vehicleSpeed));
     }
 
-    /**
-     * Returns true if set boolean value successfully.
-     * @param propId
-     * @param areaId
-     * @return
-     */
-    private boolean setBooleanPropertyHelper(int propId, int areaId) {
-        boolean currentValue = mCarPropertyManager.getBooleanProperty(propId, areaId);
-        boolean expectedValue = !currentValue;
-        try {
-            mCarPropertyManager.setBooleanProperty(propId, areaId, expectedValue);
-            Thread.sleep(WAIT_CALLBACK);
-            currentValue = mCarPropertyManager.getBooleanProperty(propId, areaId);
-            return expectedValue == currentValue;
-        } catch (Exception e) {
-            Log.e(TAG, new StringBuilder()
-                        .append("Failed to verify Property Id: 0x")
-                        .append(toHexString(propId))
-                        .append(", in areaId: 0x")
-                        .append(toHexString(areaId))
-                        .toString());
-        }
-        return false;
-    }
-
     private int[] getAreaIdsHelper(CarPropertyConfig config) {
         if (config.isGlobalProperty()) {
             int[] areaIds = {0};
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverTest.java b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
index f635625..821b69a 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
@@ -16,6 +16,9 @@
 
 package android.content.cts;
 
+import static android.content.ContentResolver.NOTIFY_INSERT;
+import static android.content.ContentResolver.NOTIFY_UPDATE;
+
 import android.accounts.Account;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
@@ -50,11 +53,17 @@
 import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 public class ContentResolverTest extends AndroidTestCase {
+    private static final String TAG = "ContentResolverTest";
+
     private final static String COLUMN_ID_NAME = "_id";
     private final static String COLUMN_KEY_NAME = "key";
     private final static String COLUMN_VALUE_NAME = "value";
@@ -1245,7 +1254,12 @@
         mContentResolver.unregisterContentObserver(mco);
     }
 
-    public void testNotifyChange_Multiple() {
+    /**
+     * Verify that callers using the {@link Iterable} version of
+     * {@link ContentResolver#notifyChange} are correctly split and delivered to
+     * disjoint listeners.
+     */
+    public void testNotifyChange_MultipleSplit() {
         final MockContentObserver observer1 = new MockContentObserver();
         final MockContentObserver observer2 = new MockContentObserver();
 
@@ -1271,6 +1285,46 @@
         mContentResolver.unregisterContentObserver(observer2);
     }
 
+    /**
+     * Verify that callers using the {@link Iterable} version of
+     * {@link ContentResolver#notifyChange} are correctly grouped and delivered
+     * to overlapping listeners, including untouched flags.
+     */
+    public void testNotifyChange_MultipleFlags() {
+        final MockContentObserver observer1 = new MockContentObserver();
+        final MockContentObserver observer2 = new MockContentObserver();
+
+        mContentResolver.registerContentObserver(LEVEL1_URI, false, observer1);
+        mContentResolver.registerContentObserver(LEVEL2_URI, false, observer2);
+
+        mContentResolver.notifyChange(
+                Arrays.asList(LEVEL1_URI), null, 0);
+        mContentResolver.notifyChange(
+                Arrays.asList(LEVEL1_URI, LEVEL2_URI), null, NOTIFY_INSERT);
+        mContentResolver.notifyChange(
+                Arrays.asList(LEVEL2_URI), null, NOTIFY_UPDATE);
+
+        final List<Change> expected1 = Arrays.asList(
+                new Change(false, Arrays.asList(LEVEL1_URI), 0),
+                new Change(false, Arrays.asList(LEVEL1_URI), NOTIFY_INSERT));
+
+        final List<Change> expected2 = Arrays.asList(
+                new Change(false, Arrays.asList(LEVEL1_URI), 0),
+                new Change(false, Arrays.asList(LEVEL1_URI, LEVEL2_URI), NOTIFY_INSERT),
+                new Change(false, Arrays.asList(LEVEL2_URI), NOTIFY_UPDATE));
+
+        new PollingCheck() {
+            @Override
+            protected boolean check() {
+                return observer1.hadChanges(expected1)
+                        && observer2.hadChanges(expected2);
+            }
+        }.run();
+
+        mContentResolver.unregisterContentObserver(observer1);
+        mContentResolver.unregisterContentObserver(observer2);
+    }
+
     public void testStartCancelSync() {
         Bundle extras = new Bundle();
 
@@ -1411,8 +1465,45 @@
         assertNull(response);
     }
 
-    private class MockContentObserver extends ContentObserver {
+    public static class Change {
+        public final boolean selfChange;
+        public final Iterable<Uri> uris;
+        public final int flags;
+
+        public Change(boolean selfChange, Iterable<Uri> uris, int flags) {
+            this.selfChange = selfChange;
+            this.uris = uris;
+            this.flags = flags;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("onChange(%b, %s, %d)",
+                    selfChange, asSet(uris).toString(), flags);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof Change) {
+                final Change change = (Change) other;
+                return change.selfChange == selfChange &&
+                        Objects.equals(asSet(change.uris), asSet(uris)) &&
+                        change.flags == flags;
+            } else {
+                return false;
+            }
+        }
+
+        private static Set<Uri> asSet(Iterable<Uri> uris) {
+            final Set<Uri> asSet = new HashSet<>();
+            uris.forEach(asSet::add);
+            return asSet;
+        }
+    }
+
+    private static class MockContentObserver extends ContentObserver {
         private boolean mHadOnChanged = false;
+        private List<Change> mChanges = new ArrayList<>();
 
         public MockContentObserver() {
             super(null);
@@ -1424,9 +1515,12 @@
         }
 
         @Override
-        public synchronized void onChange(boolean selfChange) {
-            super.onChange(selfChange);
+        public synchronized void onChange(boolean selfChange, Iterable<Uri> uris, int flags) {
+            final Change change = new Change(selfChange, uris, flags);
+            Log.v(TAG, change.toString());
+
             mHadOnChanged = true;
+            mChanges.add(change);
         }
 
         public synchronized boolean hadOnChanged() {
@@ -1436,5 +1530,9 @@
         public synchronized void reset() {
             mHadOnChanged = false;
         }
+
+        public synchronized boolean hadChanges(Collection<Change> changes) {
+            return mChanges.containsAll(changes);
+        }
     }
 }
diff --git a/tests/tests/database/src/android/database/cts/ContentObserverTest.java b/tests/tests/database/src/android/database/cts/ContentObserverTest.java
index 05fb60c..d470a62 100644
--- a/tests/tests/database/src/android/database/cts/ContentObserverTest.java
+++ b/tests/tests/database/src/android/database/cts/ContentObserverTest.java
@@ -16,7 +16,6 @@
 
 package android.database.cts;
 
-
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
@@ -24,6 +23,8 @@
 import android.os.Looper;
 import android.test.InstrumentationTestCase;
 
+import java.util.Arrays;
+
 public class ContentObserverTest extends InstrumentationTestCase {
     private static final Uri CONTENT_URI = Uri.parse("content://uri");
 
@@ -120,9 +121,37 @@
         assertFalse(contentObserver.deliverSelfNotifications());
     }
 
+    /**
+     * Verify that all incoming dispatch methods invoke all outgoing callbacks.
+     */
+    public void testDispatchChange_Completeness() {
+        final MyContentObserver observer = new MyContentObserver(null);
+
+        observer.resetStatus();
+        observer.dispatchChange(false, Arrays.asList(CONTENT_URI), 0);
+        assertEquals(4, observer.getChangeCount());
+
+        observer.resetStatus();
+        observer.dispatchChange(false, Arrays.asList(CONTENT_URI, CONTENT_URI), 0);
+        assertEquals(7, observer.getChangeCount());
+
+        observer.resetStatus();
+        observer.dispatchChange(false, CONTENT_URI, 0);
+        assertEquals(4, observer.getChangeCount());
+
+        observer.resetStatus();
+        observer.dispatchChange(false, CONTENT_URI);
+        assertEquals(4, observer.getChangeCount());
+
+        observer.resetStatus();
+        observer.dispatchChange(false);
+        assertEquals(4, observer.getChangeCount());
+    }
+
     private static class MyContentObserver extends ContentObserver {
         private boolean mHasChanged;
         private boolean mSelfChange;
+        private int mChangeCount = 0;
 
         public MyContentObserver(Handler handler) {
             super(handler);
@@ -134,10 +163,35 @@
             synchronized(this) {
                 mHasChanged = true;
                 mSelfChange = selfChange;
+                mChangeCount++;
                 notifyAll();
             }
         }
 
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange, uri);
+            synchronized (this) {
+                mChangeCount++;
+            }
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri, int flags) {
+            super.onChange(selfChange, uri, flags);
+            synchronized (this) {
+                mChangeCount++;
+            }
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Iterable<Uri> uris, int flags) {
+            super.onChange(selfChange, uris, flags);
+            synchronized (this) {
+                mChangeCount++;
+            }
+        }
+
         protected synchronized boolean hasChanged(long timeout) throws InterruptedException {
             if (!mHasChanged) {
                 wait(timeout);
@@ -152,6 +206,7 @@
         protected void resetStatus() {
             mHasChanged = false;
             mSelfChange = false;
+            mChangeCount = 0;
         }
 
         protected boolean getSelfChangeState() {
@@ -161,6 +216,10 @@
         protected void setSelfChangeState(boolean state) {
             mSelfChange = state;
         }
+
+        protected int getChangeCount() {
+            return mChangeCount;
+        }
     }
 
     private static class MyContentObserverWithUri extends ContentObserver {
diff --git a/tests/tests/graphics/jni/android_graphics_cts_FrameRateCtsActivity.cpp b/tests/tests/graphics/jni/android_graphics_cts_FrameRateCtsActivity.cpp
index 2b4bef6..e33f7f6 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_FrameRateCtsActivity.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_FrameRateCtsActivity.cpp
@@ -219,8 +219,12 @@
     Surface* surface = reinterpret_cast<Surface*>(surfaceControlLong);
     ASurfaceControl* surfaceControl = surface->getSurfaceControl();
     // Android's Color.* values are represented as ARGB. Convert to RGBA.
-    int rgbaColor = ((argbColor >> 16) & 0xff) | ((argbColor >> 8) & 0xff) |
-            ((argbColor >> 0) & 0xff) | ((argbColor >> 24) & 0xff);
+    int32_t rgbaColor = 0;
+    int8_t* rgbaColorBytes = reinterpret_cast<int8_t*>(&rgbaColor);
+    rgbaColorBytes[0] = (argbColor >> 16) & 0xff;
+    rgbaColorBytes[1] = (argbColor >> 8) & 0xff;
+    rgbaColorBytes[2] = (argbColor >> 0) & 0xff;
+    rgbaColorBytes[3] = (argbColor >> 24) & 0xff;
 
     Buffer buffer(surface->getWidth(), surface->getHeight(), rgbaColor);
     if (!buffer.isValid()) {
diff --git a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
index 2cef070..5cf438b 100644
--- a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
+++ b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
@@ -251,6 +251,12 @@
                 mSurface = null;
             }
             if (mSurfaceControl != null) {
+                SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+                try {
+                    transaction.reparent(mSurfaceControl, null).apply();
+                } finally {
+                    transaction.close();
+                }
                 mSurfaceControl.release();
                 mSurfaceControl = null;
             }
diff --git a/tests/tests/hardware/res/raw/nintendo_switchpro_keyeventtests.json b/tests/tests/hardware/res/raw/nintendo_switchpro_keyeventtests.json
new file mode 100644
index 0000000..615bba3
--- /dev/null
+++ b/tests/tests/hardware/res/raw/nintendo_switchpro_keyeventtests.json
@@ -0,0 +1,236 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x04, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x08, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x01, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x02, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_L1",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x40, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_R1",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x40, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_L2",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x80, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L2"},
+      {"action": "UP", "keycode": "BUTTON_L2"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_R2",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x80, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R2"},
+      {"action": "UP", "keycode": "BUTTON_R2"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_THUMBL",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x08, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_THUMBR",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x04, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_SELECT (minus)",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x01, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_MODE (circle)",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x20, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_MODE"},
+      {"action": "UP", "keycode": "BUTTON_MODE"}
+    ]
+  },
+  {
+    "name": "Press BUTTON_START (plus)",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x02, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/nintendo_switchpro_motioneventtests.json b/tests/tests/hardware/res/raw/nintendo_switchpro_motioneventtests.json
new file mode 100644
index 0000000..e701a38
--- /dev/null
+++ b/tests/tests/hardware/res/raw/nintendo_switchpro_motioneventtests.json
@@ -0,0 +1,165 @@
+[
+  {
+    "name": "Sanity check - should not produce any events",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+    ]
+  },
+  {
+    "name": "Left stick - press left",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf1, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -1.0}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xfd, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 1.0}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0xda, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -1.0}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x17, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 1.0}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf1, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -1.0}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xfd, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": 1.0}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0xdb, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1.0}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x16, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00],
+      [0x30, 0xe2, 0x40, 0x00, 0x00, 0x00, 0xcf, 0xf7, 0x7c, 0xcf, 0xf7, 0x7c, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1.0}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/nintendo_switchpro_register.json b/tests/tests/hardware/res/raw/nintendo_switchpro_register.json
new file mode 100644
index 0000000..d1b1939
--- /dev/null
+++ b/tests/tests/hardware/res/raw/nintendo_switchpro_register.json
@@ -0,0 +1,65 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Nintendo Switch Pro Controller (Test)",
+  "vid": 0x057e,
+  "pid": 0x2009,
+  "bus": "bluetooth",
+  "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x06, 0x01, 0xff, 0x85, 0x21, 0x09, 0x21,
+    0x75, 0x08, 0x95, 0x30, 0x81, 0x02, 0x85, 0x30, 0x09, 0x30, 0x75, 0x08, 0x95, 0x30, 0x81,
+    0x02, 0x85, 0x31, 0x09, 0x31, 0x75, 0x08, 0x96, 0x69, 0x01, 0x81, 0x02, 0x85, 0x32, 0x09,
+    0x32, 0x75, 0x08, 0x96, 0x69, 0x01, 0x81, 0x02, 0x85, 0x33, 0x09, 0x33, 0x75, 0x08, 0x96,
+    0x69, 0x01, 0x81, 0x02, 0x85, 0x3f, 0x05, 0x09, 0x19, 0x01, 0x29, 0x10, 0x15, 0x00, 0x25,
+    0x01, 0x75, 0x01, 0x95, 0x10, 0x81, 0x02, 0x05, 0x01, 0x09, 0x39, 0x15, 0x00, 0x25, 0x07,
+    0x75, 0x04, 0x95, 0x01, 0x81, 0x42, 0x05, 0x09, 0x75, 0x04, 0x95, 0x01, 0x81, 0x01, 0x05,
+    0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x33, 0x09, 0x34, 0x16, 0x00, 0x00, 0x27, 0xff, 0xff,
+    0x00, 0x00, 0x75, 0x10, 0x95, 0x04, 0x81, 0x02, 0x06, 0x01, 0xff, 0x85, 0x01, 0x09, 0x01,
+    0x75, 0x08, 0x95, 0x30, 0x91, 0x02, 0x85, 0x10, 0x09, 0x10, 0x75, 0x08, 0x95, 0x30, 0x91,
+    0x02, 0x85, 0x11, 0x09, 0x11, 0x75, 0x08, 0x95, 0x30, 0x91, 0x02, 0x85, 0x12, 0x09, 0x12,
+    0x75, 0x08, 0x95, 0x30, 0x91, 0x02, 0xc0],
+  "outputs": [
+    {
+      "description": "Ack for 'set report mode' (0x03)",
+      "output": [0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb, 0x1,
+        0x03]
+    },
+    {
+      "description": "Ack for 'enable rumble' (0x48)",
+      "output": [0x1, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x48, 0x1],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb, 0x1,
+        0x48]
+    },
+    {
+      "description": "Some other rumble command?",
+      "output": [0x1, 0x4, 0x0, 0x1, 0x64, 0x64, 0x0, 0x1, 0x64, 0x64, 0x48, 0x3],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb, 0x1,
+        0x48]
+    },
+    {
+      "description": "Some other rumble command? -- 2",
+      "output": [0x1, 0x4, 0x0, 0x1, 0x64, 0x64, 0x0, 0x1, 0x64, 0x64, 0x48, 0x7],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb, 0x1,
+        0x48]
+    },
+    {
+      "description": "Info about MAC address (0x2)",
+      "output": [0x1, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb, 0x1,
+        0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]
+    },
+    {
+      "description": "Ack for 'set player led' (0x30)",
+      "output": [0x1, 0x4, 0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40, 0x30, 0xf],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb, 0x1,
+        0x30]
+    },
+    {
+      "description": "Ack for 'set home led' (0x38)",
+      "output": [0x1, 0x5, 0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40, 0x38, 0x1, 0x0, 0x0, 0x11,
+        0x11],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb,
+        0x1, 0x38]
+    }
+  ]
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
new file mode 100644
index 0000000..c826b59
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+import android.os.SystemClock;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class NintendoSwitchProTest extends InputTestCase {
+    public NintendoSwitchProTest() {
+        super(R.raw.nintendo_switchpro_register);
+    }
+
+    @Before
+    public void setUp() {
+        super.setUp();
+        /**
+         * During probe, hid-nintendo sends commands to the joystick and waits for some of those
+         * commands to execute. Somewhere in the middle of the commands, the driver will register
+         * an input device, which is the notification received by InputTestCase.
+         * If a command is still being waited on while we start writing
+         * events to uhid, all incoming events are dropped, because probe() still hasn't finished.
+         * To ensure that hid-nintendo probe is done, add a delay here.
+         */
+        SystemClock.sleep(1000);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.nintendo_switchpro_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.nintendo_switchpro_motioneventtests);
+    }
+}
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserInputReader.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserInputReader.java
index d7fe90d..c906020 100644
--- a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserInputReader.java
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserInputReader.java
@@ -31,8 +31,7 @@
     }
 
     @Override
-    public int read(byte[] buffer, int offset, int readLength)
-            throws IOException, InterruptedException {
+    public int read(byte[] buffer, int offset, int readLength) throws IOException {
         return mFakeExtractorInput.read(buffer, offset, readLength);
     }
 
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
index c3eafe7..6728c78 100644
--- a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
@@ -33,6 +33,7 @@
 import com.google.android.exoplayer2.video.ColorInfo;
 
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.util.ArrayList;
 
 public class MockMediaParserOutputConsumer implements MediaParser.OutputConsumer {
@@ -81,13 +82,19 @@
 
     @Override
     public void onSampleData(int trackIndex, MediaParser.InputReader inputReader)
-            throws IOException, InterruptedException {
-        mFakeExtractorOutput
-                .track(trackIndex, C.TRACK_TYPE_UNKNOWN)
-                .sampleData(
-                        new ExtractorInputAdapter(inputReader),
-                        (int) inputReader.getLength(),
-                        false);
+            throws IOException {
+        try {
+            mFakeExtractorOutput
+                    .track(trackIndex, C.TRACK_TYPE_UNKNOWN)
+                    .sampleData(
+                            new ExtractorInputAdapter(inputReader),
+                            (int) inputReader.getLength(),
+                            false);
+        } catch (InterruptedException e) {
+            // TODO: Remove this exception replacement once we update the ExoPlayer
+            // version.
+            throw new InterruptedIOException();
+        }
     }
 
     @Override
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
new file mode 100644
index 0000000..994b6c9
--- /dev/null
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts;
+
+import static android.net.wifi.WifiEnterpriseConfig.Eap.AKA;
+
+import android.net.MacAddress;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiNetworkSuggestion;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+
+public class WifiNetworkSuggestionTest extends AndroidTestCase {
+    private static final String TEST_SSID = "testSsid";
+    private static final String TEST_BSSID = "00:df:aa:bc:12:23";
+    private static final String TEST_PASSPHRASE = "testPassword";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            super.tearDown();
+            return;
+        }
+        super.tearDown();
+    }
+
+    private WifiNetworkSuggestion.Builder createBuilderWithCommonParams() {
+        return createBuilderWithCommonParams(false);
+    }
+
+    private WifiNetworkSuggestion.Builder createBuilderWithCommonParams(boolean isPasspoint) {
+        WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder();
+        if (!isPasspoint) {
+            builder.setSsid(TEST_SSID);
+            builder.setBssid(MacAddress.fromString(TEST_BSSID));
+            builder.setIsEnhancedOpen(false);
+            builder.setIsHiddenSsid(true);
+        }
+        builder.setPriority(0);
+        builder.setIsAppInteractionRequired(true);
+        builder.setIsUserInteractionRequired(true);
+        builder.setIsMetered(true);
+        builder.setCarrierId(TelephonyManager.UNKNOWN_CARRIER_ID);
+        builder.setCredentialSharedWithUser(true);
+        builder.setIsInitialAutojoinEnabled(true);
+        builder.setUntrusted(false);
+        return builder;
+    }
+
+    private void validateCommonParams(WifiNetworkSuggestion suggestion) {
+        validateCommonParams(suggestion, false);
+    }
+
+    private void validateCommonParams(WifiNetworkSuggestion suggestion, boolean isPasspoint) {
+        assertNotNull(suggestion);
+        assertNotNull(suggestion.getWifiConfiguration());
+        if (!isPasspoint) {
+            assertEquals(TEST_SSID, suggestion.getSsid());
+            assertEquals(TEST_BSSID, suggestion.getBssid().toString());
+            assertFalse(suggestion.isEnhancedOpen());
+            assertTrue(suggestion.isHiddenSsid());
+        }
+        assertEquals(0, suggestion.getPriority());
+        assertTrue(suggestion.isAppInteractionRequired());
+        assertTrue(suggestion.isUserInteractionRequired());
+        assertTrue(suggestion.isMetered());
+        assertTrue(suggestion.isCredentialSharedWithUser());
+        assertTrue(suggestion.isInitialAutojoinEnabled());
+        assertFalse(suggestion.isUntrusted());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithWpa2Passphrase() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                .setWpa2Passphrase(TEST_PASSPHRASE)
+                .build();
+        validateCommonParams(suggestion);
+        assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithWpa3Passphrase() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa3Passphrase(TEST_PASSPHRASE)
+                        .build();
+        validateCommonParams(suggestion);
+        assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithWapiPassphrase() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWapiPassphrase(TEST_PASSPHRASE)
+                        .build();
+        validateCommonParams(suggestion);
+        assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    private static WifiEnterpriseConfig createEnterpriseConfig() {
+        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+        config.setEapMethod(AKA);
+        return config;
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithWpa2Enterprise() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = createEnterpriseConfig();
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa2EnterpriseConfig(enterpriseConfig)
+                        .build();
+        validateCommonParams(suggestion);
+        assertNull(suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertEquals(enterpriseConfig.getEapMethod(),
+                suggestion.getEnterpriseConfig().getEapMethod());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithWpa3Enterprise() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = createEnterpriseConfig();
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa3EnterpriseConfig(enterpriseConfig)
+                        .build();
+        validateCommonParams(suggestion);
+        assertNull(suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertEquals(enterpriseConfig.getEapMethod(),
+                suggestion.getEnterpriseConfig().getEapMethod());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Helper function for creating a {@link PasspointConfiguration} for testing.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private static PasspointConfiguration createPasspointConfig() {
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("fqdn");
+        homeSp.setFriendlyName("friendly name");
+        homeSp.setRoamingConsortiumOis(new long[] {0x55, 0x66});
+        Credential cred = new Credential();
+        cred.setRealm("realm");
+        cred.setUserCredential(null);
+        cred.setCertCredential(null);
+        cred.setSimCredential(new Credential.SimCredential());
+        cred.getSimCredential().setImsi("1234*");
+        cred.getSimCredential().setEapType(23); // EAP-AKA
+        cred.setCaCertificate(null);
+        cred.setClientCertificateChain(null);
+        cred.setClientPrivateKey(null);
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.setHomeSp(homeSp);
+        config.setCredential(cred);
+        return config;
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithPasspointConfig() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        PasspointConfiguration passpointConfig = createPasspointConfig();
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams(true)
+                        .setPasspointConfig(passpointConfig)
+                        .build();
+        validateCommonParams(suggestion, true);
+        assertNull(suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertEquals(passpointConfig, suggestion.getPasspointConfig());
+    }
+}
diff --git a/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java b/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
index 57f2e9b..eaf161e 100644
--- a/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
@@ -21,7 +21,10 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import androidx.test.InstrumentationRegistry;
@@ -39,13 +42,13 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class TelephonyManagerTest {
+    private Context mContext;
     private TelephonyManager mTelephonyManager;
 
     @Before
     public void setUp() throws Exception {
-        mTelephonyManager =
-                (TelephonyManager) InstrumentationRegistry.getContext().getSystemService(
-                        Context.TELEPHONY_SERVICE);
+        mContext = InstrumentationRegistry.getContext();
+        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
     }
 
     @Test
@@ -86,6 +89,23 @@
                     "An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
                             + "receive " + Build.UNKNOWN + " when invoking Build.getSerial",
                     Build.getSerial(), Build.UNKNOWN);
+            // Previous getIccId documentation does not indicate the value returned if the ICC ID is
+            // not available, so to prevent NPEs SubscriptionInfo#getIccId will return an empty
+            // string if the caller does not have permission to access this identifier.
+            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+                SubscriptionManager subscriptionManager =
+                        (SubscriptionManager) mContext.getSystemService(
+                                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+                int subId = subscriptionManager.getDefaultSubscriptionId();
+                if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    SubscriptionInfo subInfo = subscriptionManager.getActiveSubscriptionInfo(
+                            subId);
+                    assertEquals(
+                            "An app targeting pre-Q with the READ_PHONE_STATE permission granted "
+                                    + "must receive an empty string when invoking getIccId",
+                            "", subInfo.getIccId());
+                }
+            }
         } catch (SecurityException e) {
             fail("An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
                     + "receive null (or "
diff --git a/tests/tests/text/res/layout/webview_layout.xml b/tests/tests/text/res/layout/webview_layout.xml
index 7a0ed0d..d266d21 100644
--- a/tests/tests/text/res/layout/webview_layout.xml
+++ b/tests/tests/text/res/layout/webview_layout.xml
@@ -17,9 +17,9 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content">
+    android:layout_height="match_parent">
 
     <WebView android:id="@+id/web_page"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 </LinearLayout>
diff --git a/tests/tests/text/src/android/text/cts/EmojiCtsActivity.java b/tests/tests/text/src/android/text/cts/EmojiCtsActivity.java
index fcbc510..e93a224 100644
--- a/tests/tests/text/src/android/text/cts/EmojiCtsActivity.java
+++ b/tests/tests/text/src/android/text/cts/EmojiCtsActivity.java
@@ -18,6 +18,8 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.webkit.WebView;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
@@ -27,13 +29,13 @@
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        try {
-            super.onCreate(savedInstanceState);
-            setContentView(R.layout.webview_layout);
-            mWebView = (WebView) findViewById(R.id.web_page);
-        } catch (Exception e) {
-            NullWebViewUtils.determineIfWebViewAvailable(this, e);
-        }
+        super.onCreate(savedInstanceState);
+
+        // Only inflate the layout if the device is supposed to have a WebView implementation.
+        if (!NullWebViewUtils.isWebViewAvailable()) return;
+
+        setContentView(R.layout.webview_layout);
+        mWebView = (WebView) findViewById(R.id.web_page);
     }
 
     public WebView getWebView() {
@@ -43,6 +45,10 @@
     @Override
     public void onDestroy() {
         if (mWebView != null) {
+            ViewParent parent =  mWebView.getParent();
+            if (parent instanceof ViewGroup) {
+                ((ViewGroup) parent).removeView(mWebView);
+            }
             mWebView.destroy();
         }
         super.onDestroy();
diff --git a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
index 0877e1b..ab4ce58 100644
--- a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
@@ -319,10 +319,8 @@
 
     AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb);
 
-    ALooper* looper = ALooper_forThread();
     // Give the display system time to push an initial callback.
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    ALooper_pollAll(16, nullptr, nullptr, nullptr);
     verifyRefreshRateCallback(env, cb, 1);
     AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb);
 }
@@ -335,23 +333,18 @@
 
     AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
 
-    ALooper* looper = ALooper_forThread();
-
     // Give the display system time to push an initial callback.
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    ALooper_pollAll(16, nullptr, nullptr, nullptr);
     verifyRefreshRateCallback(env, cb1, 1);
 
     AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb1);
     // Flush out pending callback events for the callback
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    ALooper_pollAll(16, nullptr, nullptr, nullptr);
     resetRefreshRateCallback(cb1);
 
     AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb2);
     // Verify that cb2 is called on registration, but not cb1.
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    ALooper_pollAll(16, nullptr, nullptr, nullptr);
     verifyRefreshRateCallback(env, cb1, 0);
     verifyRefreshRateCallback(env, cb2, 1);
     AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb2);
@@ -366,12 +359,9 @@
     AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
     AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb2);
 
-    ALooper* looper = ALooper_forThread();
-
     // Give the display system time to push an initial refresh rate change.
     // Polling the event will allow both callbacks to be triggered.
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    ALooper_pollAll(16, nullptr, nullptr, nullptr);
     verifyRefreshRateCallback(env, cb1, 1);
     verifyRefreshRateCallback(env, cb2, 1);
 
@@ -388,28 +378,50 @@
     AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
     AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
 
-    ALooper* looper = ALooper_forThread();
-
     // Give the display system time to push an initial callback.
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    ALooper_pollAll(16, nullptr, nullptr, nullptr);
     verifyRefreshRateCallback(env, cb1, 1);
 
     AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb1);
     // Flush out pending callback events for the callback
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    ALooper_pollAll(16, nullptr, nullptr, nullptr);
     resetRefreshRateCallback(cb1);
 
     AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb2);
     // Verify that cb1 is not called again, even thiough it was registered once
     // and unregistered again
     std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
-    ALooper_pollAll(16, nullptr, nullptr, nullptr);
     verifyRefreshRateCallback(env, cb1, 0);
     AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb2);
 }
 
+// This test must be run on the UI thread for fine-grained control of looper
+// scheduling.
+static void android_view_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks(
+        JNIEnv* env, jclass, jlong choreographerPtr) {
+    AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
+    RefreshRateCallback* cb = new RefreshRateCallback("cb");
+
+    AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb);
+
+    Callback* cb1 = new Callback("cb1");
+    Callback* cb64 = new Callback("cb64");
+    auto start = now();
+
+    auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
+    AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, cb1, delay);
+    AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, cb64, delay);
+
+    std::this_thread::sleep_for(DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 10);
+    ALooper_pollAll(16, nullptr, nullptr, nullptr);
+    verifyRefreshRateCallback(env, cb, 1);
+    verifyCallback(env, cb64, 1, start, DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 11);
+    const auto delayToTestFor32Bit =
+            sizeof(long) == sizeof(int64_t) ? DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 11 : ZERO;
+    verifyCallback(env, cb1, 1, start, delayToTestFor32Bit);
+    AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb);
+}
+
 static JNINativeMethod gMethods[] = {
     {  "nativeGetChoreographer", "()J",
             (void *) android_view_cts_ChoreographerNativeTest_getChoreographer},
@@ -435,6 +447,8 @@
             (void *) android_view_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks},
     {  "nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice", "(J)V",
             (void *) android_view_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice},
+    {  "nativeTestRefreshRateCallbackMixedWithFrameCallbacks", "(J)V",
+            (void *) android_view_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks},
 };
 
 int register_android_view_cts_ChoreographerNativeTest(JNIEnv* env)
diff --git a/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java b/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
index 20a9bda..a8e0134 100644
--- a/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
+++ b/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
@@ -60,6 +60,7 @@
     private static native void nativeTestMultipleRefreshRateCallbacks(long ptr);
     private static native void nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice(
             long ptr);
+    private static native void nativeTestRefreshRateCallbackMixedWithFrameCallbacks(long ptr);
 
     private Context mContext;
     private DisplayManager mDisplayManager;
@@ -151,4 +152,11 @@
         nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice(mChoreographerPtr);
     }
 
+    @UiThreadTest
+    @SmallTest
+    @Test
+    public void testRefreshRateCallbackMixedWithFrameCallbacks() {
+        nativeTestRefreshRateCallbackMixedWithFrameCallbacks(mChoreographerPtr);
+    }
+
 }
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieSyncManagerCtsActivity.java b/tests/tests/webkit/src/android/webkit/cts/CookieSyncManagerCtsActivity.java
index 7661013..bc052ec 100644
--- a/tests/tests/webkit/src/android/webkit/cts/CookieSyncManagerCtsActivity.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieSyncManagerCtsActivity.java
@@ -32,23 +32,19 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        try {
-            CookieSyncManager.createInstance(this);
+        // Only do the rest of setup if the device is supposed to have a WebView implementation.
+        if (!NullWebViewUtils.isWebViewAvailable()) return;
 
-            mWebView = new WebView(this);
-            setContentView(mWebView);
-        } catch (Exception e) {
-            NullWebViewUtils.determineIfWebViewAvailable(this, e);
-        }
+        CookieSyncManager.createInstance(this);
+        mWebView = new WebView(this);
+        setContentView(mWebView);
     }
 
     @Override
     protected void onResume() {
         super.onResume();
-        try {
+        if (NullWebViewUtils.isWebViewAvailable()) {
             CookieSyncManager.getInstance().startSync();
-        } catch (Exception e) {
-            // May throw on a device with no webview, OK to ignore at this point.
         }
     }
 
@@ -67,14 +63,12 @@
     @Override
     protected void onStop() {
         super.onStop();
-        try {
+        if (NullWebViewUtils.isWebViewAvailable()) {
             CookieSyncManager.getInstance().stopSync();
-        } catch (Exception e) {
-            // May throw on a device with no webview, OK to ignore at this point.
         }
     }
 
-    public WebView getWebView(){
+    public WebView getWebView() {
         return mWebView;
     }
 }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewCtsActivity.java b/tests/tests/webkit/src/android/webkit/cts/WebViewCtsActivity.java
index 2724580..953d2cb 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewCtsActivity.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewCtsActivity.java
@@ -16,11 +16,7 @@
 
 package android.webkit.cts;
 
-import android.webkit.cts.R;
-
 import android.app.Activity;
-import android.app.ActivityManager;
-import android.os.Build;
 import android.os.Bundle;
 import android.view.ViewGroup;
 import android.view.ViewParent;
@@ -30,28 +26,19 @@
 
 public class WebViewCtsActivity extends Activity {
     private WebView mWebView;
-    private Exception mInflationException;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        try {
-            super.onCreate(savedInstanceState);
-            setContentView(R.layout.webview_layout);
-            mWebView = (WebView) findViewById(R.id.web_page);
-            mInflationException = null;
-        } catch (Exception e) {
-            NullWebViewUtils.determineIfWebViewAvailable(this, e);
-            // If WebView is available, then the exception we just caught should be propagated.
-            if (NullWebViewUtils.isWebViewAvailable()) {
-                mInflationException = e;
-           }
-        }
+        super.onCreate(savedInstanceState);
+
+        // Only inflate the layout if the device is supposed to have a WebView implementation.
+        if (!NullWebViewUtils.isWebViewAvailable()) return;
+
+        setContentView(R.layout.webview_layout);
+        mWebView = (WebView) findViewById(R.id.web_page);
     }
 
     public WebView getWebView() {
-        if (mInflationException != null) {
-            throw new RuntimeException("Exception caught in onCreate", mInflationException);
-        }
         return mWebView;
     }