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;
}