Merge "IME OnBackInvokedCallback migration CTS tests" into tm-dev
diff --git a/apps/CtsVerifier/res/layout/audio_tap2tone_activity.xml b/apps/CtsVerifier/res/layout/audio_tap2tone_activity.xml
index 65994c0..2848130 100644
--- a/apps/CtsVerifier/res/layout/audio_tap2tone_activity.xml
+++ b/apps/CtsVerifier/res/layout/audio_tap2tone_activity.xml
@@ -25,6 +25,57 @@
android:layout_height="wrap_content"
android:orientation="vertical">
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text="Pro Audio:"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text=""
+ android:id="@+id/audio_t2t_pro_audio"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text="Low Latency:"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text=""
+ android:id="@+id/audio_t2t_low_latency"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text="Media Performance Class:"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text=""
+ android:id="@+id/audio_t2t_mpc"/>
+ </LinearLayout>
+
<include layout="@layout/audio_java_native_api_buttons" />
<LinearLayout
@@ -47,9 +98,29 @@
android:id="@+id/tap2tone_stopBtn" />
</LinearLayout>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:textSize="20sp"
+ android:textStyle="bold"
+ android:text="Required Maximum Latency:"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text=""
+ android:textSize="20sp"
+ android:textStyle="bold"
+ android:id="@+id/audio_t2t_required_latency"/>
+ </LinearLayout>
+
<TextView
android:id="@+id/tap2tone_specTxt"
- android:text="@string/audio_tap2tone_spec"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index ec3e787..9314c4d 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -5389,6 +5389,7 @@
<string name="audio_general_clear_results">Clear Results</string>
<string name="audio_general_test_not_run">Test Not Run</string>
+ <string name="audio_general_testnotcompleted">Test not completed.</string>
<!-- Audio Loopback Latency Test -->
<string name="audio_loopback_latency_test">Audio Loopback Latency Test</string>
@@ -5651,16 +5652,18 @@
<string name="audio_tap2tone">Audio Tap To Tone Test</string>
<string name="audio_tap2tone_info">This tests the latency from a screen interaction to a
resulting tone. This time is a combination of touch screen latency and audio latency.\nThis
- test is best conducted in a quiet room with the device laying on a table. Select
- the Audio API to test (\"Native\" has the lowest latency) and press the \"Start\" button.
- Use your fingernail to tap ONCE on the field below to trigger the test tone. DO NOT leave
+ test is best conducted in a quiet room with the device laying on a table.
+ \n\nTo execute test:
+ \n1. Select the Audio API to test (\"Native\" has the lowest latency) and press the \"Start\" button.
+ \n2. Use your fingernail to tap ONCE on the field below to trigger the test tone. DO NOT leave
the finger resting on the test field. A strong "tick" sound from the fingernail striking
- the display is necessary to register the start of the test process. After tapping ONCE,
- wait until the results are displayed in the field above the waveform display before
- tapping for the next test run.\nFive successful tests runs are required to determine
- if the test as a whole succeeds.
+ the display is necessary to register the start of the test process.
+ \n3. After tapping ONCE, wait until the results are displayed in the field above the
+ waveform display before tapping for the next test run.
+ \nFive successful tests runs are required to determine if the test as a whole succeeds.
+ \n4. Press the \"Stop"\ button to conclude the test. The results will be displayed along with the passing/failing measurement and requirement.
+ \n\n(see <a href="https://source.android.com/compatibility/12/android-12-cdd#56_audio_latency">Android CDD § 5.6. Audio Latency</a>)
</string>
- <string name="audio_tap2tone_spec">80ms or less average latency is STRONGLY RECOMMENDED to pass.</string>
<string name="audio_tap2tone_too_few">Not enough edges. Use fingernail.</string>
<string name="audio_tap2tone_too_many">Too many edges. Use fingernail. Ensure there is
no background noise.</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index b4b99db..9f987a2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -40,6 +40,7 @@
import android.widget.Switch;
import android.widget.Toast;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
@@ -55,6 +56,8 @@
// Flag of launch app to fetch the unfolded/folded tests in main view from AndroidManifest.xml.
protected static boolean sInitialLaunch;
+ private String[] mRequestedPermissions;
+
// Enumerates the display modes, including unfolded and folded.
protected enum DisplayMode {
UNFOLDED, FOLDED;
@@ -68,7 +71,7 @@
* Coverts the mode as suffix with brackets for test name.
*
* @return A string containing mode with brackets for folded mode;
- * empty string for unfolded mode.
+ * empty string for unfolded mode.
*/
public String asSuffix() {
if (name().equals(FOLDED.name())) {
@@ -79,7 +82,7 @@
}
@Override
- public void onClick (View v) {
+ public void onClick(View v) {
handleMenuItemSelected(v.getId());
}
@@ -91,10 +94,11 @@
PackageManager pm = getPackageManager();
PackageInfo packageInfo = pm.getPackageInfo(
getApplicationInfo().packageName, PackageManager.GET_PERMISSIONS);
+ mRequestedPermissions = packageInfo.requestedPermissions;
- if (packageInfo.requestedPermissions != null) {
- String[] permissionsToRequest = removeString(packageInfo.requestedPermissions,
- Manifest.permission.ACCESS_BACKGROUND_LOCATION);
+ if (mRequestedPermissions != null) {
+ String[] permissionsToRequest = removeString(mRequestedPermissions,
+ Manifest.permission.ACCESS_BACKGROUND_LOCATION);
permissionsToRequest = Arrays.stream(permissionsToRequest).filter(s -> {
try {
return (pm.getPermissionInfo(s, 0).getProtection() & PROTECTION_DANGEROUS)
@@ -146,10 +150,12 @@
// If we're sending them to settings we don't need to request background location
// since they can just grant in settings.
sendUserToSettings();
- } else {
- requestPermissions(new String[] {Manifest.permission.ACCESS_BACKGROUND_LOCATION},
+ } else if (new ArrayList<>(Arrays.asList(mRequestedPermissions)).contains(
+ Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
+ requestPermissions(new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
CTS_VERIFIER_BACKGROUND_LOCATION_PERMISSION_REQUEST);
}
+ return;
}
if (requestCode == CTS_VERIFIER_BACKGROUND_LOCATION_PERMISSION_REQUEST) {
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
@@ -191,7 +197,7 @@
displayModeSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
- boolean isChecked) {
+ boolean isChecked) {
if (isChecked) {
sCurrentDisplayMode = DisplayMode.FOLDED.toString();
} else {
@@ -210,20 +216,20 @@
private void handleClearItemSelected() {
new AlertDialog.Builder(this)
- .setMessage(R.string.test_results_clear_title)
- .setPositiveButton(R.string.test_results_clear_yes,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- mAdapter.clearTestResults();
- Toast.makeText(
- TestListActivity.this,
- R.string.test_results_cleared,
- Toast.LENGTH_SHORT)
- .show();
- }
- })
- .setNegativeButton(R.string.test_results_clear_cancel, null)
- .show();
+ .setMessage(R.string.test_results_clear_title)
+ .setPositiveButton(R.string.test_results_clear_yes,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ mAdapter.clearTestResults();
+ Toast.makeText(
+ TestListActivity.this,
+ R.string.test_results_cleared,
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ })
+ .setNegativeButton(R.string.test_results_clear_cancel, null)
+ .show();
}
private void handleExportItemSelected() {
@@ -265,7 +271,7 @@
*/
private String getCurrentDisplayMode() {
String mode = getSharedPreferences(DisplayMode.class.getName(), MODE_PRIVATE)
- .getString(DisplayMode.class.getName(), "");
+ .getString(DisplayMode.class.getName(), "");
return mode;
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioTap2ToneActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioTap2ToneActivity.java
index 9ad290c..9275553 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioTap2ToneActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioTap2ToneActivity.java
@@ -16,6 +16,7 @@
package com.android.cts.verifier.audio;
+import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
@@ -30,6 +31,7 @@
import com.android.cts.verifier.CtsVerifierReportLog;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
+import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
import com.android.cts.verifier.audio.audiolib.CircularBufferFloat;
import com.android.cts.verifier.audio.audiolib.StatUtils;
import com.android.cts.verifier.audio.audiolib.TapLatencyAnalyser;
@@ -104,8 +106,17 @@
private TapLatencyAnalyser mTapLatencyAnalyser;
// Stats for latency
- // STRONGLY RECOMMENDED in CDD 5.6
- private static final int MAX_TAP_2_TONE_LATENCY = 80; // ms
+ private double mMaxRequiredLatency;
+
+ // REQUIRED CDD 5.6/H-1-1
+ private static final int MAX_TAP_2_TONE_LATENCY_BASIC = 500; // ms
+ // Requirement for "R" and "S"
+ private static final int MAX_TAP_2_TONE_LATENCY_RS = 100; // ms
+ // Requirement for "T"
+ private static final int MAX_TAP_2_TONE_LATENCY_T = 80; // ms
+ // Requirement for any builds declaring "ProAudio" and "LowLatency"
+ private static final int MAX_TAP_2_TONE_LATENCY_PRO = 80; // ms
+ private static final int MAX_TAP_2_TONE_LATENCY_LOW = 80; // ms
// Test API (back-end) IDs
private static final int NUM_TEST_APIS = 2;
@@ -113,8 +124,8 @@
private static final int TEST_API_JAVA = 1;
private int mActiveTestAPI = TEST_API_NATIVE;
- private int[] mNumMeasurements = new int[NUM_TEST_APIS]; // ms
- private int[] mLatencySumSamples = new int[NUM_TEST_APIS]; // ms
+ private int[] mNumMeasurements = new int[NUM_TEST_APIS];
+ private int[] mLatencySumSamples = new int[NUM_TEST_APIS];
private double[] mLatencyMin = new double[NUM_TEST_APIS]; // ms
private double[] mLatencyMax = new double[NUM_TEST_APIS]; // ms
private double[] mLatencyAve = new double[NUM_TEST_APIS]; // ms
@@ -138,6 +149,48 @@
super.onCreate(savedInstanceState);
// Setup UI
+ String yesString = getResources().getString(R.string.audio_general_yes);
+ String noString = getResources().getString(R.string.audio_general_no);
+
+ boolean claimsProAudio = AudioSystemFlags.claimsProAudio(this);
+ boolean claimsLowLatencyAudio = AudioSystemFlags.claimsLowLatencyAudio(this);
+
+ ((TextView) findViewById(R.id.audio_t2t_pro_audio))
+ .setText(claimsProAudio ? yesString : noString);
+ ((TextView) findViewById(R.id.audio_t2t_low_latency))
+ .setText(claimsLowLatencyAudio ? yesString : noString);
+
+ String mediaPerformanceClassString;
+ if (Build.VERSION.MEDIA_PERFORMANCE_CLASS == Build.VERSION_CODES.TIRAMISU) {
+ mediaPerformanceClassString = "T";
+ } else if (Build.VERSION.MEDIA_PERFORMANCE_CLASS == Build.VERSION_CODES.S) {
+ mediaPerformanceClassString = "S";
+ } else if (Build.VERSION.MEDIA_PERFORMANCE_CLASS == Build.VERSION_CODES.R) {
+ mediaPerformanceClassString = "R";
+ } else {
+ mediaPerformanceClassString = "none";
+ }
+ ((TextView) findViewById(R.id.audio_t2t_mpc)).setText(mediaPerformanceClassString);
+
+ // Note: These tests need to be ordered such that we find the LOWEST allowable latency
+ mMaxRequiredLatency = MAX_TAP_2_TONE_LATENCY_BASIC;
+ if (claimsProAudio) {
+ mMaxRequiredLatency = Math.min(mMaxRequiredLatency, MAX_TAP_2_TONE_LATENCY_PRO);
+ }
+ if (claimsLowLatencyAudio) {
+ mMaxRequiredLatency = Math.min(mMaxRequiredLatency, MAX_TAP_2_TONE_LATENCY_LOW);
+ }
+ if (Build.VERSION.MEDIA_PERFORMANCE_CLASS == Build.VERSION_CODES.TIRAMISU) {
+ mMaxRequiredLatency = Math.min(mMaxRequiredLatency, MAX_TAP_2_TONE_LATENCY_T);
+ }
+ if (Build.VERSION.MEDIA_PERFORMANCE_CLASS == Build.VERSION_CODES.R
+ || Build.VERSION.MEDIA_PERFORMANCE_CLASS == Build.VERSION_CODES.S) {
+ mMaxRequiredLatency = Math.min(mMaxRequiredLatency, MAX_TAP_2_TONE_LATENCY_RS);
+ }
+
+ ((TextView) findViewById(R.id.audio_t2t_required_latency))
+ .setText("" + mMaxRequiredLatency + "ms");
+
mStartBtn = (Button) findViewById(R.id.tap2tone_startBtn);
mStartBtn.setOnClickListener(this);
mStopBtn = (Button) findViewById(R.id.tap2tone_stopBtn);
@@ -237,7 +290,7 @@
private void clearResults() {
resetStats();
- mSpecView.setText(getResources().getString(R.string.audio_tap2tone_spec));
+ mSpecView.setText("");
mResultsView.setText("");
mStatsView.setText("");
}
@@ -248,21 +301,24 @@
}
private void calculateTestPass() {
- // 80ms is currently STRONGLY RECOMMENDED, so pass the test as long as they have run it.
boolean testCompleted = mTestPhase >= NUM_TEST_PHASES;
- boolean pass = mLatencyAve[mActiveTestAPI] != 0
- && mLatencyAve[mActiveTestAPI] <= MAX_TAP_2_TONE_LATENCY;
-
- if (testCompleted) {
- if (pass) {
- mSpecView.setText("Ave: " + mLatencyAve[mActiveTestAPI] + " ms <= "
- + MAX_TAP_2_TONE_LATENCY + " ms -- PASS");
- } else {
- mSpecView.setText("Ave: " + mLatencyAve[mActiveTestAPI] + " ms > "
- + MAX_TAP_2_TONE_LATENCY + " ms -- DOES NOT MEET STRONGLY RECOMMENDED");
- }
+ if (!testCompleted) {
+ mSpecView.setText(getResources().getString(R.string.audio_general_testnotcompleted));
+ getPassButton().setEnabled(false);
+ return;
}
- getPassButton().setEnabled(testCompleted);
+
+ double averageLatency = mLatencyAve[mActiveTestAPI];
+ boolean pass = averageLatency != 0 && averageLatency <= mMaxRequiredLatency;
+
+ if (pass) {
+ mSpecView.setText("Average: " + averageLatency + " ms <= "
+ + mMaxRequiredLatency + " ms -- PASS");
+ } else {
+ mSpecView.setText("Average: " + averageLatency + " ms > "
+ + mMaxRequiredLatency + " ms -- FAIL");
+ }
+ getPassButton().setEnabled(pass);
}
private void recordTestStatus() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
index bcc8ce9..106c7e9 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
@@ -126,11 +126,11 @@
}
private boolean shouldPerformTest(int state) {
- if (state == STATE_VERIFY_SIZE_CALLBACK
- && sSDKLevel < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ if (state == STATE_VERIFY_SIZE_CALLBACK) {
+ // TODO: revert when b/228227212 is fixed (underlying cause of b/204831731)
return false;
- } else if (state == STATE_VERIFY_RESIZE
- && sSDKLevel < android.os.Build.VERSION_CODES.HONEYCOMB) {
+ } else if (state == STATE_VERIFY_RESIZE) {
+ // TODO: revert when b/228227212 is fixed (underlying cause of b/204831731)
return false;
} else if (state == STATE_VERIFY_COLLECTIONS
&& sSDKLevel < android.os.Build.VERSION_CODES.HONEYCOMB) {
@@ -139,8 +139,7 @@
&& sSDKLevel < android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
return false;
}
- // TODO: revert when b/228227212 is fixed (underlying cause of b/204831731)
- return false;
+ return true;
}
@Override
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
index ef10a81..0c91c75 100644
--- a/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
@@ -24,6 +24,6 @@
],
srcs: ["src/**/*.java"],
sdk_version: "test_current",
- target_sdk_version: "current",
+ target_sdk_version: "33",
min_sdk_version: "30",
}
diff --git a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
index 8deeb76..12fbb3c 100644
--- a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
+++ b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
@@ -73,6 +73,7 @@
EXCEPTION_PATTERNS.add(":: 1002"); // used by remote control
EXCEPTION_PATTERNS.add(":: 1020"); // used by remote control
EXCEPTION_PATTERNS.add("0.0.0.0:7275"); // used by supl
+ EXCEPTION_PATTERNS.add("0.0.0.0:68"); // DHCP server for Tethering
// b/150186547 ports
EXCEPTION_PATTERNS.add("192.168.17.10:48881");
EXCEPTION_PATTERNS.add("192.168.17.10:48896");
diff --git a/hostsidetests/car/Android.bp b/hostsidetests/car/Android.bp
index caa8290..3fab74a 100644
--- a/hostsidetests/car/Android.bp
+++ b/hostsidetests/car/Android.bp
@@ -42,6 +42,7 @@
"general-tests",
],
static_libs: [
+ "car-cts-host-util",
"cts-statsd-atom-host-test-utils",
],
data: [
diff --git a/hostsidetests/car/nonrecoverable/Android.bp b/hostsidetests/car/nonrecoverable/Android.bp
new file mode 100644
index 0000000..538e0d2
--- /dev/null
+++ b/hostsidetests/car/nonrecoverable/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "CtsCarHostNonRecoverableTestCases",
+ defaults: ["cts_defaults"],
+ // Only compile source java files in this apk.
+ srcs: [
+ "src/**/*.java",
+ ],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "compatibility-host-util",
+ "truth-prebuilt",
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ static_libs: [
+ "car-cts-host-util",
+ "cts-statsd-atom-host-test-utils",
+ ],
+}
diff --git a/hostsidetests/car/nonrecoverable/AndroidTest.xml b/hostsidetests/car/nonrecoverable/AndroidTest.xml
new file mode 100644
index 0000000..eb03b30
--- /dev/null
+++ b/hostsidetests/car/nonrecoverable/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Car Host Non-Recoverable Tests">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="auto" />
+ <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" />
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsCarHostNonRecoverableTestCases.jar" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/hostsidetests/car/nonrecoverable/TEST_MAPPING b/hostsidetests/car/nonrecoverable/TEST_MAPPING
new file mode 100644
index 0000000..fd34970
--- /dev/null
+++ b/hostsidetests/car/nonrecoverable/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "auto-presubmit": [
+ {
+ "name": "CtsCarHostNonRecoverableTestCases"
+ }
+ ]
+}
diff --git a/hostsidetests/car/src/android/car/cts/CarServiceHelperServiceTest.java b/hostsidetests/car/nonrecoverable/src/android/car/cts/CarServiceHelperServiceTest.java
similarity index 100%
rename from hostsidetests/car/src/android/car/cts/CarServiceHelperServiceTest.java
rename to hostsidetests/car/nonrecoverable/src/android/car/cts/CarServiceHelperServiceTest.java
diff --git a/hostsidetests/car/util/Android.bp b/hostsidetests/car/util/Android.bp
new file mode 100644
index 0000000..ab1fb2e
--- /dev/null
+++ b/hostsidetests/car/util/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library_host {
+ name: "car-cts-host-util",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+ libs: [
+ "tradefed",
+ "compatibility-host-util",
+ ],
+ static_libs: [
+ "cts-statsd-atom-host-test-utils",
+ ],
+}
diff --git a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java b/hostsidetests/car/util/src/android/car/cts/CarHostJUnit4TestCase.java
similarity index 100%
rename from hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
rename to hostsidetests/car/util/src/android/car/cts/CarHostJUnit4TestCase.java
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 418f926..560e10f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -697,6 +697,10 @@
final String SECURE_SETTING_CATEGORY = "secure";
final String GLOBAL_SETTING_CATEGORY = "global";
final File apk = mBuildHelper.getTestFile(TEST_APP_APK);
+
+ // Needed to access dpm.getPolicyExemptApps()
+ allowTestApiAccess(DEVICE_ADMIN_PKG);
+
try {
// Install the test and prepare the test apk.
installAppAsUser(PACKAGE_INSTALLER_APK, mUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index a4b5c71..4ba614b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -422,6 +422,15 @@
public void testPermissionPrompts() throws Exception {
}
+
+ @Override
+ @LargeTest
+ @Test
+ @IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't have UI")
+ public void testPackageInstallUserRestrictions() throws Exception {
+ super.testPackageInstallUserRestrictions();
+ }
+
@Override
@Test
@IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't launch activities")
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0241/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0241/Android.bp
new file mode 100644
index 0000000..ac0cbd4
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0241/Android.bp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "CVE-2020-0241",
+ defaults: [
+ "cts_hostsidetests_securitybulletin_defaults",
+ ],
+ srcs: [
+ "poc.cpp",
+ ],
+ compile_multilib: "32",
+ header_libs: [
+ "libmediadrm_headers",
+ ],
+ shared_libs: [
+ "libutils",
+ "libbinder",
+ "libmedia",
+ "libmediaplayerservice",
+ ],
+ include_dirs: [
+ "frameworks/av/media/libmediaplayerservice/nuplayer/include/",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0241/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0241/poc.cpp
new file mode 100644
index 0000000..3e6a5ec
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0241/poc.cpp
@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../includes/common.h"
+#include <nuplayer/NuPlayerStreamListener.h>
+#include <stdlib.h>
+
+const size_t kBufferSize = 1024;
+
+using namespace android;
+
+bool isTestInProgress = false;
+
+struct sigaction new_action, old_action;
+
+void sigabrt_handler(int signum, siginfo_t *info, void *context) {
+ if (isTestInProgress && info->si_signo == SIGABRT) {
+ (*old_action.sa_sigaction)(signum, info, context);
+ return;
+ }
+ exit(EXIT_FAILURE);
+}
+
+class StreamSource : public IStreamSource {
+public:
+ void setListener(const sp<IStreamListener> &listener
+ __attribute__((unused))) {}
+ void setBuffers(const Vector<sp<IMemory>> &buffers __attribute__((unused))) {}
+ void onBufferAvailable(size_t index __attribute__((unused))) {}
+
+protected:
+ IBinder *onAsBinder() { return (IBinder *)malloc(kBufferSize); }
+};
+
+int main() {
+ sigemptyset(&new_action.sa_mask);
+ new_action.sa_flags = SA_SIGINFO;
+ new_action.sa_sigaction = sigabrt_handler;
+ sigaction(SIGABRT, &new_action, &old_action);
+
+ const sp<StreamSource> source = new StreamSource();
+ FAIL_CHECK(source != nullptr);
+ isTestInProgress = true;
+ sp<NuPlayer::NuPlayerStreamListener> listener =
+ new NuPlayer::NuPlayerStreamListener(source, nullptr);
+ isTestInProgress = false;
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0241.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0241.java
new file mode 100644
index 0000000..237ed83
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0241.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.util.regex.Pattern;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0241 extends SecurityTestCase {
+
+ /**
+ * b/151456667
+ * Vulnerability Behavior : SIGABRT in self
+ * Vulnerable Library : libmediaplayerservice (As per AOSP code)
+ * Vulnerable Function : android::NuPlayer::NuPlayerStreamListener::NuPlayerStreamListener
+ (As per AOSP code)
+ */
+ @AsbSecurityTest(cveBugId = 151456667)
+ @Test
+ public void testPocCVE_2020_0241() throws Exception {
+ pocPusher.only32();
+ String binaryName = "CVE-2020-0241";
+ AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+ testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName))
+ .setBacktraceIncludes(new BacktraceFilterPattern("libmediaplayerservice",
+ "android::NuPlayer::NuPlayerStreamListener::NuPlayerStreamListener"));
+ String signals[] = {CrashUtils.SIGABRT};
+ testConfig.config.setSignals(signals);
+ testConfig.config.setAbortMessageIncludes(
+ AdbUtils.escapeRegexSpecialChars("Pure virtual function called"));
+ AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0315.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0315.java
new file mode 100644
index 0000000..7487d15
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0315.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0315 extends StsExtraBusinessLogicHostTestBase {
+ static final String TEST_PKG = "android.security.cts.CVE_2021_0315";
+ ITestDevice mDevice;
+
+ @After
+ public void tearDown() throws Exception {
+ AdbUtils.runCommandLine("input keyevent KEYCODE_BACK", mDevice);
+ }
+
+ @AsbSecurityTest(cveBugId = 169763814)
+ @Test
+ public void testPocCVE_2021_0315() throws Exception {
+ mDevice = getDevice();
+ uninstallPackage(mDevice, TEST_PKG);
+
+ /* Wake up the screen */
+ AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", mDevice);
+ AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", mDevice);
+ AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", mDevice);
+
+ installPackage("CVE-2021-0315.apk");
+ runDeviceTests(TEST_PKG, TEST_PKG + ".DeviceTest", "testOverlayButtonPresence");
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2022_20115.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2022_20115.java
new file mode 100644
index 0000000..a8256d6
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2022_20115.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2022_20115 extends StsExtraBusinessLogicHostTestBase {
+ private static final String TEST_PKG = "android.security.cts.CVE_2022_20115";
+ private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+ private static final String TEST_APP = "CVE-2022-20115.apk";
+
+ @AsbSecurityTest(cveBugId = 210118427)
+ @Test
+ public void testPocCVE_2022_20115() throws Exception {
+ ITestDevice device = getDevice();
+ uninstallPackage(device, TEST_PKG);
+
+ /* Wake up the screen */
+ AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+ AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+ AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+ installPackage(TEST_APP);
+
+ Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testCellLocation"));
+ }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/Android.bp
new file mode 100644
index 0000000..5fbde22
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CVE-2021-0315",
+ defaults: [
+ "cts_support_defaults",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+ test_suites: [
+ "sts",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.test.core",
+ ],
+ platform_apis: true,
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/AndroidManifest.xml
new file mode 100644
index 0000000..9a2afd5
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="android.security.cts.CVE_2021_0315"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <application android:label="CVE-2021-0315">
+ <service android:name=".PocService" />
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.security.cts.CVE_2021_0315" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/res/values/strings.xml
new file mode 100644
index 0000000..38f57bf
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/res/values/strings.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="accountName">abc@xyz.org</string>
+ <string name="accountType">com.sampleAccType</string>
+ <string name="activityNotFoundMsg">The activity with intent %1$s was not found</string>
+ <string name="canNotDrawOverlaysMsg">The application cannot draw overlays</string>
+ <string name="cmdDumpsysActivity">dumpsys activity %1$s</string>
+ <string name="errorAuthResponse">Got an error in GrantCredentialsPermissionActivity
+ AccountAuthenticatorResponse with errorCode = %1$s and errorMessage = %2$s</string>
+ <string name="exShellCmdDumpsys">Got an exception while running shell cmd dumpsys activity
+ </string>
+ <string name="failMsg">Device is vulnerable to b/169763814 hence any app with
+ "SYSTEM_ALERT_WINDOW can overlay the %1$s screen</string>
+ <string name="overlayButtonText">OverlayButton</string>
+ <string name="overlayUiScreenError">Overlay UI did not appear on the screen</string>
+ <string name="mResumedTrue">mResumed=true</string>
+ <string name="startServiceExMsg">The service with intent %1$s could not be started</string>
+ <string name="vulActivityNotRunningError">The %1$s is not currently running on the device
+ </string>
+ <string name="vulClsName">android.accounts.GrantCredentialsPermissionActivity</string>
+ <string name="vulPkg">android</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/src/android/security/cts/CVE_2021_0315/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/src/android/security/cts/CVE_2021_0315/DeviceTest.java
new file mode 100644
index 0000000..b5c1128
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/src/android/security/cts/CVE_2021_0315/DeviceTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_0315;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.GrantCredentialsPermissionActivity;
+import android.accounts.IAccountAuthenticatorResponse;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Process;
+import android.provider.Settings;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+ Context mContext;
+ String mVulActivity = "";
+
+ private void startOverlayService() {
+ Intent intent = new Intent(mContext, PocService.class);
+ assumeTrue(mContext.getString(R.string.canNotDrawOverlaysMsg),
+ Settings.canDrawOverlays(mContext));
+ try {
+ mContext.startService(intent);
+ } catch (Exception e) {
+ assumeNoException(mContext.getString(R.string.startServiceExMsg, intent), e);
+ }
+ }
+
+ public void startVulnerableActivity() {
+ Intent intent = new Intent();
+ intent.setClassName(mContext.getString(R.string.vulPkg),
+ mContext.getString(R.string.vulClsName));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT,
+ new Account(mContext.getString(R.string.accountName),
+ mContext.getString(R.string.accountType)));
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE,
+ AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE);
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE,
+ new AccountAuthenticatorResponse(new IAccountAuthenticatorResponse.Stub() {
+ @Override
+ public void onResult(Bundle value) {
+ }
+
+ @Override
+ public void onRequestContinued() {
+ }
+
+ @Override
+ public void onError(int errorCode, String errorMessage) {
+ assumeTrue(mContext.getString(R.string.errorAuthResponse, errorCode,
+ errorMessage), false);
+ }
+ }));
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, Process.myUid());
+ PackageManager pm = mContext.getPackageManager();
+ assumeNotNull(pm);
+ ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ assumeNotNull(mContext.getString(R.string.activityNotFoundMsg, intent), ri);
+ assumeNotNull(ri.activityInfo);
+ mVulActivity = ri.activityInfo.name;
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ assumeNoException(e);
+ }
+ }
+
+ @Test
+ public void testOverlayButtonPresence() {
+ mContext = getApplicationContext();
+ assumeNotNull(mContext);
+ UiDevice device = UiDevice.getInstance(getInstrumentation());
+ assumeNotNull(device);
+
+ /* Start the overlay service */
+ startOverlayService();
+
+ /* Wait for the overlay window */
+ Pattern overlayTextPattern = Pattern.compile(mContext.getString(R.string.overlayButtonText),
+ Pattern.CASE_INSENSITIVE);
+ final int launchTimeoutMs = 20000;
+ assumeTrue(mContext.getString(R.string.overlayUiScreenError),
+ device.wait(Until.hasObject(By.text(overlayTextPattern)), launchTimeoutMs));
+
+ /* Start the vulnerable activity */
+ startVulnerableActivity();
+
+ /* Wait until an object of current activity is gone */
+ boolean overlayDisallowed =
+ device.wait(Until.gone(By.pkg(mContext.getPackageName())), launchTimeoutMs);
+
+ /* Check if the currently running activity is the vulnerable activity */
+ String activityDump = "";
+ try {
+ activityDump = device.executeShellCommand(
+ mContext.getString(R.string.cmdDumpsysActivity, mVulActivity));
+ } catch (IOException e) {
+ assumeNoException(mContext.getString(R.string.exShellCmdDumpsys), e);
+ }
+ Pattern activityPattern = Pattern.compile(mContext.getString(R.string.mResumedTrue),
+ Pattern.CASE_INSENSITIVE);
+ assumeTrue(mContext.getString(R.string.vulActivityNotRunningError, mVulActivity),
+ activityPattern.matcher(activityDump).find());
+
+ /* Failing the test as fix is not present */
+ assertTrue(mContext.getString(R.string.failMsg, mVulActivity), overlayDisallowed);
+ }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/src/android/security/cts/CVE_2021_0315/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/src/android/security/cts/CVE_2021_0315/PocService.java
new file mode 100644
index 0000000..319546d
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0315/src/android/security/cts/CVE_2021_0315/PocService.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_0315;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+
+public class PocService extends Service {
+ Button mButton;
+ WindowManager mWindowManager;
+ LayoutParams mLayoutParams;
+
+ private int getScreenWidth() {
+ return Resources.getSystem().getDisplayMetrics().widthPixels;
+ }
+
+ private int getScreenHeight() {
+ return Resources.getSystem().getDisplayMetrics().heightPixels;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mWindowManager = getSystemService(WindowManager.class);
+ mLayoutParams = new LayoutParams();
+ mLayoutParams.type = LayoutParams.TYPE_APPLICATION_OVERLAY;
+ mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
+ mLayoutParams.format = PixelFormat.OPAQUE;
+ mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+ mLayoutParams.width = getScreenWidth();
+ mLayoutParams.height = getScreenHeight();
+ mLayoutParams.x = getScreenWidth() / 2;
+ mLayoutParams.y = getScreenHeight() / 2;
+
+ /* Show the floating window */
+ assumeTrue(getString(R.string.canNotDrawOverlaysMsg), Settings.canDrawOverlays(this));
+ mButton = new Button(this);
+ mButton.setText(getString(R.string.overlayButtonText));
+ mWindowManager.addView(mButton, mLayoutParams);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mWindowManager != null && mButton != null) {
+ mWindowManager.removeView(mButton);
+ }
+ super.onDestroy();
+ }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/Android.bp
new file mode 100644
index 0000000..4cb6abe
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/Android.bp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CVE-2022-20115",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "sts",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.test.core",
+ "compatibility-device-util-axt",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/AndroidManifest.xml
new file mode 100644
index 0000000..5a48cbb
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="android.security.cts.CVE_2022_20115"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+ <application
+ android:allowBackup="true"
+ android:label="CVE_2022_20115"
+ android:supportsRtl="true">
+ <activity android:name="PocActivity" android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.security.cts.CVE_2022_20115" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/res/layout/activity_main.xml
new file mode 100644
index 0000000..b8f7f3f
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/res/layout/activity_main.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:text="CVE-2022-20115"/>
+</LinearLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/res/values/integers.xml b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/res/values/integers.xml
new file mode 100644
index 0000000..1389507
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/res/values/integers.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<resources>
+ <integer name="PASS">0</integer>
+ <integer name="ASSUMPTION_FAILURE">1</integer>
+ <integer name="FAIL">2</integer>
+ <integer name="MAX_WAIT_TIME_MS">2000</integer>
+ <integer name="BROADCAST_WAIT_TIME_MS">100</integer>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/res/values/strings.xml
new file mode 100644
index 0000000..0fd17f5
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/res/values/strings.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<resources>
+ <string name="activityNotStartedException">Activity Failed to Start!</string>
+ <string name="airplaneModeEnable">enable</string>
+ <string name="airplaneModeDisable">disable</string>
+ <string name="assumptionFailure">ServiceState Object or Bundle returned NULL</string>
+ <string name="errorMessage">Device is vulnerable to b/210118427! hence any app with
+ READ_PHONE_STATE permission can access Cell Location</string>
+ <string name="failedToDisable">Failed to Disable Airplane mode!</string>
+ <string name="failedToEnable">Failed to Enable Airplane mode!</string>
+ <string name="failedToExecute">Failed to execute shell command!</string>
+ <string name="failedToOpenApp">Failed to open the app!</string>
+ <string name="getAirplaneModeStatus">settings get global airplane_mode_on</string>
+ <string name="passMessage">Test Passed.Fix is present.</string>
+ <string name="MESSAGE_KEY">MESSAGE</string>
+ <string name="RESULT_KEY">RESULT</string>
+ <string name="SHARED_PREFERENCE">CVE_2022_20115</string>
+ <string name="serviceStateIntentExtra">android.intent.extra.SERVICE_STATE</string>
+ <string name="sendBroadcast">cmd connectivity airplane-mode</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/src/android/security/cts/CVE_2022_20115/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/src/android/security/cts/CVE_2022_20115/DeviceTest.java
new file mode 100644
index 0000000..d94e577
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/src/android/security/cts/CVE_2022_20115/DeviceTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2022_20115;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.pm.PackageManager;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import com.android.compatibility.common.util.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+ static final int TIMEOUT_MS = 20000;
+ static final int TIMEOUT_SP = 10;
+ private UiDevice mDevice;
+ private Context mContext;
+
+ @Before
+ public void enableAirplaneMode() {
+ mContext = getApplicationContext();
+ assumeNotNull(mContext);
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ assumeNotNull(mDevice);
+ try {
+ mDevice.executeShellCommand(mContext.getResources().getString(R.string.sendBroadcast)
+ + " " + mContext.getResources().getString(R.string.airplaneModeEnable));
+ TestUtils.waitUntil(mContext.getResources().getString(R.string.failedToEnable),
+ TIMEOUT_SP / 2, () -> isAirplaneModeOn());
+ } catch (Exception e) {
+ assumeNoException(mContext.getResources().getString(R.string.failedToExecute), e);
+ }
+ }
+
+ boolean isAirplaneModeOn() {
+ boolean isEnabled = false;
+ try {
+ isEnabled = mDevice
+ .executeShellCommand(
+ mContext.getResources().getString(R.string.getAirplaneModeStatus))
+ .trim().equals("1");
+ } catch (IOException e) {
+ assumeNoException(mContext.getResources().getString(R.string.failedToExecute), e);
+ }
+ return isEnabled;
+ }
+
+ boolean isAirplaneModeOff() {
+ boolean isDisabled = false;
+ try {
+ isDisabled = mDevice
+ .executeShellCommand(
+ mContext.getResources().getString(R.string.getAirplaneModeStatus))
+ .trim().equals("0");
+ } catch (IOException e) {
+ assumeNoException(mContext.getResources().getString(R.string.failedToExecute), e);
+ }
+ return isDisabled;
+ }
+
+ @Test
+ public void testCellLocation() {
+ PackageManager packageManager = mContext.getPackageManager();
+ assumeNotNull(packageManager);
+ final Intent intent = packageManager.getLaunchIntentForPackage(mContext.getPackageName());
+ assumeNotNull(intent);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ try {
+ mContext.startActivity(intent);
+ } catch (Exception e) {
+ assumeNoException(mContext.getString(R.string.activityNotStartedException), e);
+ }
+ assumeTrue(mContext.getResources().getString(R.string.failedToOpenApp), mDevice
+ .wait(Until.hasObject(By.pkg(mContext.getPackageName()).depth(0)), TIMEOUT_MS));
+
+ /* Disable Airplane Mode */
+ try {
+ mDevice.executeShellCommand(mContext.getResources().getString(R.string.sendBroadcast)
+ + " " + mContext.getResources().getString(R.string.airplaneModeDisable));
+ TestUtils.waitUntil(mContext.getResources().getString(R.string.failedToDisable),
+ TIMEOUT_SP / 2, () -> isAirplaneModeOff());
+ } catch (Exception e) {
+ assumeNoException(mContext.getResources().getString(R.string.failedToExecute), e);
+ }
+
+ int result = -1;
+ String message = null;
+ SharedPreferences sh = mContext.getSharedPreferences(
+ mContext.getResources().getString(R.string.SHARED_PREFERENCE),
+ mContext.MODE_APPEND);
+ assumeNotNull(sh);
+ final Semaphore preferenceChanged = new Semaphore(0);
+ OnSharedPreferenceChangeListener listener = new OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals(mContext.getResources().getString(R.string.RESULT_KEY))) {
+ if (sharedPreferences.getInt(key, 0) == mContext.getResources()
+ .getInteger(R.integer.FAIL)) {
+ preferenceChanged.release();
+ }
+ }
+ }
+ };
+ sh.registerOnSharedPreferenceChangeListener(listener);
+ try {
+ preferenceChanged.tryAcquire(TIMEOUT_SP, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ assumeNoException(e);
+ }
+ result = sh.getInt(mContext.getResources().getString(R.string.RESULT_KEY),
+ mContext.getResources().getInteger(R.integer.PASS));
+ message = sh.getString(mContext.getResources().getString(R.string.MESSAGE_KEY),
+ mContext.getResources().getString(R.string.passMessage));
+ assumeTrue(message,
+ result != mContext.getResources().getInteger(R.integer.ASSUMPTION_FAILURE));
+ assertNotEquals(message, result, mContext.getResources().getInteger(R.integer.FAIL));
+ }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/src/android/security/cts/CVE_2022_20115/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/src/android/security/cts/CVE_2022_20115/PocActivity.java
new file mode 100644
index 0000000..8d38d94
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20115/src/android/security/cts/CVE_2022_20115/PocActivity.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2022_20115;
+
+import static org.junit.Assume.assumeNoException;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.telephony.CellIdentity;
+import android.telephony.ServiceState;
+import android.telephony.NetworkRegistrationInfo;
+import android.os.Bundle;
+
+import java.util.List;
+import java.util.ArrayList;
+
+public class PocActivity extends Activity {
+ private void setResultForSP(int result, String message) {
+ SharedPreferences sh =
+ getSharedPreferences(getString(R.string.SHARED_PREFERENCE), Context.MODE_PRIVATE);
+ SharedPreferences.Editor edit = sh.edit();
+ edit.putInt(getString(R.string.RESULT_KEY), result);
+ edit.putString(getString(R.string.MESSAGE_KEY), message);
+ edit.commit();
+ }
+
+ private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ ServiceState serviceStateObj;
+ Bundle bundle = intent.getExtras();
+ if (bundle == null) {
+ setResultForSP(getResources().getInteger(R.integer.ASSUMPTION_FAILURE),
+ getResources().getString(R.string.assumptionFailure));
+ return;
+ }
+
+ serviceStateObj = bundle.getParcelable(
+ context.getResources().getString(R.string.serviceStateIntentExtra));
+ if (serviceStateObj == null) {
+ setResultForSP(getResources().getInteger(R.integer.ASSUMPTION_FAILURE),
+ getResources().getString(R.string.assumptionFailure));
+ return;
+ }
+
+ List<NetworkRegistrationInfo> networkInfoList = new ArrayList<>();
+ networkInfoList = serviceStateObj.getNetworkRegistrationInfoList();
+ for (NetworkRegistrationInfo netRegInfo : networkInfoList) {
+ long startTime = System.currentTimeMillis();
+ CellIdentity cellIdentity = netRegInfo.getCellIdentity();
+ while ((cellIdentity == null) && (System.currentTimeMillis() - startTime < context
+ .getResources().getInteger(R.integer.MAX_WAIT_TIME_MS))) {
+ /* waiting to receive airplane mode changes broadcast */
+ try {
+ Thread.sleep(context.getResources()
+ .getInteger(R.integer.BROADCAST_WAIT_TIME_MS));
+ } catch (InterruptedException e) {
+ assumeNoException(e);
+ }
+ }
+
+ if (cellIdentity != null) {
+ setResultForSP(getResources().getInteger(R.integer.FAIL),
+ getResources().getString(R.string.errorMessage));
+ } else {
+ setResultForSP(getResources().getInteger(R.integer.PASS),
+ getResources().getString(R.string.passMessage));
+ }
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_SERVICE_STATE);
+ PocActivity pocActivity = PocActivity.this;
+ pocActivity.registerReceiver(pocActivity.broadcastReceiver, intentFilter);
+ }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
index 76f4723..b716174 100644
--- a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
@@ -144,7 +144,10 @@
mLocationTimeZoneManagerShellHelper.stop();
// Reset settings and server flags as best we can.
- mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
+ if (mTimeZoneDetectorShellHelper.isAutoDetectionEnabled()
+ != mOriginalAutoDetectionEnabled) {
+ mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
+ }
mLocationShellHelper.setLocationEnabledForCurrentUser(mOriginalLocationEnabled);
mDeviceConfigShellHelper.restoreDeviceConfigStateForTest(mDeviceConfigPreTestState);
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java
index 0be4769..e591d2d 100644
--- a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java
@@ -142,7 +142,10 @@
mLocationTimeZoneManagerShellHelper.stop();
// Reset settings and server flags as best we can.
- mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
+ if (mTimeZoneDetectorShellHelper.isAutoDetectionEnabled()
+ != mOriginalAutoDetectionEnabled) {
+ mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
+ }
mLocationShellHelper.setLocationEnabledForCurrentUser(mOriginalLocationEnabled);
ConfigUtils.removeConfig(getDevice());
diff --git a/tests/PhotoPicker/Android.bp b/tests/PhotoPicker/Android.bp
index 5a9fc33..45ecfe2 100644
--- a/tests/PhotoPicker/Android.bp
+++ b/tests/PhotoPicker/Android.bp
@@ -25,6 +25,7 @@
test_suites: ["general-tests", "mts-mediaprovider", "cts"],
sdk_version: "core_current",
min_sdk_version: "30",
+ target_sdk_version: "33",
libs: [
"android.test.base",
"android.test.runner",
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 674ee21..201a85d 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -1954,8 +1954,13 @@
@Test
public void testGetUidProcessState_checkAccess() throws Exception {
- PermissionUtils.grantPermission(
- STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ boolean hasPermissionGrantChanged = false;
+ if (!PermissionUtils.isPermissionGranted(STUB_PACKAGE_NAME,
+ android.Manifest.permission.PACKAGE_USAGE_STATS)) {
+ PermissionUtils.grantPermission(
+ STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ hasPermissionGrantChanged = true;
+ }
int newUserId = UserHandle.USER_NULL;
try {
// Verify that calling the API doesn't trigger any exceptions.
@@ -1979,15 +1984,22 @@
if (newUserId != UserHandle.USER_NULL) {
removeUser(newUserId);
}
- PermissionUtils.revokePermission(
- STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ if (hasPermissionGrantChanged) {
+ PermissionUtils.revokePermission(
+ STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ }
}
}
@Test
public void testGetUidProcessCapabilities_checkAccess() throws Exception {
- PermissionUtils.grantPermission(
- STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ boolean hasPermissionGrantChanged = false;
+ if (!PermissionUtils.isPermissionGranted(STUB_PACKAGE_NAME,
+ android.Manifest.permission.PACKAGE_USAGE_STATS)) {
+ PermissionUtils.grantPermission(
+ STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ hasPermissionGrantChanged = true;
+ }
int newUserId = UserHandle.USER_NULL;
try {
// Verify that calling the API doesn't trigger any exceptions.
@@ -2011,8 +2023,10 @@
if (newUserId != UserHandle.USER_NULL) {
removeUser(newUserId);
}
- PermissionUtils.revokePermission(
- STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ if (hasPermissionGrantChanged) {
+ PermissionUtils.revokePermission(
+ STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
+ }
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java
index 0f6ce62..9865d1b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java
@@ -114,7 +114,7 @@
mUiBot.waitForIdleSync();
// Verify IME is shown
- assertMockImeStatus(activity.getRootWindowInsets(), true);
+ assertMockImeStatus(activity, true);
}
@Test
@@ -526,6 +526,93 @@
activity.assertAutoFilled();
}
+ @Test
+ public void testCancelFillDialog_showDropdown() throws Exception {
+ // Enable feature and test service
+ enableFillDialogFeature(sContext);
+ enableService();
+
+ // Set response with a dataset > fill dialog should have two buttons
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("Dropdown Presentation"))
+ .setDialogPresentation(createPresentation("Dialog Presentation"))
+ .build())
+ .setDialogHeader(createPresentation("Dialog Header"))
+ .setDialogTriggerIds(ID_PASSWORD);
+ sReplier.addResponse(builder.build());
+
+ // Start activity and autofill
+ LoginActivity activity = startLoginActivity();
+ mUiBot.waitForIdleSync();
+
+ sReplier.getNextFillRequest();
+ mUiBot.waitForIdleSync();
+
+ // Click on password field to trigger fill dialog
+ mUiBot.selectByRelativeIdFromUiDevice(ID_PASSWORD);
+ mUiBot.waitForIdleSync();
+
+ // Verify the fill dialog shown
+ mUiBot.assertFillDialogDatasets("Dialog Presentation");
+
+ // Touch outside to cancel the fill dialog, should back to dropdown UI
+ mUiBot.touchOutsideDialog();
+ mUiBot.waitForIdleSync();
+
+ mUiBot.assertDatasets("Dropdown Presentation");
+ assertMockImeStatus(activity, true);
+
+ // Set expected value, then select dataset
+ activity.expectAutoFill("dude", "sweet");
+ mUiBot.selectDataset("Dropdown Presentation");
+
+ // Check the results.
+ activity.assertAutoFilled();
+ }
+
+ @Test
+ public void testDismissedFillDialog_showIme() throws Exception {
+ // Enable feature and test service
+ enableFillDialogFeature(sContext);
+ enableService();
+
+ // Set response with a dataset
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("Dropdown Presentation"))
+ .setDialogPresentation(createPresentation("Dialog Presentation"))
+ .build())
+ .setDialogHeader(createPresentation("Dialog Header"))
+ .setDialogTriggerIds(ID_PASSWORD);
+ sReplier.addResponse(builder.build());
+
+ // Start activity and autofill
+ LoginActivity activity = startLoginActivity();
+ mUiBot.waitForIdleSync();
+
+ sReplier.getNextFillRequest();
+ mUiBot.waitForIdleSync();
+
+ // Click on password field to trigger fill dialog
+ mUiBot.selectByRelativeIdFromUiDevice(ID_PASSWORD);
+ mUiBot.waitForIdleSync();
+
+ // Verify the fill dialog shown
+ mUiBot.assertFillDialogDatasets("Dialog Presentation");
+
+ // Touch "No thanks" button to dismiss the fill dialog
+ mUiBot.clickFillDialogDismiss();
+ mUiBot.waitForIdleSync();
+
+ // Verify IME is shown
+ assertMockImeStatus(activity, true);
+ }
+
private FieldsNoPasswordActivity startNoPasswordActivity() throws Exception {
final Intent intent = new Intent(mContext, FieldsNoPasswordActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
index 9502925..c9d4fa6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -39,6 +39,7 @@
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.LoginActivity;
import android.content.AutofillOptions;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -1696,11 +1697,11 @@
/**
* Asserts whether mock IME is showing
*/
- public static void assertMockImeStatus(WindowInsets rootWindowInsets,
+ public static void assertMockImeStatus(LoginActivity activity,
boolean expectedImeShow) throws Exception {
Timeouts.MOCK_IME_TIMEOUT.run("assertMockImeStatus(" + expectedImeShow + ")",
() -> {
- final boolean actual = isImeShowing(rootWindowInsets);
+ final boolean actual = isImeShowing(activity.getRootWindowInsets());
Log.v(TAG, "assertMockImeStatus(): expected=" + expectedImeShow + ", actual="
+ actual);
return actual == expectedImeShow ? "expected" : null;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
index e51e4c9..134e0c0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
@@ -1368,6 +1368,26 @@
selectDataset(picker, dataset);
}
+ /**
+ * Touch outside the fill dialog.
+ */
+ public void touchOutsideDialog() throws Exception {
+ Log.v(TAG, "touchOutsideDialog()");
+ final UiObject2 picker = findFillDialogPicker();
+ mDevice.click(1, picker.getVisibleBounds().top - 1);
+ }
+
+ /**
+ * click dismiss button the fill dialog.
+ */
+ public void clickFillDialogDismiss() throws Exception {
+ Log.v(TAG, "dismissedFillDialog()");
+ final UiObject2 picker = findFillDialogPicker();
+ final UiObject2 noButton =
+ picker.findObject(By.res("android", RESOURCE_ID_FILL_DIALOG_BUTTON_NO));
+ noButton.click();
+ }
+
private UiObject2 findFillDialogPicker() throws Exception {
return waitForObject(FILL_DIALOG_SELECTOR, UI_DATASET_PICKER_TIMEOUT);
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java
index 49586c4..1ec0a11 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java
@@ -82,7 +82,9 @@
@After
public void tearDown() {
- mTaskFragmentOrganizer.unregisterOrganizer();
+ if (mTaskFragmentOrganizer != null) {
+ mTaskFragmentOrganizer.unregisterOrganizer();
+ }
}
public static IBinder getActivityToken(@NonNull Activity activity) {
diff --git a/tests/quickaccesswallet/src/android/quickaccesswallet/cts/QuickAccessWalletClientTest.java b/tests/quickaccesswallet/src/android/quickaccesswallet/cts/QuickAccessWalletClientTest.java
index 8a5d528..0e794d0 100755
--- a/tests/quickaccesswallet/src/android/quickaccesswallet/cts/QuickAccessWalletClientTest.java
+++ b/tests/quickaccesswallet/src/android/quickaccesswallet/cts/QuickAccessWalletClientTest.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
import android.quickaccesswallet.NoPermissionQuickAccessWalletService;
import android.quickaccesswallet.QuickAccessWalletActivity;
@@ -69,6 +70,7 @@
* Tests parceling of the {@link WalletCard}
*/
@RunWith(AndroidJUnit4.class)
+@AppModeFull
public class QuickAccessWalletClientTest {
private static final String SETTING_KEY = "lockscreen_show_wallet";
diff --git a/tests/signature/api-check/shared-libs-api/Android.bp b/tests/signature/api-check/shared-libs-api/Android.bp
index afab2f3..607df48 100644
--- a/tests/signature/api-check/shared-libs-api/Android.bp
+++ b/tests/signature/api-check/shared-libs-api/Android.bp
@@ -24,6 +24,7 @@
static_libs: [
"cts-api-signature-test",
"compatibility-device-util-axt",
+ "junit-params",
],
}
@@ -36,6 +37,8 @@
jarjar_rules: ":cts-android-test-jarjar-rules",
java_resources: [
+ ":cts-current-api-gz",
+ ":cts-shared-libs-names.txt",
":CtsSharedLibsApiSignatureTestCases_cts-shared-libs-all-current.api",
":CtsSharedLibsApiSignatureTestCases_cts-shared-libs-all-previous.api",
],
@@ -60,12 +63,29 @@
srcs: ["src/**/*.java"],
}
+genrule {
+ name: "cts-shared-libs-names.txt",
+ srcs: [
+ "AndroidManifest.xml",
+ ],
+ out: [
+ "shared-libs-names.txt",
+ ],
+ cmd: "grep 'uses-library' $(in) | cut -f2 -d\\\" | sort > $(out)",
+}
+
// Generates a zip file containing the current public and system API files for shared libraries.
genrule {
name: "CtsSharedLibsApiSignatureTestCases_cts-shared-libs-all-current.api",
srcs: [
":android.net.ipsec.ike{.public.api.txt}",
":android.net.ipsec.ike{.system.api.txt}",
+ ":android.test.base{.public.api.txt}",
+ ":android.test.base{.system.api.txt}",
+ ":android.test.runner{.public.api.txt}",
+ ":android.test.runner{.system.api.txt}",
+ ":android.test.mock{.public.api.txt}",
+ ":android.test.mock{.system.api.txt}",
":com.android.future.usb.accessory{.public.api.txt}",
":com.android.future.usb.accessory{.system.api.txt}",
":com.android.libraries.tv.tvsystem{.public.api.txt}",
@@ -82,6 +102,8 @@
":com.android.nfc_extras{.system.api.txt}",
":javax.obex{.public.api.txt}",
":javax.obex{.system.api.txt}",
+ ":org.apache.http.legacy{.public.api.txt}",
+ ":org.apache.http.legacy{.system.api.txt}",
],
tools: [
"soong_zip",
diff --git a/tests/signature/api-check/shared-libs-api/AndroidTest.xml b/tests/signature/api-check/shared-libs-api/AndroidTest.xml
index 14a54a2..9bd8233 100644
--- a/tests/signature/api-check/shared-libs-api/AndroidTest.xml
+++ b/tests/signature/api-check/shared-libs-api/AndroidTest.xml
@@ -28,6 +28,7 @@
<option name="package" value="android.signature.cts.api.shared_libs" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="class" value="android.signature.cts.api.SignatureMultiLibsTest" />
+ <option name="instrumentation-arg" key="base-api-files" value="current.api.gz" />
<option name="instrumentation-arg" key="expected-api-files" value="shared-libs-all-current.api.zip" />
<option name="instrumentation-arg" key="previous-api-files" value="shared-libs-all-previous.api.zip" />
<option name="runtime-hint" value="30s" />
diff --git a/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java b/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java
index 2bc63f7..2d0f719 100644
--- a/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java
+++ b/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java
@@ -17,20 +17,33 @@
package android.signature.cts.api;
import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.SharedLibraryInfo;
import android.signature.cts.ApiComplianceChecker;
import android.signature.cts.ApiDocumentParser;
import android.signature.cts.JDiffClassDescription;
import android.signature.cts.VirtualPath;
+import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
-import java.util.Arrays;
+import com.google.common.base.Suppliers;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.junit.BeforeClass;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import junitparams.naming.TestCaseName;
+import org.junit.Assume;
+import org.junit.FixMethodOrder;
import org.junit.Test;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
/**
* Verifies that any shared library provided by this device and for which this test has a
@@ -40,23 +53,103 @@
* {@code <uses-library>} entry for every shared library that provides an API that is contained
* within the shared-libs-all.api.zip supplied to this test.
*/
-public class SignatureMultiLibsTest extends SignatureTest {
+@RunWith(JUnitParamsRunner.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SignatureMultiLibsTest extends AbstractSignatureTest {
+
+ protected static final Supplier<String[]> EXPECTED_API_FILES =
+ getSupplierOfAMandatoryCommaSeparatedListArgument(EXPECTED_API_FILES_ARG);
+
+ protected static final Supplier<String[]> PREVIOUS_API_FILES =
+ getSupplierOfAMandatoryCommaSeparatedListArgument(PREVIOUS_API_FILES_ARG);
private static final String TAG = SignatureMultiLibsTest.class.getSimpleName();
- private static Set<String> libraries;
+ /**
+ * A memoized supplier of the list of shared libraries on the device.
+ */
+ protected static final Supplier<Set<String>> AVAILABLE_SHARED_LIBRARIES =
+ Suppliers.memoize(SignatureMultiLibsTest::retrieveActiveSharedLibraries)::get;
+
+ private static final String SHARED_LIBRARY_LIST_RESOURCE_NAME = "shared-libs-names.txt";
/**
- * Obtain a list of shared libraries from the device.
+ * Retrieve the names of the shared libraries that are active on the device.
+ *
+ * @return The set of shared library names.
*/
- @BeforeClass
- public static void retrieveListOfSharedLibrariesOnDevice() throws Exception {
+ private static Set<String> retrieveActiveSharedLibraries() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- String result = runShellCommand(instrumentation, "cmd package list libraries");
- libraries = Arrays.stream(result.split("\n")).map(line -> line.split(":")[1])
- .peek(library -> System.out.printf("%s: Found library: %s%n",
- SignatureMultiLibsTest.class.getSimpleName(), library))
- .collect(Collectors.toCollection(TreeSet::new));
+ Context context = instrumentation.getTargetContext();
+
+ List<SharedLibraryInfo> sharedLibraries =
+ context.getPackageManager().getSharedLibraries(0);
+
+ Set<String> sharedLibraryNames = new TreeSet<>();
+ for (SharedLibraryInfo sharedLibrary : sharedLibraries) {
+ String name = sharedLibrary.getName();
+ sharedLibraryNames.add(name);
+ Log.d(TAG, String.format("Found library: %s%n", name));
+ }
+
+ return sharedLibraryNames;
+ }
+
+ /**
+ * A memoized supplier of the list of shared libraries that this can test.
+ */
+ protected static final Supplier<List<String>> TESTABLE_SHARED_LIBRARIES =
+ Suppliers.memoize(SignatureMultiLibsTest::retrieveTestableSharedLibraries)::get;
+
+ /**
+ * Retrieve the names of the shared libraries that are testable by this test.
+ *
+ * @return The set of shared library names.
+ */
+ private static List<String> retrieveTestableSharedLibraries() {
+ ClassLoader classLoader = SignatureMultiLibsTest.class.getClassLoader();
+ try (InputStream is = classLoader.getResourceAsStream(SHARED_LIBRARY_LIST_RESOURCE_NAME)) {
+ if (is == null) {
+ throw new RuntimeException(
+ "Resource " + SHARED_LIBRARY_LIST_RESOURCE_NAME + " could not be found");
+ }
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
+ return reader.lines()
+ .filter(line -> !line.isEmpty())
+ .sorted()
+ .collect(Collectors.toList());
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Could not retrieve testable shared libraries", e);
+ }
+ }
+
+ /**
+ * Convert the list of testable shared libraries into a form suitable for parameterizing a test.
+ */
+ public Object[][] getTestableSharedLibraryParameters() {
+ List<String> libraries = TESTABLE_SHARED_LIBRARIES.get();
+ Object[][] params = new Object[libraries.size()][1];
+ for (int i = 0; i < libraries.size(); i++) {
+ String name = libraries.get(i);
+ TestableLibraryParameter param = new TestableLibraryParameter(name);
+ params[i][0] = param;
+ }
+ return params;
+ }
+
+ /**
+ * Skips the test if the supplied library is unavailable on the device.
+ *
+ * <p>If the library is unavailable then this throws an
+ * {@link org.junit.AssumptionViolatedException}.</p>
+ *
+ * @param library the name of the library that needs to be available.
+ */
+ private void skipTestIfLibraryIsUnavailable(String library) {
+ Assume.assumeTrue("Shared library " + library + " is not available on this device",
+ AVAILABLE_SHARED_LIBRARIES.get().contains(library));
}
/**
@@ -65,29 +158,45 @@
*
* @param apiDocumentParser the parser to use.
* @param apiResources the list of API resource files.
+ * @param library the name of the library whose APIs should be parsed.
* @return a stream of {@link JDiffClassDescription}.
*/
private Stream<JDiffClassDescription> parseActiveSharedLibraryApis(
- ApiDocumentParser apiDocumentParser, String[] apiResources) {
+ ApiDocumentParser apiDocumentParser, String[] apiResources, String library) {
+
return retrieveApiResourcesAsStream(getClass().getClassLoader(), apiResources)
- .filter(this::checkLibrary)
+ .filter(path -> {
+ String apiLibraryName = getLibraryNameFromPath(path);
+ return apiLibraryName.equals(library);
+ })
.flatMap(apiDocumentParser::parseAsStream);
}
/**
- * Tests that the device's API matches the expected set defined in xml.
- * <p/>
- * Will check the entire API, and then report the complete list of failures
+ * Tests that each shared library's API matches its current API.
+ *
+ * <p>One test per shared library, checks the entire API, and then reports the complete list of
+ * failures.</p>
*/
@Test
- public void testSignature() {
+ // Parameterize this method with the set of testable shared libraries.
+ @Parameters(method = "getTestableSharedLibraryParameters")
+ // The test name is the method name followed by and _ and the shared library name, with .s
+ // replaced with _. e.g. testRuntimeCompatibilityWithCurrentApi_android_test_base.
+ @TestCaseName("{method}_{0}")
+ public void testRuntimeCompatibilityWithCurrentApi(TestableLibraryParameter parameter) {
+ String library = parameter.getName();
+ skipTestIfLibraryIsUnavailable(library);
runWithTestResultObserver(mResultObserver -> {
ApiComplianceChecker complianceChecker =
new ApiComplianceChecker(mResultObserver, mClassProvider);
+ // Load classes from any API files that form the base which the expected APIs extend.
+ loadBaseClasses(complianceChecker);
+
ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
- parseActiveSharedLibraryApis(apiDocumentParser, expectedApiFiles)
+ parseActiveSharedLibraryApis(apiDocumentParser, EXPECTED_API_FILES.get(), library)
.forEach(complianceChecker::checkSignatureCompliance);
// After done parsing all expected API files, perform any deferred checks.
@@ -96,17 +205,27 @@
}
/**
- * Tests that the device's API matches the previous APIs defined in xml.
+ * Tests that each shared library's API matches its previous APIs.
+ *
+ * <p>One test per shared library, checks the entire API, and then reports the complete list of
+ * failures.</p>
*/
@Test
- public void testPreviousSignatures() {
+ // Parameterize this method with the set of testable shared libraries.
+ @Parameters(method = "getTestableSharedLibraryParameters")
+ // The test name is the method name followed by and _ and the shared library name, with .s
+ // replaced with _. e.g. testRuntimeCompatibilityWithPreviousApis_android_test_base.
+ @TestCaseName("{method}_{0}")
+ public void testRuntimeCompatibilityWithPreviousApis(TestableLibraryParameter parameter) {
+ String library = parameter.getName();
+ skipTestIfLibraryIsUnavailable(library);
runWithTestResultObserver(mResultObserver -> {
ApiComplianceChecker complianceChecker =
new ApiComplianceChecker(mResultObserver, mClassProvider);
ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
- parseActiveSharedLibraryApis(apiDocumentParser, previousApiFiles)
+ parseActiveSharedLibraryApis(apiDocumentParser, PREVIOUS_API_FILES.get(), library)
.map(clazz -> clazz.setPreviousApiFlag(true))
.forEach(complianceChecker::checkSignatureCompliance);
@@ -116,25 +235,37 @@
}
/**
- * Check to see if the supplied name is an API file for a shared library that is available on
- * this device.
+ * Get the library name from the API file's path.
*
* @param path the path of the API file.
- * @return true if the API corresponds to a shared library on the device, false otherwise.
+ * @return the library name for the API file.
*/
- private boolean checkLibrary (VirtualPath path) {
+ private String getLibraryNameFromPath(VirtualPath path) {
String name = path.toString();
- String libraryName = name.substring(name.lastIndexOf('/') + 1).split("-")[0];
- boolean matched = libraries.contains(libraryName);
- if (matched) {
- System.out.printf("%s: Processing API file %s, from library %s as it does match a"
- + " shared library on this device%n",
- getClass().getSimpleName(), name, libraryName);
- } else {
- System.out.printf("%s: Ignoring API file %s, from library %s as it does not match a"
- + " shared library on this device%n",
- getClass().getSimpleName(), name, libraryName);
+ return name.substring(name.lastIndexOf('/') + 1).split("-")[0];
+ }
+
+ /**
+ * A wrapper around a shared library name to ensure that its string representation is suitable
+ * for use in a parameterized test name, i.e. does not contain any characters that are not
+ * allowed in a test name by CTS/AndroidJUnitRunner.
+ */
+ public static class TestableLibraryParameter {
+ private final String name;
+ private final String parameter;
+
+ public TestableLibraryParameter(String name) {
+ this.name = name;
+ this.parameter = name.replace('.', '_');
}
- return matched;
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return parameter;
+ }
}
}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
index f4d364b..b1dd014 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
@@ -26,24 +26,16 @@
import android.signature.cts.JDiffClassDescription;
import android.signature.cts.ResultObserver;
import android.signature.cts.VirtualPath;
-import android.signature.cts.VirtualPath.LocalFilePath;
-import android.signature.cts.VirtualPath.ResourcePath;
import android.util.Log;
-
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-
import com.android.compatibility.common.util.DynamicConfigDeviceSide;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import com.google.common.base.Suppliers;
import java.util.Collection;
import java.util.Collections;
-import java.util.function.Predicate;
+import java.util.function.Supplier;
import java.util.stream.Stream;
-import java.util.zip.ZipFile;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.runner.RunWith;
@@ -62,8 +54,6 @@
*/
private static final String DYNAMIC_CONFIG_NAME_OPTION = "dynamic-config-name";
- private static final String TAG = "SignatureTest";
-
private TestResultObserver mResultObserver;
ClassProvider mClassProvider;
@@ -73,6 +63,11 @@
*/
private Collection<String> expectedFailures = Collections.emptyList();
+ @AfterClass
+ public static void closeResourceStore() {
+ ResourceStore.close();
+ }
+
public Instrumentation getInstrumentation() {
return InstrumentationRegistry.getInstrumentation();
}
@@ -176,6 +171,13 @@
mResultObserver.onTestComplete(); // Will throw is there are failures
}
+ static Supplier<String[]> getSupplierOfAnOptionalCommaSeparatedListArgument(String key) {
+ return Suppliers.memoize(() -> {
+ Bundle arguments = InstrumentationRegistry.getArguments();
+ return getCommaSeparatedListOptional(arguments, key);
+ })::get;
+ }
+
static String[] getCommaSeparatedListOptional(Bundle instrumentationArgs, String key) {
String argument = instrumentationArgs.getString(key);
if (argument == null) {
@@ -184,6 +186,13 @@
return argument.split(",");
}
+ static Supplier<String[]> getSupplierOfAMandatoryCommaSeparatedListArgument(String key) {
+ return Suppliers.memoize(() -> {
+ Bundle arguments = InstrumentationRegistry.getArguments();
+ return getCommaSeparatedListRequired(arguments, key);
+ })::get;
+ }
+
static String[] getCommaSeparatedListRequired(Bundle instrumentationArgs, String key) {
String argument = instrumentationArgs.getString(key);
if (argument == null) {
@@ -192,47 +201,6 @@
return argument.split(",");
}
- private static Stream<VirtualPath> readResource(ClassLoader classLoader, String resourceName) {
- try {
- ResourcePath resourcePath =
- VirtualPath.get(classLoader, resourceName);
- if (resourceName.endsWith(".zip")) {
- // Extract to a temporary file and read from there.
- Path file = extractResourceToFile(resourceName, resourcePath.newInputStream());
- return flattenPaths(VirtualPath.get(file.toString()));
- } else {
- return Stream.of(resourcePath);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static Path extractResourceToFile(String resourceName, InputStream is) throws IOException {
- Path tempDirectory = Files.createTempDirectory("signature");
- Path file = tempDirectory.resolve(resourceName);
- Log.i(TAG, "extractResourceToFile: extracting " + resourceName + " to " + file);
- Files.copy(is, file);
- is.close();
- return file;
- }
-
- /**
- * Given a path in the local file system (possibly of a zip file) flatten it into a stream of
- * virtual paths.
- */
- private static Stream<VirtualPath> flattenPaths(LocalFilePath path) {
- try {
- if (path.toString().endsWith(".zip")) {
- return getZipEntryFiles(path);
- } else {
- return Stream.of(path);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
/**
* Create a stream of {@link JDiffClassDescription} by parsing a set of API resource files.
*
@@ -263,18 +231,6 @@
ClassLoader classLoader,
String[] apiResources) {
return Stream.of(apiResources)
- .flatMap(resourceName -> readResource(classLoader, resourceName));
- }
-
- /**
- * Get the zip entries that are files.
- *
- * @param path the path to the zip file.
- * @return paths to zip entries
- */
- private static Stream<VirtualPath> getZipEntryFiles(LocalFilePath path) throws IOException {
- @SuppressWarnings("resource")
- ZipFile zip = new ZipFile(path.toFile());
- return zip.stream().map(entry -> VirtualPath.get(zip, entry));
+ .flatMap(resourceName -> ResourceStore.readResource(classLoader, resourceName));
}
}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractSignatureTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractSignatureTest.java
new file mode 100644
index 0000000..6df224f
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractSignatureTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.signature.cts.api;
+
+import android.signature.cts.ApiComplianceChecker;
+import android.signature.cts.ApiDocumentParser;
+import java.util.function.Supplier;
+
+/**
+ * Base class of the tests that check accessibility of API signatures at runtime.
+ */
+public class AbstractSignatureTest extends AbstractApiTest {
+
+ private static final String TAG = AbstractSignatureTest.class.getSimpleName();
+
+ /**
+ * The name of the instrumentation option that contains the list of the current API signatures
+ * that are expected to be accessible.
+ */
+ protected static final String EXPECTED_API_FILES_ARG = "expected-api-files";
+
+ /**
+ * The name of the instrumentation option that contains the list of the previous API signatures
+ * that are expected to be accessible.
+ */
+ protected static final String PREVIOUS_API_FILES_ARG = "previous-api-files";
+
+ /**
+ * Supplier of the list of files specified in the instrumentation argument "base-api-files".
+ */
+ private static final Supplier<String[]> BASE_API_FILES =
+ getSupplierOfAnOptionalCommaSeparatedListArgument("base-api-files");
+
+ /**
+ * Load the base API files into the supplied compliance checker.
+ *
+ * <p>Base API files are not checked by the compliance checker but may be extended by classes
+ * which are checked.</p>
+ *
+ * @param complianceChecker the {@link ApiComplianceChecker} into which the base API will be
+ * loaded.
+ */
+ protected void loadBaseClasses(ApiComplianceChecker complianceChecker) {
+ ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
+ parseApiResourcesAsStream(apiDocumentParser, BASE_API_FILES.get())
+ .forEach(complianceChecker::addBaseClass);
+ }
+}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/ResourceStore.java b/tests/signature/api-check/src/java/android/signature/cts/api/ResourceStore.java
new file mode 100644
index 0000000..722ea63
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/ResourceStore.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.signature.cts.api;
+
+import android.signature.cts.VirtualPath;
+import android.util.Log;
+import com.google.common.base.Suppliers;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import java.util.zip.ZipFile;
+
+/**
+ * Manages local storage of resources that need to be extracted from the Jar into the local
+ * filesystem before use.
+ */
+public class ResourceStore {
+
+ private static final String TAG = ResourceStore.class.getSimpleName();
+
+ /**
+ * Supplier for the temporary directory.
+ */
+ private static final Supplier<Path> TEMPORARY_DIRECTORY = Suppliers.memoize(() -> {
+ try {
+ return Files.createTempDirectory("signature");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ })::get;
+
+ /**
+ * A map from a {@link VirtualPath} to a {@link ZipFile} for zip file resources that
+ * have been extracted from the jar into the local file system.
+ */
+ private static final Map<VirtualPath, ZipFile> extractedZipFiles = new HashMap<>();
+
+ public static Stream<VirtualPath> readResource(ClassLoader classLoader, String resourceName) {
+ try {
+ VirtualPath resourcePath = VirtualPath.get(classLoader, resourceName);
+ if (resourceName.endsWith(".zip")) {
+ // Extract to a temporary file and then read from there. If the resource has already
+ // been extracted before then reuse the previous file.
+ @SuppressWarnings("resource")
+ ZipFile zip = extractedZipFiles.computeIfAbsent(resourcePath, virtualPath -> {
+ try {
+ Path path = extractResourceToFile(resourceName, resourcePath);
+ return new ZipFile(path.toFile());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ return zip.stream().map(entry -> VirtualPath.get(zip, entry));
+ } else {
+ return Stream.of(resourcePath);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Close any previously opened {@link ZipFile} instances.
+ */
+ public static void close() {
+ for (ZipFile zipfile: extractedZipFiles.values()) {
+ // If an error occurred when closing the ZipFile log the failure and continue.
+ try {
+ zipfile.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Could not close ZipFile " + zipfile, e);
+ }
+ }
+ }
+
+ private static Path extractResourceToFile(String resourceName, VirtualPath resourcePath)
+ throws IOException {
+ Path tempDirectory = TEMPORARY_DIRECTORY.get();
+ Path file = tempDirectory.resolve(resourceName);
+ try (InputStream is = resourcePath.newInputStream()) {
+ Log.i(TAG, "extractResourceToFile: extracting " + resourceName + " to " + file);
+ Files.copy(is, file);
+ }
+ return file;
+ }
+}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java
index 1be75ca..136211f 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java
@@ -23,48 +23,55 @@
import android.signature.cts.FailureType;
import android.signature.cts.JDiffClassDescription;
import android.signature.cts.ReflectionHelper;
+import com.google.common.base.Suppliers;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.junit.Test;
/**
* Performs the signature check via a JUnit test.
*/
-public class SignatureTest extends AbstractApiTest {
+public class SignatureTest extends AbstractSignatureTest {
private static final String TAG = SignatureTest.class.getSimpleName();
- protected String[] expectedApiFiles;
- protected String[] previousApiFiles;
- protected String[] baseApiFiles;
- private String[] unexpectedApiFiles;
+ private static final String UNEXPECTED_API_FILES_ARG = "unexpected-api-files";
+
+ private static final Supplier<String[]> EXPECTED_API_FILES =
+ getSupplierOfAnOptionalCommaSeparatedListArgument(EXPECTED_API_FILES_ARG);
+ private static final Supplier<String[]> UNEXPECTED_API_FILES =
+ getSupplierOfAnOptionalCommaSeparatedListArgument(UNEXPECTED_API_FILES_ARG);
+ private static final Supplier<String[]> PREVIOUS_API_FILES =
+ getSupplierOfAnOptionalCommaSeparatedListArgument(PREVIOUS_API_FILES_ARG);
+
+ private static final Supplier<Set<JDiffClassDescription>> UNEXPECTED_CLASSES =
+ Suppliers.memoize(SignatureTest::loadUnexpectedClasses)::get;
@Override
- protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
- expectedApiFiles = getCommaSeparatedListOptional(instrumentationArgs, "expected-api-files");
- baseApiFiles = getCommaSeparatedListOptional(instrumentationArgs, "base-api-files");
- unexpectedApiFiles = getCommaSeparatedListOptional(instrumentationArgs, "unexpected-api-files");
- previousApiFiles = getCommaSeparatedListOptional(instrumentationArgs, "previous-api-files");
+ protected void initializeFromArgs(Bundle instrumentationArgs) {
+ String[] expectedApiFiles = EXPECTED_API_FILES.get();
+ String[] unexpectedApiFiles = UNEXPECTED_API_FILES.get();
if (expectedApiFiles.length + unexpectedApiFiles.length == 0) {
throw new IllegalStateException(
- "Expected at least one file to be specified in"
- + " 'expected-api-files' or 'unexpected-api-files'");
+ String.format("Expected at least one file to be specified in '%s' or '%s'",
+ EXPECTED_API_FILES_ARG, UNEXPECTED_API_FILES_ARG));
}
}
/**
- * Tests that the device's API matches the expected set defined in xml.
- * <p/>
- * Will check the entire API, and then report the complete list of failures
+ * Make sure that this APK cannot access any unexpected classes.
+ *
+ * <p>The set of unexpected classes may be empty, in which case this test does nothing.</p>
*/
@Test
- public void testSignature() {
+ public void testCannotAccessUnexpectedClasses() {
runWithTestResultObserver(mResultObserver -> {
- Set<JDiffClassDescription> unexpectedClasses = loadUnexpectedClasses();
+ Set<JDiffClassDescription> unexpectedClasses = UNEXPECTED_CLASSES.get();
for (JDiffClassDescription classDescription : unexpectedClasses) {
Class<?> unexpectedClass = findUnexpectedClass(classDescription, mClassProvider);
if (unexpectedClass != null) {
@@ -74,16 +81,52 @@
"Class should not be accessible to this APK");
}
}
+ });
+ }
+ /**
+ * Tests that the device's API matches the expected set defined in xml.
+ *
+ * <p>Will check the entire API, and then report the complete list of failures</p>
+ */
+ @Test
+ public void testRuntimeCompatibilityWithCurrentApi() {
+ runWithTestResultObserver(mResultObserver -> {
ApiComplianceChecker complianceChecker =
new ApiComplianceChecker(mResultObserver, mClassProvider);
// Load classes from any API files that form the base which the expected APIs extend.
loadBaseClasses(complianceChecker);
+
// Load classes from system API files and check for signature compliance.
+ String[] expectedApiFiles = EXPECTED_API_FILES.get();
+ Set<JDiffClassDescription> unexpectedClasses = UNEXPECTED_CLASSES.get();
checkClassesSignatureCompliance(complianceChecker, expectedApiFiles, unexpectedClasses,
false /* isPreviousApi */);
+
+ // After done parsing all expected API files, perform any deferred checks.
+ complianceChecker.checkDeferred();
+ });
+ }
+
+ /**
+ * Tests that the device's API matches the last few previously released api files.
+ *
+ * <p>Will check all the recently released api files, and then report the complete list of
+ * failures.</p>
+ */
+ @Test
+ public void testRuntimeCompatibilityWithPreviousApis() {
+ runWithTestResultObserver(mResultObserver -> {
+ ApiComplianceChecker complianceChecker =
+ new ApiComplianceChecker(mResultObserver, mClassProvider);
+
+ // Load classes from any API files that form the base which the expected APIs extend.
+ loadBaseClasses(complianceChecker);
+
// Load classes from previous API files and check for signature compliance.
+ String[] previousApiFiles = PREVIOUS_API_FILES.get();
+ Set<JDiffClassDescription> unexpectedClasses = UNEXPECTED_CLASSES.get();
checkClassesSignatureCompliance(complianceChecker, previousApiFiles, unexpectedClasses,
true /* isPreviousApi */);
@@ -105,9 +148,10 @@
}
}
- private Set<JDiffClassDescription> loadUnexpectedClasses() {
+ private static Set<JDiffClassDescription> loadUnexpectedClasses() {
ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
- return parseApiResourcesAsStream(apiDocumentParser, unexpectedApiFiles)
+ return retrieveApiResourcesAsStream(SignatureTest.class.getClassLoader(), UNEXPECTED_API_FILES.get())
+ .flatMap(apiDocumentParser::parseAsStream)
.collect(Collectors.toCollection(SignatureTest::newSetOfClassDescriptions));
}
@@ -115,12 +159,6 @@
return new TreeSet<>(Comparator.comparing(JDiffClassDescription::getAbsoluteClassName));
}
- private void loadBaseClasses(ApiComplianceChecker complianceChecker) {
- ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
- parseApiResourcesAsStream(apiDocumentParser, baseApiFiles)
- .forEach(complianceChecker::addBaseClass);
- }
-
private void checkClassesSignatureCompliance(ApiComplianceChecker complianceChecker,
String[] classes, Set<JDiffClassDescription> unexpectedClasses, boolean isPreviousApi) {
ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
@@ -129,5 +167,4 @@
.map(clazz -> clazz.setPreviousApiFlag(isPreviousApi))
.forEach(complianceChecker::checkSignatureCompliance);
}
-
}
diff --git a/tests/signature/lib/android/src/android/signature/cts/VirtualPath.java b/tests/signature/lib/android/src/android/signature/cts/VirtualPath.java
index 140dd6d..51b2ff0 100644
--- a/tests/signature/lib/android/src/android/signature/cts/VirtualPath.java
+++ b/tests/signature/lib/android/src/android/signature/cts/VirtualPath.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -55,6 +56,24 @@
public abstract InputStream newInputStream() throws IOException;
+ /**
+ * Override as abstract to force sub-classes to implement it.
+ */
+ @Override
+ public abstract int hashCode();
+
+ /**
+ * Override as abstract to force sub-classes to implement it.
+ */
+ @Override
+ public abstract boolean equals(Object obj);
+
+ /**
+ * Override as abstract to force sub-classes to implement it.
+ */
+ @Override
+ public abstract String toString();
+
public static class LocalFilePath extends VirtualPath {
private final String path;
@@ -76,6 +95,23 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LocalFilePath that = (LocalFilePath) o;
+ return path.equals(that.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(path);
+ }
+
+ @Override
public String toString() {
return path;
}
@@ -98,6 +134,24 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ZipEntryPath that = (ZipEntryPath) o;
+ return zip.getName().equals(that.zip.getName())
+ && entry.getName().equals(that.entry.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(zip.getName(), entry.getName());
+ }
+
+ @Override
public String toString() {
return "zip:file:" + zip.getName() + "!/" + entry.getName();
}
@@ -119,6 +173,23 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ResourcePath that = (ResourcePath) o;
+ return url.equals(that.url);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(url);
+ }
+
+ @Override
public String toString() {
return url.toExternalForm();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
index d1e08fc..6138f38 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
@@ -17,6 +17,8 @@
package android.bluetooth.cts;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED;
import static org.junit.Assert.assertEquals;
@@ -114,7 +116,7 @@
}
MockitoAnnotations.initMocks(this);
mExecutor = mContext.getMainExecutor();
- TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+ TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT,BLUETOOTH_PRIVILEGED,BLUETOOTH_SCAN);
mAdapter = TestUtils.getBluetoothAdapterOrDie();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
index 7c23b4d..aaba29f 100644
--- a/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
@@ -44,12 +44,11 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.PollingCheck;
-import com.android.compatibility.common.util.SystemUtil;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -69,14 +68,11 @@
private static final String GRANTED_PERMISSION_INTERACT_ACROSS_USERS =
"android.permission.INTERACT_ACROSS_USERS";
private static final String PERMISSION_REMOVE_TASKS = "android.permission.REMOVE_TASKS";
- private static final String PERMISSION_GET_TASKS = "android.permission.GET_TASKS";
private static final String PERMISSION_MANAGE_ACTIVITY_TASKS =
"android.permission.MANAGE_ACTIVITY_TASKS";
private static final String SIMPLE_APP_PACKAGE_NAME = "android.car.cts.builtin.apps.simple";
- private static final String SIMPLE_ACTIVITY_NAME = "SimpleActivity";
- private static final String START_SIMPLE_ACTIVITY_COMMAND = "am start -W -n "
- + SIMPLE_APP_PACKAGE_NAME + "/." + SIMPLE_ACTIVITY_NAME;
+ private static final String SIMPLE_ACTIVITY_NAME = SIMPLE_APP_PACKAGE_NAME + ".SimpleActivity";
private static final int OWNING_UID = UserHandle.ALL.getIdentifier();
private static final int MAX_NUM_TASKS = 1_000;
@@ -100,6 +96,13 @@
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private final Context mContext = mInstrumentation.getContext();
+ @Before
+ public void setUp() throws Exception {
+ // Home was launched in ActivityManagerTestBase#setUp, wait until it is stable,
+ // in order not to mix the event of its TaskView Activity with the TestActivity.
+ mWmState.waitForHomeActivityVisible();
+ }
+
@Test
public void testCheckComponentPermission() throws Exception {
// not requested from Manifest
@@ -183,20 +186,18 @@
public void testProcessObserverCallback() throws Exception {
// setup
ProcessObserverCallbackTestImpl callbackImpl = new ProcessObserverCallbackTestImpl();
- launchSimpleActivity();
// execute
try {
mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
- PERMISSION_SET_ACTIVITY_WATCHER);
+ PERMISSION_SET_ACTIVITY_WATCHER); // for registerProcessObserverCallback
ActivityManagerHelper.registerProcessObserverCallback(callbackImpl);
- ArrayList<Integer> appTasks = getAppTasks(SIMPLE_APP_PACKAGE_NAME);
- appTasks.forEach((taskId) -> ActivityManagerHelper.removeTask(taskId));
+ launchSimpleActivity();
// assert
- assertThat(callbackImpl.isProcessDiedObserved()).isTrue();
+ assertThat(callbackImpl.waitForForegroundActivitiesChanged()).isTrue();
} finally {
// teardown
ActivityManagerHelper.unregisterProcessObserverCallback(callbackImpl);
@@ -249,45 +250,27 @@
private static final class ProcessObserverCallbackTestImpl extends ProcessObserverCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
- private int mObservedPid = -1;
- private int mObservedUid = -1;
- private boolean mProcessDiedObserved;
-
+ // Use onForegroundActivitiesChanged(), because onProcessDied() can be called
+ // in very long time later even if the task was removed.
@Override
- public void onProcessDied(int pid, int uid) {
- mProcessDiedObserved = true;
- Log.d(TAG, "ProcessDied: pid " + pid + " uid " + uid);
+ public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
+ Log.d(TAG, "onForegroundActivitiesChanged: pid " + pid + " uid " + uid);
mLatch.countDown();
}
- public boolean isProcessDiedObserved() throws Exception {
- if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- throw new Exception("process died is not observed in " + TIMEOUT_MS + " ms)");
- }
- return mProcessDiedObserved;
+ public boolean waitForForegroundActivitiesChanged() throws Exception {
+ return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
}
private void launchSimpleActivity() {
- SystemUtil.runShellCommand(START_SIMPLE_ACTIVITY_COMMAND);
- }
-
- private ArrayList<Integer> getAppTasks(String pkgName) {
- ActivityManager am = mContext.getSystemService(ActivityManager.class);
-
- List<ActivityManager.RunningTaskInfo> runningTasks = am.getRunningTasks(MAX_NUM_TASKS);
- ArrayList<Integer> appTasks = new ArrayList<>();
- for (ActivityManager.RunningTaskInfo taskInfo : runningTasks) {
- String taskPackageName = taskInfo.baseActivity.getPackageName();
- int taskId = taskInfo.taskId;
- Log.d(TAG, "tasks package name: " + taskPackageName);
- if (taskPackageName.equals(pkgName)) {
- Log.d(TAG, "getAppTask(): adding " + SIMPLE_APP_PACKAGE_NAME + " task " + taskId);
- appTasks.add(taskId);
- }
- }
-
- return appTasks;
+ ComponentName simpleActivity = new ComponentName(
+ SIMPLE_APP_PACKAGE_NAME, SIMPLE_ACTIVITY_NAME);
+ Intent intent = new Intent()
+ .setComponent(simpleActivity)
+ .addFlags(FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent, /* options = */ null);
+ waitAndAssertTopResumedActivity(simpleActivity, DEFAULT_DISPLAY, "Activity isn't resumed");
}
private <T> T launchTestActivity(Class<T> type) {
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/UiAutomationTestBase.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/UiAutomationTestBase.kt
index eca3280..c961624 100644
--- a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/UiAutomationTestBase.kt
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/UiAutomationTestBase.kt
@@ -31,6 +31,7 @@
import android.content.Intent
import android.net.MacAddress
import android.os.Parcelable
+import android.os.SystemClock.sleep
import androidx.test.uiautomator.UiDevice
import org.junit.Assume
import org.junit.Assume.assumeFalse
@@ -100,7 +101,7 @@
} else {
confirmationUi.clickNegativeButtonMultipleDevices()
}
- }
+ }
protected fun test_userDismissed(
singleDevice: Boolean = false,
@@ -118,8 +119,24 @@
displayName: String?,
cancelAction: () -> Unit
) {
+ // Give the discovery service extra time to find the first match device before
+ // pressing the negative button for singleDevice && userRejected.
+ if (singleDevice) {
+ setDiscoveryTimeout(2.seconds)
+ }
+
sendRequestAndLaunchConfirmation(singleDevice, selfManaged, displayName)
+ if (singleDevice) {
+ // The discovery timeout is 2 sec, but let's wait for 3. So that we have enough
+ // time to wait until the dialog appeared.
+ sleep(3.seconds.inWholeMilliseconds)
+ }
+ // Test can stop here since there's no device found after discovery timeout.
+ assumeFalse(callback.invocations.contains(OnFailure(REASON_DISCOVERY_TIMEOUT)))
+ // Check callback invocations: There should be 0 invocation before any actions are made.
+ assertEmpty(callback.invocations)
+
callback.assertInvokedByActions {
cancelAction()
}
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandMultiUserTest.kt b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandMultiUserTest.kt
index 96fe027..cf9dde0 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandMultiUserTest.kt
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandMultiUserTest.kt
@@ -74,6 +74,15 @@
InstrumentationRegistry.getInstrumentation().getUiAutomation()
private var backgroundThread = HandlerThread("PackageManagerShellCommandMultiUserTest")
+
+ fun skipTheInstallType(installTypeString: String): Boolean {
+ if (installTypeString == "install-incremental" &&
+ !context.packageManager.hasSystemFeature(
+ PackageManager.FEATURE_INCREMENTAL_DELIVERY)) {
+ return true
+ }
+ return false
+ }
}
private lateinit var primaryUser: UserReference
@@ -130,6 +139,9 @@
"install-incremental"
) installTypeString: String
) {
+ if (skipTheInstallType(installTypeString)) {
+ return
+ }
val startTimeMillisForPrimaryUser = System.currentTimeMillis()
installPackageAsUser(TEST_HW5, primaryUser, installTypeString)
assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, primaryUser))
@@ -205,6 +217,9 @@
"install-incremental"
) installTypeString: String
) {
+ if (skipTheInstallType(installTypeString)) {
+ return
+ }
if (!backgroundThread.isAlive) {
backgroundThread.start()
}
@@ -264,6 +279,9 @@
"install-incremental"
) installTypeString: String
) {
+ if (skipTheInstallType(installTypeString)) {
+ return
+ }
installPackageAsUser(TEST_HW5, primaryUser, installTypeString)
assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, primaryUser))
assertFalse(isAppInstalledForUser(TEST_APP_PACKAGE, secondaryUser))
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualDeviceTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualDeviceTestCase.java
index b8d3f6f..ba192c4 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualDeviceTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualDeviceTestCase.java
@@ -16,8 +16,6 @@
package android.hardware.input.cts.tests;
-import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
-
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@@ -35,6 +33,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.Surface;
@@ -165,9 +164,10 @@
if (mVirtualDevice != null) {
mVirtualDevice.close();
}
- InstrumentationRegistry.getTargetContext().getSystemService(InputManager.class)
- .unregisterInputDeviceListener(mInputDeviceListener);
- disassociateCompanionDevice();
+ final Context context = InstrumentationRegistry.getTargetContext();
+ context.getSystemService(InputManager.class).unregisterInputDeviceListener(
+ mInputDeviceListener);
+ disassociateCompanionDevice(context.getPackageName());
}
}
@@ -181,13 +181,14 @@
private void associateCompanionDevice(String packageName) {
// Associate this package for user 0 with a zeroed-out MAC address (not used in this test)
SystemUtil.runShellCommand(
- String.format("cmd companiondevice associate 0 %s 00:00:00:00:00:00", packageName));
+ String.format("cmd companiondevice associate %d %s 00:00:00:00:00:00",
+ Process.myUserHandle().getIdentifier(), packageName));
}
- private void disassociateCompanionDevice() {
- SystemUtil.runShellCommand("cmd companiondevice disassociate 0 "
- + InstrumentationRegistry.getTargetContext().getPackageName()
- + " 00:00:00:00:00:00");
+ private void disassociateCompanionDevice(String packageName) {
+ SystemUtil.runShellCommand(
+ String.format("cmd companiondevice disassociate %d %s 00:00:00:00:00:00",
+ Process.myUserHandle().getIdentifier(), packageName));
}
private void tapActivityToFocus() {
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualKeyboardTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualKeyboardTest.java
index 741116b..1492aae 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualKeyboardTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualKeyboardTest.java
@@ -63,7 +63,7 @@
KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_A,
/* repeat= */ 0,
- /* metaState= */ KeyEvent.META_NUM_LOCK_ON,
+ /* metaState= */ 0,
/* deviceId= */ 0,
/* scancode= */ 0,
/* flags= */ 0,
@@ -74,7 +74,7 @@
KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_A,
/* repeat= */ 0,
- /* metaState= */ KeyEvent.META_NUM_LOCK_ON,
+ /* metaState= */ 0,
/* deviceId= */ 0,
/* scancode= */ 0,
/* flags= */ 0,
diff --git a/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java b/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java
index 541c68f..fc5d459 100644
--- a/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java
+++ b/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java
@@ -15,41 +15,25 @@
*/
package android.media.cts;
-import android.app.Activity;
-import android.content.Intent;
import android.media.cts.R;
-import android.os.Build;
+
+import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
-import android.view.Window;
-import android.view.WindowInsets;
-import android.view.WindowInsetsController;
import android.view.WindowManager;
-import androidx.test.filters.SdkSuppress;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
public class MediaStubActivity extends Activity {
private static final String TAG = "MediaStubActivity";
private SurfaceHolder mHolder;
private SurfaceHolder mHolder2;
- public static final String INTENT_EXTRA_NO_TITLE = "NoTitle";
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
- Intent intent = getIntent();
- if (intent.getBooleanExtra(INTENT_EXTRA_NO_TITLE, false)) {
- hideTitle();
- }
- }
setTurnScreenOn(true);
setShowWhenLocked(true);
@@ -80,26 +64,4 @@
public SurfaceHolder getSurfaceHolder2() {
return mHolder2;
}
-
- /** Note: Must be called from the thread used to create this activity. */
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
- public void hideSystemBars() {
- var surfaceV = (SurfaceView)findViewById(R.id.surface);
- WindowInsetsController windowInsetsController = surfaceV.getWindowInsetsController();
- if (windowInsetsController == null) {
- return;
- }
- // Configure the behavior of the hidden system bars
- windowInsetsController.setSystemBarsBehavior(
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
- // Hide both the status bar and the navigation bar
- windowInsetsController.hide(WindowInsets.Type.systemBars());
- }
-
- /** Note: Must be called before {@code setContentView}. */
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
- private void hideTitle() {
- getWindow().requestFeature(Window.FEATURE_NO_TITLE);
- }
-
}
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderPushBlankBuffersOnStopTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderPushBlankBuffersOnStopTest.java
deleted file mode 100644
index 394a779..0000000
--- a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderPushBlankBuffersOnStopTest.java
+++ /dev/null
@@ -1,256 +0,0 @@
-package android.media.decoder.cts;
-
-import android.content.Intent;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.media.cts.MediaHeavyPresubmitTest;
-import android.media.cts.MediaStubActivity;
-import android.media.cts.Preconditions;
-import android.media.MediaCodec;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.test.core.app.ActivityScenario;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.runner.screenshot.ScreenCapture;
-import androidx.test.runner.screenshot.Screenshot;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
-import org.junit.Test;
-import org.junit.Assert;
-import org.junit.Assume;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Class that contains tests for the "Push blank buffers on stop" decoder feature.
- * <br>
- * In order to detect that a blank buffer has been pushed to the {@code Surface}that the codec works
- * on, we take a fullscreen screenshot before and after the call to {@code MediaCodec#stop}. This
- * workaround appears necessary at the time of writing because the usual APIs to extract the content
- * of a native {@code Surface} (such as {@code PixelCopy} or {@code ImageReader}) appear to fail for
- * this frame specifically.
- * <br>
- * This test class is inspired from the {@link DecoderTest} test class, but with specific setup code
- * to ensure the activity is launched in immersive mode and its title is removed.
- */
-@MediaHeavyPresubmitTest
-public class DecoderPushBlankBuffersOnStopTest {
- private static final String TAG = "DecoderPushBlankBufferOnStopTest";
- private static final String mInpPrefix = WorkDir.getMediaDirString();
-
- /**
- * Retrieve a file descriptor to a test resource from its file name.
- * @param res Name from a resource in the media assets
- */
- private static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
- throws FileNotFoundException {
- final String mediaDirPath = WorkDir.getMediaDirString();
- File mediaFile = new File(mediaDirPath + res);
- Preconditions.assertTestFileExists(mediaDirPath + res);
- ParcelFileDescriptor parcelFD =
- ParcelFileDescriptor.open(mediaFile, ParcelFileDescriptor.MODE_READ_ONLY);
- return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
- }
-
- private static boolean isUniformlyBlank(Bitmap bitmap) {
- final var color = new Color(); // Defaults to opaque black in sRGB
- final int width = bitmap.getWidth();
- final int height = bitmap.getHeight();
- // Check a subset of pixels against the first pixel of the image.
- // This is not strictly sufficient, but probably good enough and much more efficient.
- for (int y = 0; y < height; y+=4) {
- for (int x = 0; x < width; x+=4) {
- if (color.toArgb() != bitmap.getColor(x, y).toArgb()) {
- return false;
- }
- }
- }
- return true;
- }
-
- private void testPushBlankBuffersOnStop(String testVideo) throws Exception {
- // Configure the test activity to hide its title
- final var noTitle = new Intent(ApplicationProvider.getApplicationContext(),
- MediaStubActivity.class);
- noTitle.putExtra(MediaStubActivity.INTENT_EXTRA_NO_TITLE, true);
- try(ActivityScenario<MediaStubActivity> scenario = ActivityScenario.launch(noTitle)) {
- final var surface = new AtomicReference<Surface>();
- scenario.onActivity(activity -> {
- surface.set(activity.getSurfaceHolder().getSurface());
- });
-
- // Setup media extraction
- final AssetFileDescriptor fd = getAssetFileDescriptorFor(testVideo);
- final var extractor = new MediaExtractor();
- extractor.setDataSource(fd);
- fd.close();
- MediaFormat format = null;
- int trackIndex = -1;
- for (int i = 0; i < extractor.getTrackCount(); i++) {
- format = extractor.getTrackFormat(i);
- if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
- trackIndex = i;
- break;
- }
- }
- Assert.assertTrue("No video track was found", trackIndex >= 0);
- extractor.selectTrack(trackIndex);
- // Enable PUSH_BLANK_BUFFERS_ON_STOP
- format.setInteger(MediaFormat.KEY_PUSH_BLANK_BUFFERS_ON_STOP, 1);
-
- // Setup video codec
- final var mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
- final String decoderName = mcl.findDecoderForFormat(format);
- Assume.assumeNotNull(String.format("No decoder for %s", format), format);
- final MediaCodec decoder = MediaCodec.createByCodecName(decoderName);
- // Boolean set from the decoding thread to signal that a frame has been decoded
- final var displayedFrame = new AtomicBoolean(false);
- // Lock used for thread synchronization
- final Lock lock = new ReentrantLock();
- // Condition that signals the decoding thread has made enough progress
- final Condition processingDone = lock.newCondition();
- final var cb = new MediaCodec.Callback() {
- /** Queue input buffers until one buffer has been decoded. */
- @Override
- public void onInputBufferAvailable(MediaCodec codec, int index) {
- lock.lock();
- try {
- // Stop queuing frames once a frame has been displayed
- if (displayedFrame.get()) {
- return;
- }
- } finally {
- lock.unlock();
- }
-
- ByteBuffer inputBuffer = codec.getInputBuffer(index);
- int sampleSize = extractor.readSampleData(inputBuffer,
- 0 /* offset */);
- if (sampleSize < 0) {
- codec.queueInputBuffer(index, 0 /* offset */, 0 /* sampleSize */,
- 0 /* presentationTimeUs */,
- MediaCodec.BUFFER_FLAG_END_OF_STREAM);
- return;
- }
- final long presentationTimeMs = System.currentTimeMillis();
- codec.queueInputBuffer(index, 0 /* offset */, sampleSize,
- presentationTimeMs * 1000, 0 /* flags */);
- extractor.advance();
- }
-
- /** Render the output buffer and signal that the processing is done. */
- @Override
- public void onOutputBufferAvailable(MediaCodec codec, int index,
- MediaCodec.BufferInfo info) {
- lock.lock();
- try {
- // Stop dequeuing frames once a frame has been displayed
- if (displayedFrame.get()) {
- return;
- }
- } finally {
- lock.unlock();
- }
- if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- return;
- }
- codec.releaseOutputBuffer(index, true);
- }
-
- /**
- * Check if the error is transient. If it is, ignore it, otherwise signal end of
- * processing.
- */
- @Override
- public void onError(MediaCodec codec, MediaCodec.CodecException e) {
- if (e.isTransient()) {
- return;
- }
- lock.lock();
- try {
- processingDone.signal();
- } finally {
- lock.unlock();
- }
- }
-
- /** Ignore format changed events. */
- @Override
- public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { }
- };
- final var onFrameRenderedListener = new MediaCodec.OnFrameRenderedListener() {
- @Override
- public void onFrameRendered(MediaCodec codec, long presentationTimeUs,
- long nanoTime) {
- lock.lock();
- try {
- displayedFrame.set(true);
- processingDone.signal();
- } finally {
- lock.unlock();
- }
- }
- };
- decoder.setCallback(cb);
- decoder.setOnFrameRenderedListener(onFrameRenderedListener, null /* handler */);
- scenario.onActivity(activity -> activity.hideSystemBars());
- decoder.configure(format, surface.get(), null /* MediaCrypto */, 0 /* flags */);
- // Start playback
- decoder.start();
- final long startTime = System.currentTimeMillis();
- // Wait until the codec has decoded a frame, or a timeout.
- lock.lock();
- try {
- long startTimeMs = System.currentTimeMillis();
- long timeoutMs = 1000;
- while ((System.currentTimeMillis() < startTimeMs + timeoutMs) &&
- !displayedFrame.get()) {
- processingDone.await(timeoutMs, TimeUnit.MILLISECONDS);
- }
- } finally {
- lock.unlock();
- }
- Assert.assertTrue("Could not render any frame.", displayedFrame.get());
- final ScreenCapture captureBeforeStop = Screenshot.capture();
- Assert.assertFalse("Frame is blank before stop.", isUniformlyBlank(
- captureBeforeStop.getBitmap()));
- decoder.stop();
- final ScreenCapture captureAfterStop = Screenshot.capture();
- Assert.assertTrue("Frame is not blank after stop.", isUniformlyBlank(
- captureAfterStop.getBitmap()));
- decoder.release();
- extractor.release();
- }
- }
-
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
- @Test
- public void testPushBlankBuffersOnStopVp9() throws Exception {
- testPushBlankBuffersOnStop(
- "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
- }
-
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
- @Test
- public void testPushBlankBuffersOnStopAvc() throws Exception {
- testPushBlankBuffersOnStop(
- "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
- }
-}
diff --git a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
index e74efca..ac1e9df 100644
--- a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
+++ b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
@@ -18,8 +18,10 @@
import android.app.AppOpsManager
import android.app.KeyguardManager
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.content.res.Resources.NotFoundException
import android.hardware.SensorPrivacyManager
import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener
import android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE
@@ -316,6 +318,18 @@
Assume.assumeTrue(packageManager
.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN))
+// TODO use actual test api when it can be added
+// Assume.assumeTrue(callWithShellPermissionIdentity { spm.requiresAuthentication() })
+ val packageContext: Context = context.createPackageContext("android", 0)
+ try {
+ Assume.assumeTrue(packageContext.resources.getBoolean(packageContext.resources
+ .getIdentifier("config_sensorPrivacyRequiresAuthentication", "bool", "android"))
+ )
+ } catch (e: NotFoundException) {
+ // Since by default we want authentication to be required we
+ // continue the test if the OEM has removed this resource.
+ }
+
setSensor(false)
assertFalse(isSensorPrivacyEnabled())
runWhileLocked {
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java
index dbf81f2..fbcee8a 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java
@@ -18,9 +18,13 @@
import static androidx.test.InstrumentationRegistry.getContext;
+import static com.android.compatibility.common.util.PropertyUtil.getVendorApiLevel;
+
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.content.pm.PackageManager;
+import android.os.Build;
import org.junit.Before;
import org.junit.Test;
@@ -34,6 +38,7 @@
@Before
public void setUp() {
+ assumeTrue(getVendorApiLevel() > Build.VERSION_CODES.S);
mPackageManager = getContext().getPackageManager();
}
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/CreateVirtualDisplayTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/CreateVirtualDisplayTest.java
index dcba1f0..2228ed1 100644
--- a/tests/tests/virtualdevice/src/android/virtualdevice/cts/CreateVirtualDisplayTest.java
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/CreateVirtualDisplayTest.java
@@ -52,6 +52,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = " cannot be accessed by instant apps")
public class CreateVirtualDisplayTest {
@@ -175,4 +177,32 @@
/* executor= */ null,
mVirtualDisplayCallback));
}
+
+ @Test
+ public void createVirtualDisplay_createAndRemoveSeveralDisplays() throws InterruptedException {
+ mVirtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ DEFAULT_VIRTUAL_DEVICE_PARAMS);
+ ArrayList<VirtualDisplay> displays = new ArrayList<>();
+ for (int i = 0; i < 5; i++) {
+ displays.add(mVirtualDevice.createVirtualDisplay(
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 240,
+ /* surface= */ null,
+ /* flags= */ 0,
+ Runnable::run,
+ mVirtualDisplayCallback));
+ // TODO(b/230544802) - for now, use sleep to avoid deadlock when creating multiple
+ // displays in quick succession
+ Thread.sleep(50);
+ }
+
+ // Releasing several displays in quick succession should not cause deadlock
+ while (!displays.isEmpty()) {
+ int index = displays.size() - 1;
+ displays.remove(index).release();
+ }
+ }
}
diff --git a/tests/tests/voiceinteraction/TEST_MAPPING b/tests/tests/voiceinteraction/TEST_MAPPING
index 7c84b9c..b22a259 100644
--- a/tests/tests/voiceinteraction/TEST_MAPPING
+++ b/tests/tests/voiceinteraction/TEST_MAPPING
@@ -5,19 +5,6 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- // TODO(b/225076204): Remove the following four test cases after fixing the test fail.
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromDsp_success"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success"
}
]
}
diff --git a/tests/translation/AndroidManifest.xml b/tests/translation/AndroidManifest.xml
index aab6515..80ac551 100644
--- a/tests/translation/AndroidManifest.xml
+++ b/tests/translation/AndroidManifest.xml
@@ -22,10 +22,16 @@
<application android:label="Translation TestCase">
<uses-library android:name="android.test.runner"/>
+ <!--
+ EmptyActivity uses a transparent theme so that SimpleActivity below it can have its views
+ translated. See UiTranslationManagerTest#testTranslationAfterStartActivityOnSameTask.
+ -->
<activity android:name=".EmptyActivity"
android:label="EmptyActivity"
- android:exported="true">
+ android:exported="true"
+ android:theme="@style/TransparentTheme">
</activity>
+
<activity android:name=".SimpleActivity"
android:label="SimpleActivity"
android:exported="true">
diff --git a/tests/translation/res/values/styles.xml b/tests/translation/res/values/styles.xml
new file mode 100644
index 0000000..3dd2eb6
--- /dev/null
+++ b/tests/translation/res/values/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <style name="TransparentTheme">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/tests/translation/src/android/translation/cts/Helper.java b/tests/translation/src/android/translation/cts/Helper.java
index 041b055..3474979 100644
--- a/tests/translation/src/android/translation/cts/Helper.java
+++ b/tests/translation/src/android/translation/cts/Helper.java
@@ -67,6 +67,8 @@
public static final String CUSTOM_TRANSLATION_ID_MY_TAG = "myTag";
public static final String LOCAL_TEST_FILES_DIR = "/sdcard/CtsTranslationTestCases";
+ public static final int TEMP_SERVICE_DURATION_MS = 30_000;
+
private static final String LOG_TAG = "log.tag.UiTranslation";
/**
@@ -77,7 +79,8 @@
public static void setTemporaryTranslationService(String service) {
Log.d(TAG, "Setting translation service to " + service);
final int userId = UserHandle.myUserId();
- runShellCommand("cmd translation set temporary-service %d %s 12000", userId, service);
+ runShellCommand("cmd translation set temporary-service %d %s %d", userId, service,
+ TEMP_SERVICE_DURATION_MS);
}
/**
@@ -97,7 +100,8 @@
public static void setTemporaryContentCaptureService(String service) {
Log.d(TAG, "Setting content capture service to " + service);
final int userId = UserHandle.myUserId();
- runShellCommand("cmd content_capture set temporary-service %d %s 12000", userId, service);
+ runShellCommand("cmd content_capture set temporary-service %d %s %d", userId, service,
+ TEMP_SERVICE_DURATION_MS);
}
/**
diff --git a/tests/translation/src/android/translation/cts/TranslationManagerTest.java b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
index 67fa455..551e20ce 100644
--- a/tests/translation/src/android/translation/cts/TranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
@@ -245,11 +245,6 @@
translator.destroy();
assertThat(translator.isDestroyed()).isTrue();
- try {
- mServiceWatcher.waitOnDisconnected();
- } catch (InterruptedException e) {
- Log.w(TAG, "Exception waiting for onDisconnected");
- }
// Wait for translation to finish
translationLatch.await();
@@ -326,11 +321,6 @@
translator.destroy();
assertThat(translator.isDestroyed()).isTrue();
- try {
- mServiceWatcher.waitOnDisconnected();
- } catch (InterruptedException e) {
- Log.w(TAG, "Exception waiting for onDisconnected");
- }
// Wait for translation to finish
translationLatch.await();
@@ -415,11 +405,6 @@
translator.destroy();
assertThat(translator.isDestroyed()).isTrue();
- try {
- mServiceWatcher.waitOnDisconnected();
- } catch (InterruptedException e) {
- Log.w(TAG, "Exception waiting for onDisconnected");
- }
}
@Test
diff --git a/tools/cts-tradefed/etc/cts-tradefed b/tools/cts-tradefed/etc/cts-tradefed
index b9d5547..21b36ca 100755
--- a/tools/cts-tradefed/etc/cts-tradefed
+++ b/tools/cts-tradefed/etc/cts-tradefed
@@ -112,7 +112,7 @@
fi
# include any host-side test jars
-for j in ${CTS_ROOT}/android-cts/testcases/*.jar; do
+for j in $(find ${CTS_ROOT}/android-cts/testcases -name '*.jar'); do
JAR_PATH=${JAR_PATH}:$j
done