Merge "Don't verify insets for floating windowing mode" into tm-dev
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 803e5f2..e55d9f6 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -101,7 +101,8 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<!-- Needed for sensor tests -->
- <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
+ <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" android:maxSdkVersion="32" />
+ <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<!-- Needed for Wi-Fi Direct tests from T -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
@@ -2774,6 +2775,19 @@
android:value="android.hardware.type.automotive"/>
<meta-data android:name="display_mode"
android:value="single_display_mode" />
+ <meta-data android:name="ApiTest" android:value="android.hardware.Camera#getParameters|
+ android.hardware.Camera#setParameters|
+ android.hardware.Camera#setDisplayOrientation|
+ android.hardware.Camera#setPreviewCallback|
+ android.hardware.Camera#stopPreview|
+ android.hardware.Camera#release|
+ android.hardware.Camera#setPreviewTexture|
+ android.hardware.Camera#startPreview|
+ android.hardware.Camera.Parameters#setPreviewFormat|
+ android.hardware.Camera.Parameters#setPreviewSize|
+ android.hardware.Camera.Parameters#getSupportedPreviewFormats|
+ android.hardware.Camera.Parameters#getSupportedPreviewSizes|
+ android.hardware.Camera.PreviewCallback#onPreviewFrame" />
</activity>
<activity android:name=".camera.intents.CameraIntentsActivity"
@@ -4092,6 +4106,33 @@
android:value="7.4.3/C-7-4" />
</activity>
+ <!-- CTS Verifier Nan Precision and Bias Test Screen
+ test category : wifi_nan
+ test parent : PresenceTestActivity
+ -->
+ <activity
+ android:name=".presence.NanPrecisionAndBiasTestActivity"
+ android:configChanges="keyboardHidden|orientation|screenSize"
+ android:exported="true"
+ android:label="@string/nan_precision_and_bias" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+
+ <meta-data
+ android:name="test_category"
+ android:value="@string/wifi_nan" />
+ <meta-data
+ android:name="test_parent"
+ android:value="com.android.cts.verifier.presence.PresenceTestActivity" />
+ <meta-data android:name="display_mode"
+ android:value="single_display_mode" />
+ <meta-data android:name="CddTest"
+ android:value="7.4.2.5/H-1-1,7.4.2.5/H-1-2" />
+ </activity>
+
<activity-alias
android:name=".CtsVerifierActivity"
android:label="@string/app_name"
diff --git a/apps/CtsVerifier/res/layout/nan_precision_and_bias.xml b/apps/CtsVerifier/res/layout/nan_precision_and_bias.xml
new file mode 100644
index 0000000..9e5782e
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/nan_precision_and_bias.xml
@@ -0,0 +1,72 @@
+<?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.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ style="@style/RootLayoutPadding">
+ <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView android:text="@string/nan_precision_and_bias_instruction"
+ android:id="@+id/nan_precision_and_bias_instruction"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scrollbars="vertical"/>
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <EditText android:id="@+id/distance_range_1m_gt"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="number"
+ android:hint="@string/report_distance_range_1m_gt"/>
+ <EditText android:id="@+id/distance_range_3m_gt"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="number"
+ android:hint="@string/report_distance_range_3m_gt"/>
+ <EditText android:id="@+id/distance_range_5m_gt"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="number"
+ android:hint="@string/report_distance_range_5m_gt"/>
+ <EditText android:id="@+id/bias_meters"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="numberDecimal"
+ android:hint="@string/report_bias_meters"/>
+ <EditText android:id="@+id/slope"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="numberDecimal"
+ android:hint="@string/report_slope"/>
+ <EditText android:id="@+id/reference_device"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:hint="@string/report_reference_device"/>
+ </LinearLayout>
+
+ <include android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ layout="@layout/pass_fail_buttons"/>
+ </LinearLayout>
+ </ScrollView>
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 44e529e..213bc55 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -6462,13 +6462,12 @@
<string name="uwb_short_range_instruction">
1. Take 1000 measurements with DUT being 10cm apart from the reference device
\n2. Sort the values.
- \n3. Report the median (500th value) in CTS-V (must be within 8cm - 12cm to pass)
+ \n3. Report the median (500th value) (must be within 8cm - 12cm to pass).
\n4. Report the reference device used.
</string>
<string name="report_distance_median_cm">Report Median (cm)</string>
<string name="report_reference_device">Report Reference Device</string>
<string name="uwb_not_supported">Uwb is not supported on device. Finishing activity. </string>
-
<string name="ble">BLE</string>
<!-- Strings for BLE RSSI Precision Test -->
@@ -6509,4 +6508,21 @@
\n3. Report the median (500th value); must be within [-57, -63] dBm to pass
\n4. Report the reference device used
</string>
+ <string name="nan_precision_and_bias_instruction">
+ 1. Take 1000 ranging measurements at each of the ground truth points of 1m, 3m, and 5m. The Wifinanscan in Play Store is recommended for data collection.
+ \n2. For each point:
+ \n\t\ta. Compute and report the range (Range = 975th measurement - 25th measurement).
+ \n\t\tb. Range must be less than 2m for test to pass.
+ \n3. Using the same measurements collected in step 1, compute a least squares regression line.
+ \n4. Report the bias and slope.
+ \n5. Bias must be 0+/- 0.25m and slope must be 1+/- 0.05m for tests to pass.
+ \n6. Report reference device used. All fields must be filled before test can be passed.
+ </string>
+ <string name="report_distance_range_1m_gt">Report Measurement Range at 1m</string>
+ <string name="report_distance_range_3m_gt">Report Measurement Range at 3m</string>
+ <string name="report_distance_range_5m_gt">Report Measurement Range at 5m</string>
+ <string name="report_bias_meters">Report Bias (meters)</string>
+ <string name="report_slope">Report Slope</string>
+ <string name="nan_precision_and_bias">Nan Precision and Bias Test</string>
+ <string name="wifi_nan">WiFi NAN</string>
</resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/DeviceFeatureChecker.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/DeviceFeatureChecker.java
index 285aa67..256fe3a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/presence/DeviceFeatureChecker.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/DeviceFeatureChecker.java
@@ -19,13 +19,14 @@
import android.util.Log;
import android.view.View;
import android.widget.Toast;
+
/**
* Checks if a device supports a hardware feature needed for a test, and passes the test
* automatically otherwise.
*/
public class DeviceFeatureChecker {
- /**
- * Checks if a feature is supported.
+
+ /** Checks if a feature is supported.
*
* @param feature must be a string defined in PackageManager
*/
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/NanPrecisionAndBiasTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/NanPrecisionAndBiasTestActivity.java
new file mode 100644
index 0000000..80f6985
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/NanPrecisionAndBiasTestActivity.java
@@ -0,0 +1,197 @@
+/*
+ * 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 com.android.cts.verifier.presence;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.Editable;
+import android.util.Log;
+import android.widget.EditText;
+
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Activity for testing that NAN measurements are within the acceptable range and fits a trend line
+ * that has a bias and slope within the expected range
+ * range.
+ */
+public class NanPrecisionAndBiasTestActivity extends PassFailButtons.Activity {
+ private static final String TAG = NanPrecisionAndBiasTestActivity.class.getName();
+
+ // Report log schema
+ private static final String KEY_MEASUREMENT_RANGE_1M = "measurement_range_1m";
+ private static final String KEY_MEASUREMENT_RANGE_3M = "measurement_range_3m";
+ private static final String KEY_MEASUREMENT_RANGE_5M = "measurement_range_5m";
+ private static final String KEY_BIAS_METERS = "bias_meters";
+ private static final String KEY_SLOPE_METERS = "slope_meters";
+ private static final String KEY_REFERENCE_DEVICE = "reference_device";
+
+ // Thresholds
+ private static final int MAX_DISTANCE_RANGE_METERS = 2;
+ private static final double MIN_BIAS_METERS = -0.25;
+ private static final double MAX_BIAS_METERS = 0.25;
+ private static final double MIN_SLOPE = 0.95;
+ private static final double MAX_SLOPE = 1.05;
+
+ private EditText mMeasurementRange1mGt;
+ private EditText mMeasurementRange3mGt;
+ private EditText mMeasurementRange5mGt;
+ private EditText mBiasInput;
+ private EditText mSlopeInput;
+ private EditText mReferenceDeviceInput;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.nan_precision_and_bias);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+
+ DeviceFeatureChecker.checkFeatureSupported(this, getPassButton(),
+ PackageManager.FEATURE_WIFI_AWARE);
+
+ mMeasurementRange1mGt = (EditText) findViewById(R.id.distance_range_1m_gt);
+ mMeasurementRange3mGt = (EditText) findViewById(R.id.distance_range_3m_gt);
+ mMeasurementRange5mGt = (EditText) findViewById(R.id.distance_range_5m_gt);
+ mBiasInput = (EditText) findViewById(R.id.bias_meters);
+ mSlopeInput = (EditText) findViewById(R.id.slope);
+ mReferenceDeviceInput = (EditText) findViewById(R.id.reference_device);
+
+ mMeasurementRange1mGt.addTextChangedListener(
+ InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+ mMeasurementRange3mGt.addTextChangedListener(
+ InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+ mMeasurementRange5mGt.addTextChangedListener(
+ InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+ mBiasInput.addTextChangedListener(
+ InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+ mSlopeInput.addTextChangedListener(
+ InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+ mReferenceDeviceInput.addTextChangedListener(
+ InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+ }
+
+ private void checkTestInputs() {
+ getPassButton().setEnabled(checkMeasurementRangeInput()
+ && checkBiasInput() && checkSlopeInput()
+ && checkReferenceDeviceInput());
+ }
+
+ private boolean checkMeasurementRangeInput() {
+ String measurementRangeInput1mGt = mMeasurementRange1mGt.getText().toString();
+ String measurementRangeInput3mGt = mMeasurementRange3mGt.getText().toString();
+ String measurementRangeInput5mGt = mMeasurementRange1mGt.getText().toString();
+ List<String> measurementRangeList = Arrays.asList(measurementRangeInput1mGt,
+ measurementRangeInput3mGt, measurementRangeInput5mGt);
+
+ for (String input : measurementRangeList) {
+ if (input.isEmpty()) {
+ // Distance range must be inputted for all fields so fail early otherwise
+ return false;
+ }
+ int distanceRange = Integer.parseInt(input);
+ if (distanceRange > MAX_DISTANCE_RANGE_METERS) {
+ // All inputs must be in acceptable range so fail early otherwise
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean checkBiasInput() {
+ String biasInput = mBiasInput.getText().toString();
+
+ if (!biasInput.isEmpty()) {
+ double bias = Double.parseDouble(biasInput);
+ // Bias must be inputted and within acceptable range before test can be passed.
+ return bias >= MIN_BIAS_METERS && bias <= MAX_BIAS_METERS;
+ }
+ return false;
+ }
+
+ private boolean checkSlopeInput() {
+ String slopeInput = mSlopeInput.getText().toString();
+
+ if (!slopeInput.isEmpty()) {
+ double slope = Double.parseDouble(slopeInput);
+ // Slope must be inputted and within acceptable range before test can be passed.
+ return slope >= MIN_SLOPE && slope <= MAX_SLOPE;
+ }
+ return false;
+ }
+
+ private boolean checkReferenceDeviceInput() {
+ // Reference device used must be inputted before test can be passed.
+ return !mReferenceDeviceInput.getText().toString().isEmpty();
+ }
+
+ @Override
+ public void recordTestResults() {
+ String measurementRange1mGt = mMeasurementRange1mGt.getText().toString();
+ String measurementRange3mGt = mMeasurementRange3mGt.getText().toString();
+ String measurementRange5mGt = mMeasurementRange5mGt.getText().toString();
+ String bias = mBiasInput.getText().toString();
+ String slope = mSlopeInput.getText().toString();
+ String referenceDevice = mReferenceDeviceInput.getText().toString();
+
+ if (!measurementRange1mGt.isEmpty()) {
+ Log.i(TAG, "NAN Measurement Range at 1m: " + measurementRange1mGt);
+ getReportLog().addValue(KEY_MEASUREMENT_RANGE_1M,
+ Integer.parseInt(measurementRange1mGt),
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ if (!measurementRange3mGt.isEmpty()) {
+ Log.i(TAG, "NAN Measurement Range at 3m: " + measurementRange3mGt);
+ getReportLog().addValue(KEY_MEASUREMENT_RANGE_3M,
+ Integer.parseInt(measurementRange1mGt),
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ if (!measurementRange5mGt.isEmpty()) {
+ Log.i(TAG, "NAN Measurement Range at 5m: " + measurementRange5mGt);
+ getReportLog().addValue(KEY_MEASUREMENT_RANGE_5M,
+ Integer.parseInt(measurementRange1mGt),
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ if (!bias.isEmpty()) {
+ Log.i(TAG, "NAN bias: " + bias);
+ getReportLog().addValue(KEY_BIAS_METERS, Double.parseDouble(bias),
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ if (!slope.isEmpty()) {
+ Log.i(TAG, "NAN slope: " + slope);
+ getReportLog().addValue(KEY_SLOPE_METERS, Double.parseDouble(slope),
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ if (!referenceDevice.isEmpty()) {
+ Log.i(TAG, "NAN Reference Device: " + referenceDevice);
+ getReportLog().addValue(KEY_REFERENCE_DEVICE, referenceDevice,
+ ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+ getReportLog().submit();
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbPrecisionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbPrecisionActivity.java
index a9ef722..2d67640 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbPrecisionActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbPrecisionActivity.java
@@ -14,15 +14,18 @@
* limitations under the License.
*/
package com.android.cts.verifier.presence;
+
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.Editable;
import android.util.Log;
import android.widget.EditText;
+
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
+
/**
* Activity for testing that UWB distance and angle of arrival measurements are within the right
* range.
@@ -36,17 +39,21 @@
// Thresholds
private static final int MAX_DISTANCE_RANGE_CM = 10;
private static final int MAX_ANGLE_OF_ARRIVAL_RANGE_DEGREES = 5;
+
private EditText mDistanceRangeInput;
private EditText mAoaRangeInput;
private EditText mReferenceDeviceInput;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.uwb_precision);
setPassFailButtonClickListeners();
getPassButton().setEnabled(false);
+
DeviceFeatureChecker.checkFeatureSupported(this, getPassButton(),
PackageManager.FEATURE_UWB);
+
mDistanceRangeInput = (EditText) findViewById(R.id.distance_range_cm);
mAoaRangeInput = (EditText) findViewById(R.id.aoa_range_degrees);
mReferenceDeviceInput = (EditText) findViewById(R.id.reference_device);
@@ -57,33 +64,38 @@
mReferenceDeviceInput.addTextChangedListener(
InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
}
+
private void checkTestInputs() {
getPassButton().setEnabled(
checkDistanceRangeInput() && checkAoaRangeInput() && checkReferenceDeviceInput());
}
+
private boolean checkDistanceRangeInput() {
String distanceRangeInput = mDistanceRangeInput.getText().toString();
if (!distanceRangeInput.isEmpty()) {
int distanceRange = Integer.parseInt(distanceRangeInput);
// Distance range must be inputted and within acceptable range before test can be
// passed.
- return distanceRange < MAX_DISTANCE_RANGE_CM;
+ return distanceRange <= MAX_DISTANCE_RANGE_CM;
}
return false;
}
+
private boolean checkAoaRangeInput() {
String aoaRangeInput = mAoaRangeInput.getText().toString();
if (!aoaRangeInput.isEmpty()) {
int aoaRange = Integer.parseInt(aoaRangeInput);
// Aoa range must be within acceptable range before test can be passed.
- return aoaRange < MAX_ANGLE_OF_ARRIVAL_RANGE_DEGREES;
+ return aoaRange <= MAX_ANGLE_OF_ARRIVAL_RANGE_DEGREES;
}
return true;
}
+
private boolean checkReferenceDeviceInput() {
// Reference device must be inputted before test can be passed.
return !mReferenceDeviceInput.getText().toString().isEmpty();
}
+
@Override
public void recordTestResults() {
String distanceRange = mDistanceRangeInput.getText().toString();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbShortRangeActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbShortRangeActivity.java
index ef6ccc8..8f5e8d8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbShortRangeActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbShortRangeActivity.java
@@ -14,15 +14,18 @@
* limitations under the License.
*/
package com.android.cts.verifier.presence;
+
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.Editable;
import android.util.Log;
import android.widget.EditText;
+
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
+
/**
* Activity for testing that UWB distance measurements are within the acceptable median.
*/
@@ -36,14 +39,17 @@
private static final int MAX_MEDIAN = 12;
private EditText mMedianInput;
private EditText mReferenceDeviceInput;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.uwb_short_range);
setPassFailButtonClickListeners();
getPassButton().setEnabled(false);
+
DeviceFeatureChecker.checkFeatureSupported(this, getPassButton(),
PackageManager.FEATURE_UWB);
+
mMedianInput = (EditText) findViewById(R.id.distance_median_cm);
mReferenceDeviceInput = (EditText) findViewById(R.id.reference_device);
mMedianInput.addTextChangedListener(
@@ -51,9 +57,11 @@
mReferenceDeviceInput.addTextChangedListener(
InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
}
+
private void checkTestInputs() {
getPassButton().setEnabled(checkMedianInput() && checkReferenceDeviceInput());
}
+
private boolean checkMedianInput() {
String medianInput = mMedianInput.getText().toString();
if (!medianInput.isEmpty()) {
@@ -62,9 +70,11 @@
}
return false;
}
+
private boolean checkReferenceDeviceInput() {
return !mReferenceDeviceInput.getText().toString().isEmpty();
}
+
@Override
public void recordTestResults() {
String medianInput = mMedianInput.getText().toString();
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
index 707e1de..0f12974 100644
--- a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
@@ -17,16 +17,17 @@
package com.android.bedstead.harrier.policies;
import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
/**
* Policy for testing preferential network service.
+ * Behavior is undefined when applied by profile owner on full users.
* See {@code DevicePolicyManager#setPreferentialNetworkServiceEnabled(boolean)} for more detail.
*/
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER_PROFILE
| APPLIES_TO_OWN_USER)
public final class PreferentialNetworkService {
}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
index 8cdb1b0..045b625 100644
--- a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
@@ -19,6 +19,7 @@
import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PARENT_INSTANCE_OF_COPE_PROFILE_OWNER_PROFILE;
import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_GLOBALLY;
import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
@@ -29,7 +30,8 @@
* <p>Users of this policy are
* {@code DevicePolicyManager#setScreenCaptureDisabled(ComponentName, boolean)}.
*/
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER
- | APPLIED_BY_PARENT_INSTANCE_OF_COPE_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER)
+@EnterprisePolicy(dpc = {
+ APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PARENT_INSTANCE_OF_COPE_PROFILE_OWNER_PROFILE
+ | APPLIES_GLOBALLY, APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
public final class ScreenCaptureDisabled {
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java
index ce73f43..687a74c 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java
@@ -17,12 +17,15 @@
package com.android.bedstead.nene.devicepolicy;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
import static com.android.compatibility.common.util.enterprise.DeviceAdminReceiverUtils.ACTION_DISABLE_SELF;
import static com.google.common.truth.Truth.assertThat;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
@@ -60,6 +63,31 @@
super(user, pkg, componentName);
}
+ /** Returns whether the current profile is organization owned. */
+ @TargetApi(R)
+ public boolean isOrganizationOwned() {
+ if (!Versions.meetsMinimumSdkVersionRequirement(R)) {
+ return false;
+ }
+
+ DevicePolicyManager devicePolicyManager =
+ TestApis.context().androidContextAsUser(mUser).getSystemService(
+ DevicePolicyManager.class);
+ return devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
+ }
+
+ /** Sets whether the current profile is organization owned. */
+ @TargetApi(TIRAMISU)
+ public void setIsOrganizationOwned(boolean isOrganizationOwned) {
+ Versions.requireMinimumVersion(TIRAMISU);
+
+ DevicePolicyManager devicePolicyManager =
+ TestApis.context().androidContextAsUser(mUser).getSystemService(
+ DevicePolicyManager.class);
+ devicePolicyManager.setProfileOwnerOnOrganizationOwnedDevice(mComponentName,
+ isOrganizationOwned);
+ }
+
@Override
public void remove() {
if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java
index 9598e70..f2f460c 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java
@@ -16,15 +16,21 @@
package com.android.bedstead.nene.devicepolicy;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.ComponentName;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasPermission;
import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.harrier.annotations.RequireRunNotOnSecondaryUser;
import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireSdkVersion;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDpc;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
import com.android.bedstead.nene.TestApis;
@@ -42,7 +48,8 @@
@RunWith(BedsteadJUnit4.class)
public class ProfileOwnerTest {
- @ClassRule @Rule
+ @ClassRule
+ @Rule
public static final DeviceState sDeviceState = new DeviceState();
private static final ComponentName DPC_COMPONENT_NAME = RemoteDpc.DPC_COMPONENT_NAME;
@@ -127,4 +134,31 @@
assertThat(TestApis.devicePolicy().getProfileOwner()).isNull();
}
+
+ @Test
+ @RequireSdkVersion(min = TIRAMISU)
+ @RequireRunOnWorkProfile
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public void setIsOrganizationOwned_becomesOrganizationOwned() {
+ ProfileOwner profileOwner = (ProfileOwner) sDeviceState.profileOwner(
+ sDeviceState.workProfile()).devicePolicyController();
+
+ profileOwner.setIsOrganizationOwned(true);
+
+ assertThat(profileOwner.isOrganizationOwned()).isTrue();
+ }
+
+ @Test
+ @RequireSdkVersion(min = TIRAMISU)
+ @RequireRunOnWorkProfile
+ @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public void unsetIsOrganizationOwned_becomesNotOrganizationOwned() {
+ ProfileOwner profileOwner = (ProfileOwner) sDeviceState.profileOwner(
+ sDeviceState.workProfile()).devicePolicyController();
+ profileOwner.setIsOrganizationOwned(true);
+
+ profileOwner.setIsOrganizationOwned(false);
+
+ assertThat(profileOwner.isOrganizationOwned()).isFalse();
+ }
}
diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
index 0434258..d0384b1 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -761,6 +761,7 @@
.filter(file -> doesFileExist(file, testInfo.getDevice()))
// GmsCore should not contribute to *classpath.
.filter(file -> !file.contains("GmsCore"))
+ .filter(file -> !file.contains("com.google.android.gms"))
.collect(ImmutableList.toImmutableList());
final ImmutableSetMultimap.Builder<String, String> jarsToClasses =
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
index 62e2180..b9ef7ad 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -20,7 +20,6 @@
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.util.ApiLevelUtil;
-
import com.android.tradefed.device.DeviceNotAvailableException;
import com.google.common.collect.ImmutableSet;
@@ -129,6 +128,30 @@
runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testEject");
}
+ public void testScopeStorageAtInitLocationRootWithDot_blockFromTree() throws Exception {
+ if (isAtLeastT()) {
+ // From BUILD.VERSION_CODES.S, scope storage is enabled in default
+ runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
+ "testScopeStorageAtInitLocationRootWithDot_blockFromTree");
+ }
+ }
+
+ public void testScopeStorageAtInitLocationAndroidData_blockFromTree() throws Exception {
+ if (isAtLeastT()) {
+ // From BUILD.VERSION_CODES.S, scope storage is enabled in default
+ runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
+ "testScopeStorageAtInitLocationAndroidData_blockFromTree");
+ }
+ }
+
+ public void testScopeStorageAtInitLocationAndroidObb_blockFromTree() throws Exception {
+ if (isAtLeastT()) {
+ // From BUILD.VERSION_CODES.S, scope storage is enabled in default
+ runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
+ "testScopeStorageAtInitLocationAndroidObb_blockFromTree");
+ }
+ }
+
public void testRestrictStorageAccessFrameworkEnabled_blockFromTree() throws Exception {
if (isAtLeastR() && isSupportedHardware()) {
runDeviceCompatTestReported(CLIENT_PKG, ".DocumentsClientTest",
@@ -177,6 +200,14 @@
}
}
+ private boolean isAtLeastT() {
+ try {
+ return ApiLevelUtil.isAfter(getDevice(), 32 /* BUILD.VERSION_CODES.S_V2 */);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
private boolean isSupportedHardware() {
try {
if (getDevice().hasFeature("feature:android.hardware.type.television")
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 5370a12..616ce26 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -672,6 +672,65 @@
assertInstallFromBuildFails("v3-por_Z_1_2-default-caps-sharedUid-companion.apk");
}
+ public void testInstallV3WithRestoredCapabilityInSharedUserId() throws Exception {
+ // A sharedUserId contains the shared signing lineage for all packages in the UID; this
+ // shared lineage contain the full signing history for all packages along with the merged
+ // capabilities for each signer shared between the packages. This test verifies if one
+ // package revokes a capability from a previous signer, but subsequently restores that
+ // capability, then since all packages have granted the capability, it is restored to the
+ // previous signer in the shared lineage.
+
+ // Install a package with the SHARED_USER_ID capability revoked for the original signer
+ // in the lineage; verify that a package signed with only the original signer cannot join
+ // the sharedUserId.
+ assertInstallFromBuildSucceeds(
+ "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
+ assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
+ assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
+
+ // Update the package that revoked the SHARED_USER_ID with an updated lineage that restores
+ // this capability to the original signer; verify the package signed with the original
+ // signing key can now join the sharedUserId since all existing packages in the UID grant
+ // this capability to the original signer.
+ assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps-sharedUid.apk");
+ assertInstallFromBuildSucceeds("v3-ec-p256-1-sharedUid-companion2.apk");
+ }
+
+ public void testInstallV3WithRevokedCapabilityInSharedUserId() throws Exception {
+ // While a capability can be restored to a common signer in the shared signing lineage, if
+ // one package has revoked a capability from a common signer and another package is
+ // installed / updated which restores the capability to that signer, the revocation of
+ // the capability by the existing package should take precedence. A capability can only
+ // be restored to a common signer if all packages in the sharedUserId have granted this
+ // capability to the signer.
+
+ // Install a package with the SHARED_USER_ID capability revoked from the original signer,
+ // then install another package in the sharedUserId that grants this capability to the
+ // original signer. Since a package exists in the sharedUserId that has revoked this
+ // capability, another package signed with this capability shouldn't be able to join the
+ // sharedUserId.
+ assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
+ assertInstallFromBuildSucceeds(
+ "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
+ assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
+
+ // Install the same package that grants the SHARED_USER_ID capability to the original
+ // signer; when iterating over the existing packages in the packages in the sharedUserId,
+ // the original version of this package should be skipped since the lineage from the
+ // updated package is used when merging with the shared lineage.
+ assertInstallFromBuildSucceeds(
+ "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
+ assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
+
+ // Install another package that has granted the SHARED_USER_ID to the original signer; this
+ // should trigger another merge with all packages in the sharedUserId. Since one still
+ // remains that revokes the capability, the capability should be revoked in the shared
+ // lineage.
+ assertInstallFromBuildSucceeds(
+ "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion3.apk");
+ assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
+ }
+
public void testInstallV3UpdateAfterRotation() throws Exception {
// This test performs an end to end verification of the update of an app with a rotated
// key. The app under test exports a bound service that performs its own PackageManager key
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index 392f525..cd66d3e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -238,6 +238,13 @@
}
}
+ @Test
+ public void testNoExternalAppStorage() throws Exception {
+ for (int user : mUsers) {
+ runDeviceTests(PKG_NO_APP_STORAGE, CLASS_NO_APP_STORAGE, "testNoExternalStorage", user);
+ }
+ }
+
public void waitForIdle() throws Exception {
// Try getting all pending events flushed out
for (int i = 0; i < 4; i++) {
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 0df4795..9e12ad0 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -26,7 +26,6 @@
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.database.Cursor;
-import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
@@ -488,7 +487,7 @@
mDevice.waitForIdle();
- // save button is enabled for for the storage root
+ // save button is enabled for the storage root
assertTrue(findSaveButton().isEnabled());
// We should always have Android directory available
@@ -530,6 +529,36 @@
assertTrue(findSaveButton().isEnabled());
}
+ public void testScopeStorageAtInitLocationRootWithDot_blockFromTree() throws Exception {
+ if (!supportedHardware()) return;
+
+ launchOpenDocumentTreeAtInitialLocation(STORAGE_AUTHORITY, "primary:.");
+
+ // save button is disabled for the directory
+ assertFalse(findSaveButton().isEnabled());
+
+ // The Android directory is available
+ assertTrue(findDocument("Android").exists());
+ }
+
+ public void testScopeStorageAtInitLocationAndroidData_blockFromTree() throws Exception {
+ if (!supportedHardware()) return;
+
+ launchOpenDocumentTreeAtInitialLocation(STORAGE_AUTHORITY, "primary:Android/data");
+
+ // save button is disabled for the directory
+ assertFalse(findSaveButton().isEnabled());
+ }
+
+ public void testScopeStorageAtInitLocationAndroidObb_blockFromTree() throws Exception {
+ if (!supportedHardware()) return;
+
+ launchOpenDocumentTreeAtInitialLocation(STORAGE_AUTHORITY, "primary:Android/obb");
+
+ // save button is disabled for the directory
+ assertFalse(findSaveButton().isEnabled());
+ }
+
public void testGetContent_rootsShowing() throws Exception {
if (!supportedHardware()) return;
@@ -799,15 +828,7 @@
public void testOpenDocumentTreeAtInitialLocation() throws Exception {
if (!supportedHardware()) return;
- // Clear DocsUI's storage to avoid it opening stored last location.
- clearDocumentsUi();
-
- final Uri docUri = DocumentsContract.buildDocumentUri(PROVIDER_PACKAGE, "doc:dir2");
- final Intent intent = new Intent();
- intent.setAction(Intent.ACTION_OPEN_DOCUMENT_TREE);
- intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, docUri);
- mActivity.startActivityForResult(intent, REQUEST_CODE);
- mDevice.waitForIdle();
+ launchOpenDocumentTreeAtInitialLocation(PROVIDER_PACKAGE, "doc:dir2");
assertTrue(findDocument("FILE4").exists());
}
@@ -970,6 +991,19 @@
context.checkCallingOrSelfUriPermission(targetUri, permissionFlag));
}
+ private void launchOpenDocumentTreeAtInitialLocation(@NonNull String authority,
+ @NonNull String docId) throws Exception {
+ // Clear DocsUI's storage to avoid it opening stored last location.
+ clearDocumentsUi();
+
+ final Uri initUri = DocumentsContract.buildDocumentUri(authority, docId);
+ final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+ intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initUri);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ mDevice.waitForIdle();
+ }
+
private Uri assertCreateDocumentSuccess(@Nullable Uri initUri, @NonNull String displayName,
@NonNull String mimeType) throws Exception {
// Clear DocsUI's storage to avoid it opening stored last location.
diff --git a/hostsidetests/appsecurity/test-apps/NoDataStorageApp/src/com/android/cts/noappstorage/NoAppDataStorageTest.java b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/src/com/android/cts/noappstorage/NoAppDataStorageTest.java
index e69ef3e..b599b2c 100644
--- a/hostsidetests/appsecurity/test-apps/NoDataStorageApp/src/com/android/cts/noappstorage/NoAppDataStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/src/com/android/cts/noappstorage/NoAppDataStorageTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.os.Environment;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,6 +55,26 @@
assertDirDoesNotExist(mDeContext.getCodeCacheDir());
}
+ @Test
+ public void testNoExternalStorage() throws Exception {
+ final String[] types = new String[] {
+ Environment.DIRECTORY_MUSIC,
+ Environment.DIRECTORY_PODCASTS,
+ Environment.DIRECTORY_RINGTONES,
+ Environment.DIRECTORY_ALARMS,
+ Environment.DIRECTORY_NOTIFICATIONS,
+ Environment.DIRECTORY_PICTURES,
+ Environment.DIRECTORY_MOVIES,
+ Environment.DIRECTORY_DOWNLOADS,
+ Environment.DIRECTORY_DCIM,
+ Environment.DIRECTORY_DOCUMENTS
+ };
+ for (String type : types) {
+ File dir = mCeContext.getExternalFilesDir(type);
+ assertThat(dir).isNull();
+ }
+ }
+
private void assertDirDoesNotExist(File dir) throws Exception {
assertThat(dir.exists()).isFalse();
assertThat(dir.mkdirs()).isFalse();
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp b/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp
index 1c98103..df32835 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp
@@ -286,6 +286,24 @@
sdk_version: "current",
}
+// This is the third companion package signed using the V3 signature scheme
+// with a rotated key and part of a sharedUid. The capabilities of this lineage
+// grant access to the previous key in the lineage to join the sharedUid.
+android_test_helper_app {
+ name: "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion3",
+ manifest: "AndroidManifest-companion3-shareduid.xml",
+ certificate: ":ec-p256_2",
+ additional_certificates: [":ec-p256"],
+ lineage: ":ec-p256-por_1_2-default-caps",
+ srcs: ["src/**/*.java"],
+ // resource_dirs is the default value: ["res"]
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ sdk_version: "current",
+}
+
// This is a version of the test package that declares a signature permission.
// The lineage used to sign this test package does not trust the first signing
// key but grants default capabilities to the second signing key.
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion3-shareduid.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion3-shareduid.xml
new file mode 100644
index 0000000..589ad60
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion3-shareduid.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.appsecurity.cts.tinyapp_companion3"
+ android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
+ android:versionCode="10"
+ android:versionName="1.0"
+ android:targetSandboxVersion="2">
+ <application android:label="@string/app_name">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BluetoothRestrictionTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BluetoothRestrictionTest.java
index 4fc80f7..f3cb6ce 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BluetoothRestrictionTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BluetoothRestrictionTest.java
@@ -16,6 +16,8 @@
package com.android.cts.deviceowner;
+import static android.os.Process.BLUETOOTH_UID;
+
import static com.google.common.truth.Truth.assertWithMessage;
import android.bluetooth.BluetoothAdapter;
@@ -40,9 +42,8 @@
private static final int POLL_TIME_MS = 400; // ms to poll BT state
private static final int CHECK_WAIT_TIME_MS = 1_000; // ms to wait before enable/disable
private static final int COMPONENT_STATE_TIMEOUT_MS = 10_000;
- private static final ComponentName OPP_LAUNCHER_COMPONENT = new ComponentName(
- "com.android.bluetooth", "com.android.bluetooth.opp.BluetoothOppLauncherActivity");
-
+ private static final String OPP_LAUNCHER_CLASS =
+ "com.android.bluetooth.opp.BluetoothOppLauncherActivity";
private BluetoothAdapter mBluetoothAdapter;
private PackageManager mPackageManager;
@@ -131,28 +132,38 @@
return;
}
+ String bluetoothPackageName = mContext.getPackageManager()
+ .getPackagesForUid(BLUETOOTH_UID)[0];
+
+ ComponentName oppLauncherComponent = new ComponentName(
+ bluetoothPackageName, OPP_LAUNCHER_CLASS);
+
// First verify DISALLOW_BLUETOOTH.
- testOppDisabledWhenRestrictionSet(UserManager.DISALLOW_BLUETOOTH);
+ testOppDisabledWhenRestrictionSet(UserManager.DISALLOW_BLUETOOTH,
+ oppLauncherComponent);
+
// Verify DISALLOW_BLUETOOTH_SHARING which leaves bluetooth workable but the sharing
// component should be disabled.
- testOppDisabledWhenRestrictionSet(UserManager.DISALLOW_BLUETOOTH_SHARING);
+ testOppDisabledWhenRestrictionSet(UserManager.DISALLOW_BLUETOOTH_SHARING,
+ oppLauncherComponent);
}
/** Verifies that a given restriction disables the bluetooth sharing component. */
- private void testOppDisabledWhenRestrictionSet(String restriction) {
+ private void testOppDisabledWhenRestrictionSet(String restriction,
+ ComponentName oppLauncherComponent) {
// Add the user restriction.
addUserRestriction(restriction);
// The BluetoothOppLauncherActivity's component should be disabled.
assertComponentStateAfterTimeout(
- OPP_LAUNCHER_COMPONENT, PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ oppLauncherComponent, PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
// Remove the user restriction.
clearUserRestriction(restriction);
// The BluetoothOppLauncherActivity's component should be in the default state.
assertComponentStateAfterTimeout(
- OPP_LAUNCHER_COMPONENT, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+ oppLauncherComponent, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
}
/** Helper to turn BT off.
@@ -218,7 +229,8 @@
while (SystemClock.elapsedRealtime() < timeout) {
state = mPackageManager.getComponentEnabledSetting(component);
if (expectedState == state) {
- // Success
+ // Success, waiting for component to be fully turned on/off
+ sleep(CHECK_WAIT_TIME_MS);
return;
}
sleep(POLL_TIME_MS);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index 3a8eef6..32313a8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -525,7 +525,7 @@
// Test managed profile. This should not be disabled when screen capture is disabled on
// the parent by the profile owner of an organization-owned device.
- takeScreenCaptureAsUser(mUserId, "testScreenCapturePossible");
+ takeScreenCaptureAsUser(mUserId, testMethodName);
}
private void assertHasNoUser(int userId) throws DeviceNotAvailableException {
diff --git a/hostsidetests/incident/AndroidTest.xml b/hostsidetests/incident/AndroidTest.xml
index 57fd89d..727277d 100644
--- a/hostsidetests/incident/AndroidTest.xml
+++ b/hostsidetests/incident/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
<option name="user-type" value="system" />
</target_preparer>
diff --git a/hostsidetests/jvmti/allocation-tracking/Android.bp b/hostsidetests/jvmti/allocation-tracking/Android.bp
index 07956a3..208b102 100644
--- a/hostsidetests/jvmti/allocation-tracking/Android.bp
+++ b/hostsidetests/jvmti/allocation-tracking/Android.bp
@@ -27,4 +27,8 @@
test_options: {
unit_test: false,
},
+ data: [
+ ":CtsJvmtiTrackingDeviceApp",
+ ],
+ per_testcase_directory: true,
}
\ No newline at end of file
diff --git a/hostsidetests/media/bitstreams/Android.bp b/hostsidetests/media/bitstreams/Android.bp
index 30bbb69..f7bf1a3 100644
--- a/hostsidetests/media/bitstreams/Android.bp
+++ b/hostsidetests/media/bitstreams/Android.bp
@@ -36,4 +36,8 @@
],
java_resources: ["DynamicConfig.xml"],
required: ["cts-dynamic-config"],
+ data: [
+ ":CtsMediaBitstreamsDeviceSideTestApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/numberblocking/Android.bp b/hostsidetests/numberblocking/Android.bp
index 67ce71c..aaad013 100644
--- a/hostsidetests/numberblocking/Android.bp
+++ b/hostsidetests/numberblocking/Android.bp
@@ -29,4 +29,8 @@
"tradefed",
"compatibility-host-util",
],
+ data: [
+ ":CtsHostsideNumberBlockingAppTest",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/os/Android.bp b/hostsidetests/os/Android.bp
index f60a789..7c539c2 100644
--- a/hostsidetests/os/Android.bp
+++ b/hostsidetests/os/Android.bp
@@ -35,4 +35,14 @@
"cts",
"general-tests",
],
+ data: [
+ ":CtsStaticSharedLibProviderApp1",
+ ":CtsStaticSharedLibProviderApp2",
+ ":CtsDeviceOsTestApp",
+ ":CtsHostProcfsTestApp",
+ ":CtsInattentiveSleepTestApp",
+ ":CtsHostEnvironmentTestApp",
+ ":CtsStaticSharedLibTestApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/packagemanager/dynamicmime/Android.bp b/hostsidetests/packagemanager/dynamicmime/Android.bp
index 39247de..b9c4a73 100644
--- a/hostsidetests/packagemanager/dynamicmime/Android.bp
+++ b/hostsidetests/packagemanager/dynamicmime/Android.bp
@@ -30,4 +30,13 @@
"compatibility-host-util",
"truth-prebuilt",
],
+ data: [
+ ":CtsDynamicMimeUpdateAppFirstGroup",
+ ":CtsDynamicMimeUpdateAppSecondGroup",
+ ":CtsDynamicMimeUpdateAppBothGroups",
+ ":CtsDynamicMimePreferredApp",
+ ":CtsDynamicMimeHelperApp",
+ ":CtsDynamicMimeTestApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/packagemanager/installedloadingprogess/Android.bp b/hostsidetests/packagemanager/installedloadingprogess/Android.bp
index 1dff1a3..a81820a 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/Android.bp
+++ b/hostsidetests/packagemanager/installedloadingprogess/Android.bp
@@ -31,4 +31,9 @@
"cts",
"general-tests",
],
+ data: [
+ ":CtsInstalledLoadingProgressDeviceTests",
+ ":CtsInstalledLoadingProgressTestsRegisterApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/packagemanager/multiuser/Android.bp b/hostsidetests/packagemanager/multiuser/Android.bp
index 855700f..2ed3804 100644
--- a/hostsidetests/packagemanager/multiuser/Android.bp
+++ b/hostsidetests/packagemanager/multiuser/Android.bp
@@ -33,4 +33,8 @@
required: [
"CtsPackageManagerMultiUserEmptyTestApp",
],
+ data: [
+ ":CtsPackageManagerMultiUserTestApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/seccomp/Android.bp b/hostsidetests/seccomp/Android.bp
index d1f8121..9e707df 100644
--- a/hostsidetests/seccomp/Android.bp
+++ b/hostsidetests/seccomp/Android.bp
@@ -29,4 +29,8 @@
"tradefed",
"compatibility-host-util",
],
+ data: [
+ ":CtsSeccompDeviceApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/securitybulletin/Android.bp b/hostsidetests/securitybulletin/Android.bp
index 7770ebd..323d5b7 100644
--- a/hostsidetests/securitybulletin/Android.bp
+++ b/hostsidetests/securitybulletin/Android.bp
@@ -34,6 +34,12 @@
"sts-host-util",
"tradefed",
],
+ data: [
+ ":CtsHostLaunchAnyWhereApp",
+ ":MainlineModuleDetector",
+ ":hotspot",
+ ],
+ per_testcase_directory: true,
}
cc_defaults {
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Bug_183411210.java b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183411210.java
new file mode 100644
index 0000000..d59fce4
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183411210.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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 static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class Bug_183411210 extends SecurityTestCase {
+ private static final String TEST_PKG = "android.security.cts.BUG_183411210";
+ private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+ private static final String TEST_APP = "BUG-183411210.apk";
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ assumeTrue("not an Automotive device",
+ getDevice().hasFeature("feature:android.hardware.type.automotive"));
+ uninstallPackage(getDevice(), TEST_PKG);
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 183411210)
+ public void testRunDeviceTestsPassesFull() throws Exception {
+ installPackage(TEST_APP);
+ // Grant permission to draw overlays.
+ getDevice().executeShellCommand(
+ "pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW");
+ assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testTapjacking"));
+ }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/Android.bp b/hostsidetests/securitybulletin/test-apps/BUG-183411210/Android.bp
new file mode 100644
index 0000000..2648a19
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+ name: "BUG-183411210",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "vts10",
+ "sts",
+ ],
+ static_libs: [
+ "androidx.appcompat_appcompat",
+ "androidx.test.rules",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.test.core",
+ ],
+ sdk_version: "current",
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/BUG-183411210/AndroidManifest.xml
new file mode 100644
index 0000000..30598a9
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.security.cts.BUG_183411210"
+ minSdkVersion="29">
+
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <application android:theme="@style/Theme.AppCompat.Light">
+ <uses-library android:name="android.test.runner" />
+ <service android:name=".OverlayService"
+ android:enabled="true"
+ android:exported="false" />
+
+ <activity
+ android:name=".MainActivity"
+ android:label="ST (Permission)"
+ android:exported="true"
+ android:taskAffinity="android.security.cts.BUG_183411210.MainActivity">
+
+ <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.BUG_183411210" />
+
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/layout/activity_main.xml
new file mode 100644
index 0000000..c23b709
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/layout/activity_main.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="left"
+ tools:context=".MainActivity" >
+
+ <LinearLayout
+ android:id="@+id/linearLayout1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/seekShowTimes"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="53dp"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/btnStart"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Start" />
+
+ </LinearLayout>
+
+</RelativeLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/values/strings.xml
new file mode 100644
index 0000000..3db77b0
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2021 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="tapjacking_text">BUG_183411210 overlay text</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/Constants.java b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/Constants.java
new file mode 100644
index 0000000..9445d51
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/Constants.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 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.BUG_183411210;
+
+final class Constants {
+
+ public static final String LOG_TAG = "BUG-183411210";
+ public static final String TEST_APP_PACKAGE = Constants.class.getPackage().getName();
+
+ public static final String ACTION_START_TAPJACKING = "BUG_183411210.start_tapjacking";
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/DeviceTest.java
new file mode 100644
index 0000000..08b68e2
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/DeviceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2021 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.BUG_183411210;
+
+import static android.security.cts.BUG_183411210.Constants.LOG_TAG;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Basic sample for unbundled UiAutomator. */
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+ private static final long WAIT_FOR_UI_TIMEOUT = 20_000;
+
+ private Context mContext;
+ private UiDevice mDevice;
+
+ @Before
+ public void setUp() throws Exception {
+ Log.d(LOG_TAG, "startMainActivityFromHomeScreen()");
+
+ mContext = getApplicationContext();
+
+ // If the permission is not granted, the app will not be able to show an overlay dialog.
+ // This is required for the test below.
+ // NOTE: The permission is granted by the HostJUnit4Test implementation and should not fail.
+ assertEquals("Permission SYSTEM_ALERT_WINDOW not granted!",
+ mContext.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW"),
+ PackageManager.PERMISSION_GRANTED);
+
+ // Initialize UiDevice instance
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ if (!mDevice.isScreenOn()) {
+ mDevice.wakeUp();
+ }
+ mDevice.pressHome();
+ }
+
+ @Test
+ public void testTapjacking() throws InterruptedException {
+ Log.d(LOG_TAG, "Starting tap-jacking test");
+
+ launchTestApp();
+
+ launchTapjackedActivity();
+
+ mContext.sendBroadcast(new Intent(Constants.ACTION_START_TAPJACKING));
+ Log.d(LOG_TAG, "Sent intent to start tap-jacking!");
+
+ UiObject2 overlay = waitForView(By.text(mContext.getString(R.string.tapjacking_text)));
+ assertNull("Tap-jacking successful. Overlay was displayed.!", overlay);
+ }
+
+ @After
+ public void tearDown() {
+ mDevice.pressHome();
+ }
+
+ private void launchTestApp() {
+ Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(
+ Constants.TEST_APP_PACKAGE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+
+ // Wait for the app to appear
+ UiObject2 view = waitForView(By.pkg(Constants.TEST_APP_PACKAGE).depth(0));
+ assertNotNull("test-app did not appear!", view);
+ Log.d(LOG_TAG, "test-app appeared");
+ }
+
+ private void launchTapjackedActivity() {
+ Intent intent = new Intent();
+ intent.setAction("android.settings.action.MANAGE_WRITE_SETTINGS");
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+
+ UiObject2 activityInstance = waitForView(By.pkg("com.android.car.settings").depth(0));
+ assertNotNull("Activity under-test was not launched or found!", activityInstance);
+ Log.d(LOG_TAG, "Started Activity under-test.");
+ }
+
+ private UiObject2 waitForView(BySelector selector) {
+ return mDevice.wait(Until.findObject(selector), WAIT_FOR_UI_TIMEOUT);
+ }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/MainActivity.java b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/MainActivity.java
new file mode 100644
index 0000000..7833d26
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/MainActivity.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.BUG_183411210;
+
+import static android.security.cts.BUG_183411210.Constants.LOG_TAG;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Button;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+/** Main activity for the test-app. */
+public final class MainActivity extends AppCompatActivity {
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ startTapjacking();
+ }
+ };
+
+ private Button btnStart;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ registerReceiver(mReceiver, new IntentFilter(Constants.ACTION_START_TAPJACKING));
+
+ btnStart = (Button) findViewById(R.id.btnStart);
+ btnStart.setOnClickListener(v -> startTapjacking());
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(mReceiver);
+ stopOverlayService();
+ }
+
+ public void startTapjacking() {
+ Log.d(LOG_TAG, "Starting tap-jacking flow.");
+ stopOverlayService();
+
+ startOverlayService();
+ Log.d(LOG_TAG, "Started overlay-service.");
+ }
+
+ private void startOverlayService() {
+ startService(new Intent(getApplicationContext(), OverlayService.class));
+ }
+
+ private void stopOverlayService() {
+ stopService(new Intent(getApplicationContext(), OverlayService.class));
+ }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/OverlayService.java b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/OverlayService.java
new file mode 100644
index 0000000..8d27aa8
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/OverlayService.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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.BUG_183411210;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.Button;
+
+/** Service that starts the overlay for the test. */
+public final class OverlayService extends Service {
+ public Button mButton;
+ private WindowManager mWindowManager;
+ private WindowManager.LayoutParams mLayoutParams;
+
+ @Override
+ public void onCreate() {
+ Log.d(Constants.LOG_TAG, "onCreate() called");
+ super.onCreate();
+
+ DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
+ int scaledWidth = (int) (displayMetrics.widthPixels * 0.9);
+ int scaledHeight = (int) (displayMetrics.heightPixels * 0.9);
+
+ mWindowManager = getSystemService(WindowManager.class);
+ mLayoutParams = new WindowManager.LayoutParams();
+ mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ mLayoutParams.format = PixelFormat.OPAQUE;
+ mLayoutParams.gravity = Gravity.CENTER;
+ mLayoutParams.width = scaledWidth;
+ mLayoutParams.height = scaledHeight;
+ mLayoutParams.x = scaledWidth / 2;
+ mLayoutParams.y = scaledHeight / 2;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(Constants.LOG_TAG, "onStartCommand() called");
+ showFloatingWindow();
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(Constants.LOG_TAG, "onDestroy() called");
+ if (mWindowManager != null && mButton != null) {
+ mWindowManager.removeView(mButton);
+ }
+ super.onDestroy();
+ }
+
+ private void showFloatingWindow() {
+ if (!Settings.canDrawOverlays(this)) {
+ Log.w(Constants.LOG_TAG, "Cannot show overlay window. Permission denied");
+ }
+
+ mButton = new Button(getApplicationContext());
+ mButton.setText(getResources().getString(R.string.tapjacking_text));
+ mButton.setTag(mButton.getVisibility());
+ mWindowManager.addView(mButton, mLayoutParams);
+
+ new Handler(Looper.myLooper()).postDelayed(this::stopSelf, 60_000);
+ Log.d(Constants.LOG_TAG, "Floating window created");
+ }
+}
diff --git a/hostsidetests/shortcuts/hostside/AndroidTest.xml b/hostsidetests/shortcuts/hostside/AndroidTest.xml
index 36a0876..bfc33f7 100644
--- a/hostsidetests/shortcuts/hostside/AndroidTest.xml
+++ b/hostsidetests/shortcuts/hostside/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="android.cts.backup.BackupPreparer">
<option name="enable-backup-if-needed" value="true" />
<option name="select-local-transport" value="true" />
diff --git a/hostsidetests/silentupdate/Android.bp b/hostsidetests/silentupdate/Android.bp
index c562c6a..641d60b 100644
--- a/hostsidetests/silentupdate/Android.bp
+++ b/hostsidetests/silentupdate/Android.bp
@@ -30,7 +30,9 @@
"tradefed",
"compatibility-host-util",
],
+ per_testcase_directory: true,
data: [
+ ":CtsSilentUpdateTestCases",
":SilentInstallCurrent",
":SilentInstallR",
":SilentInstallQ",
diff --git a/hostsidetests/statsdatom/AndroidTest.xml b/hostsidetests/statsdatom/AndroidTest.xml
index e353d1a..fce91f0 100644
--- a/hostsidetests/statsdatom/AndroidTest.xml
+++ b/hostsidetests/statsdatom/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsStatsdAtomHostTestCases.jar" />
</test>
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
index 6547abf..c7feda6 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
@@ -43,6 +43,7 @@
private static final int APP_OP_RECORD_AUDIO = 27;
private static final int APP_OP_RECORD_AUDIO_HOTWORD = 102;
+ private static final int APP_OP_ACCESS_RESTRICTED_SETTINGS = 119;
private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
@@ -102,6 +103,10 @@
ArrayList<Integer> expectedOps = new ArrayList<>();
Set<Integer> transformedOps = new HashSet<>(mTransformedFromOp.values());
for (int i = 0; i < NUM_APP_OPS; i++) {
+ // Ignore access restricted setting as it cannot be read by normal app.
+ if (i == APP_OP_ACCESS_RESTRICTED_SETTINGS) {
+ continue;
+ }
if (!transformedOps.contains(i)) {
expectedOps.add(i);
}
diff --git a/hostsidetests/systemui/Android.bp b/hostsidetests/systemui/Android.bp
index a5918f4..2c28027 100644
--- a/hostsidetests/systemui/Android.bp
+++ b/hostsidetests/systemui/Android.bp
@@ -37,4 +37,8 @@
"cts",
"general-tests",
],
+ data: [
+ ":CtsSystemUiDeviceApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/telephony/Android.bp b/hostsidetests/telephony/Android.bp
index b3d0084..f1bd4a8 100644
--- a/hostsidetests/telephony/Android.bp
+++ b/hostsidetests/telephony/Android.bp
@@ -31,4 +31,8 @@
"tradefed",
"compatibility-host-util",
],
+ data: [
+ ":TelephonyDeviceTest",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/telephonyprovider/Android.bp b/hostsidetests/telephonyprovider/Android.bp
index 2fa3b71..406c3d6 100644
--- a/hostsidetests/telephonyprovider/Android.bp
+++ b/hostsidetests/telephonyprovider/Android.bp
@@ -31,4 +31,8 @@
"tradefed",
"compatibility-host-util",
],
+ data: [
+ ":TelephonyProviderDeviceTest",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/theme/Android.bp b/hostsidetests/theme/Android.bp
index 9d1c692..55fdbd2 100644
--- a/hostsidetests/theme/Android.bp
+++ b/hostsidetests/theme/Android.bp
@@ -39,4 +39,8 @@
"cts",
"general-tests",
],
+ data: [
+ ":CtsThemeDeviceApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/usage/Android.bp b/hostsidetests/usage/Android.bp
index 2d5b014..1af75f1 100644
--- a/hostsidetests/usage/Android.bp
+++ b/hostsidetests/usage/Android.bp
@@ -31,4 +31,9 @@
"general-tests",
"mts",
],
+ data: [
+ ":CtsAppUsageTestApp",
+ ":CtsAppUsageTestAppToo",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/webkit/Android.bp b/hostsidetests/webkit/Android.bp
index c76c2d1..734b1b8 100644
--- a/hostsidetests/webkit/Android.bp
+++ b/hostsidetests/webkit/Android.bp
@@ -30,4 +30,8 @@
"cts",
"general-tests",
],
+ data: [
+ ":CtsWebViewStartupApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/hostsidetests/wifibroadcasts/Android.bp b/hostsidetests/wifibroadcasts/Android.bp
index 30160619..ea1f50d 100644
--- a/hostsidetests/wifibroadcasts/Android.bp
+++ b/hostsidetests/wifibroadcasts/Android.bp
@@ -30,4 +30,8 @@
"tradefed",
"compatibility-host-util",
],
+ data: [
+ ":CtsWifiBroadcastsDeviceApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java b/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
index 7976f1f..362267c 100644
--- a/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
@@ -32,7 +32,6 @@
import junit.framework.Assert;
import java.util.Map;
-import java.util.concurrent.Callable;
/**
* Utility class to simplify tests that need to load data into a WebView and wait for completion
@@ -236,22 +235,12 @@
* similar functions.
*/
public void waitForLoadCompletion() {
- waitForCriteria(WebkitUtils.TEST_TIMEOUT_MS,
- new Callable<Boolean>() {
- @Override
- public Boolean call() {
- return isLoaded();
- }
- });
- clearLoad();
- }
-
- private void waitForCriteria(long timeout, Callable<Boolean> doneCriteria) {
if (isUiThread()) {
- waitOnUiThread(timeout, doneCriteria);
+ waitForLoadCompletionOnUiThread(WebkitUtils.TEST_TIMEOUT_MS);
} else {
- waitOnTestThread(timeout, doneCriteria);
+ waitForLoadCompletionOnTestThread(WebkitUtils.TEST_TIMEOUT_MS);
}
+ clearLoad();
}
/**
@@ -262,6 +251,14 @@
}
/**
+ * @return A summary of the current loading status for error reporting.
+ */
+ private synchronized String getLoadStatus() {
+ return "Current load status: mLoaded=" + mLoaded + ", mNewPicture="
+ + mNewPicture + ", mProgress=" + mProgress;
+ }
+
+ /**
* Makes a WebView call, waits for completion and then resets the
* load state in preparation for the next load call.
*
@@ -295,17 +292,17 @@
/**
* Uses a polling mechanism, while pumping messages to check when the
- * criteria is met.
+ * load is done.
*/
- private void waitOnUiThread(long timeout, final Callable<Boolean> doneCriteria) {
+ private void waitForLoadCompletionOnUiThread(long timeout) {
new PollingCheck(timeout) {
@Override
protected boolean check() {
pumpMessages();
try {
- return doneCriteria.call();
+ return isLoaded();
} catch (Exception e) {
- Assert.fail("Unexpected error while checking the criteria: "
+ Assert.fail("Unexpected error while checking load completion: "
+ e.getMessage());
return true;
}
@@ -314,21 +311,23 @@
}
/**
- * Uses a wait/notify to check when the criteria is met.
+ * Uses a wait/notify to check when the load is done.
*/
- private synchronized void waitOnTestThread(long timeout, Callable<Boolean> doneCriteria) {
+ private synchronized void waitForLoadCompletionOnTestThread(long timeout) {
try {
long waitEnd = SystemClock.uptimeMillis() + timeout;
long timeRemaining = timeout;
- while (!doneCriteria.call() && timeRemaining > 0) {
+ while (!isLoaded() && timeRemaining > 0) {
this.wait(timeRemaining);
timeRemaining = waitEnd - SystemClock.uptimeMillis();
}
- Assert.assertTrue("Action failed to complete before timeout", doneCriteria.call());
+ if (!isLoaded()) {
+ Assert.fail("Action failed to complete before timeout: " + getLoadStatus());
+ }
} catch (InterruptedException e) {
// We'll just drop out of the loop and fail
} catch (Exception e) {
- Assert.fail("Unexpected error while checking the criteria: "
+ Assert.fail("Unexpected error while checking load completion: "
+ e.getMessage());
}
}
diff --git a/tests/BlobStore/Android.bp b/tests/BlobStore/Android.bp
index 7fcb613..e1173d4 100644
--- a/tests/BlobStore/Android.bp
+++ b/tests/BlobStore/Android.bp
@@ -36,7 +36,13 @@
"cts",
"general-tests",
],
- sdk_version: "test_current"
+ sdk_version: "test_current",
+ data: [
+ ":CtsBlobStoreTestHelper",
+ ":CtsBlobStoreTestHelperDiffSig",
+ ":CtsBlobStoreTestHelperDiffSig2",
+ ],
+ per_testcase_directory: true,
}
android_test_helper_app {
diff --git a/tests/JobScheduler/Android.bp b/tests/JobScheduler/Android.bp
index c4f8018..ec39138 100644
--- a/tests/JobScheduler/Android.bp
+++ b/tests/JobScheduler/Android.bp
@@ -38,4 +38,8 @@
],
// sdk_version: "current",
platform_apis: true,
+ data: [
+ ":CtsJobTestApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
index 2c54508..3f4a767 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
@@ -22,6 +22,7 @@
import android.app.job.JobInfo;
import android.content.ClipData;
import android.content.Intent;
+import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.Uri;
import android.os.Bundle;
@@ -495,6 +496,7 @@
JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
.build();
assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+ assertNull(ji.getRequiredNetwork());
// Confirm JobScheduler accepts the JobInfo object.
mJobScheduler.schedule(ji);
@@ -502,6 +504,14 @@
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build();
assertEquals(JobInfo.NETWORK_TYPE_ANY, ji.getNetworkType());
+ assertTrue(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+ assertTrue(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+ assertFalse(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+ assertFalse(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
// Confirm JobScheduler accepts the JobInfo object.
mJobScheduler.schedule(ji);
@@ -509,6 +519,14 @@
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.build();
assertEquals(JobInfo.NETWORK_TYPE_UNMETERED, ji.getNetworkType());
+ assertTrue(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+ assertTrue(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+ assertFalse(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+ assertFalse(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
// Confirm JobScheduler accepts the JobInfo object.
mJobScheduler.schedule(ji);
@@ -516,6 +534,14 @@
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
.build();
assertEquals(JobInfo.NETWORK_TYPE_NOT_ROAMING, ji.getNetworkType());
+ assertTrue(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+ assertTrue(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+ assertFalse(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+ assertFalse(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
// Confirm JobScheduler accepts the JobInfo object.
mJobScheduler.schedule(ji);
@@ -523,6 +549,14 @@
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
.build();
assertEquals(JobInfo.NETWORK_TYPE_CELLULAR, ji.getNetworkType());
+ assertTrue(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+ assertTrue(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+ assertFalse(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+ assertFalse(ji.getRequiredNetwork()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
// Confirm JobScheduler accepts the JobInfo object.
mJobScheduler.schedule(ji);
@@ -530,6 +564,7 @@
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
.build();
assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+ assertNull(ji.getRequiredNetwork());
// Confirm JobScheduler accepts the JobInfo object.
mJobScheduler.schedule(ji);
}
diff --git a/tests/JobSchedulerSharedUid/Android.bp b/tests/JobSchedulerSharedUid/Android.bp
index ee30d75..05fc1f3 100644
--- a/tests/JobSchedulerSharedUid/Android.bp
+++ b/tests/JobSchedulerSharedUid/Android.bp
@@ -39,4 +39,10 @@
//sdk_version: "current"
platform_apis: true,
+ data: [
+ ":CtsJobSchedulerJobPerm",
+ ":CtsJobSchedulerSharedUid",
+ ":CtsJobSharedUidTestApp",
+ ],
+ per_testcase_directory: true,
}
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index bbce105..9ac38e9 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -24,6 +24,11 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <!-- Some tests use sticky broadcasts to ensure that inline suggestion extras
+ are delivered to the IME even when its process is not running persistently.
+ This can happen when the IME is unbound as a result of enabling
+ the config_preventImeStartupUnlessTextEditor option. -->
+ <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<application>
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
index 0a32981..d507dd7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
@@ -68,6 +68,12 @@
*/
private static final String EXTRA_OUTPUT_IS_EPHEMERAL_DATASET = "output_is_ephemeral_dataset";
+ /**
+ * When launched with a non-null intent associated with this extra, the intent will be returned
+ * as the response.
+ */
+ private static final String EXTRA_RESPONSE_INTENT = "response_intent";
+
private static final int MSG_WAIT_FOR_LATCH = 1;
private static final int MSG_REQUEST_AUTOFILL = 2;
@@ -117,6 +123,10 @@
return createSender(context, id, dataset, null);
}
+ public static IntentSender createSender(Context context, Intent responseIntent) {
+ return createSender(context, null, 1, null, null, responseIntent);
+ }
+
public static IntentSender createSender(Context context, int id,
CannedDataset dataset, Bundle outClientState) {
return createSender(context, id, dataset, outClientState, null);
@@ -127,14 +137,14 @@
Preconditions.checkArgument(id > 0, "id must be positive");
Preconditions.checkState(sDatasets.get(id) == null, "already have id");
sDatasets.put(id, dataset);
- return createSender(context, EXTRA_DATASET_ID, id, outClientState, isEphemeralDataset);
+ return createSender(context, EXTRA_DATASET_ID, id, outClientState, isEphemeralDataset,
+ null);
}
/**
* Creates an {@link IntentSender} with the given unique id for the given fill response.
*/
- public static IntentSender createSender(Context context, int id,
- CannedFillResponse response) {
+ public static IntentSender createSender(Context context, int id, CannedFillResponse response) {
return createSender(context, id, response, null);
}
@@ -143,12 +153,12 @@
Preconditions.checkArgument(id > 0, "id must be positive");
Preconditions.checkState(sResponses.get(id) == null, "already have id");
sResponses.put(id, response);
- return createSender(context, EXTRA_RESPONSE_ID, id, outData, null);
+ return createSender(context, EXTRA_RESPONSE_ID, id, outData, null, null);
}
private static IntentSender createSender(Context context, String extraName, int id,
- Bundle outClientState, Boolean isEphemeralDataset) {
- final Intent intent = new Intent(context, AuthenticationActivity.class);
+ Bundle outClientState, Boolean isEphemeralDataset, Intent responseIntent) {
+ Intent intent = new Intent(context, AuthenticationActivity.class);
intent.putExtra(extraName, id);
if (outClientState != null) {
Log.d(TAG, "Create with " + outClientState + " as " + EXTRA_OUTPUT_CLIENT_STATE);
@@ -159,6 +169,7 @@
+ EXTRA_OUTPUT_IS_EPHEMERAL_DATASET);
intent.putExtra(EXTRA_OUTPUT_IS_EPHEMERAL_DATASET, isEphemeralDataset);
}
+ intent.putExtra(EXTRA_RESPONSE_INTENT, responseIntent);
final PendingIntent pendingIntent =
PendingIntent.getActivity(context, id, intent, PendingIntent.FLAG_MUTABLE);
sPendingIntents.add(pendingIntent);
@@ -267,6 +278,20 @@
}
private void doIt() {
+ final int resultCode;
+ synchronized (sLock) {
+ resultCode = sResultCode;
+ }
+
+ // If responseIntent is provided, use that to return, otherwise contstruct the response.
+ Intent responseIntent = getIntent().getParcelableExtra(EXTRA_RESPONSE_INTENT, Intent.class);
+ if (responseIntent != null) {
+ Log.d(TAG, "Returning code " + resultCode);
+ setResult(resultCode, responseIntent);
+ finish();
+ return;
+ }
+
// We should get the assist structure...
final AssistStructure structure = getIntent().getParcelableExtra(
AutofillManager.EXTRA_ASSIST_STRUCTURE);
@@ -313,11 +338,6 @@
intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
isEphemeralDataset);
}
-
- final int resultCode;
- synchronized (sLock) {
- resultCode = sResultCode;
- }
Log.d(TAG, "Returning code " + resultCode);
setResult(resultCode, intent);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java
index d8c10dd..d9a84fd 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java
@@ -38,6 +38,7 @@
import android.autofillservice.cts.testcore.Helper;
import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.platform.test.annotations.AppModeFull;
@@ -977,7 +978,20 @@
fillResponseAuthServiceHasNoDataTest(false);
}
+ // Tests fix for bug in Android 11 where app crashes when autofill provider return empty Intent
+ // with success from authentication activity.
+ @Test
+ @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+ public void testFillResponseAuthServiceReturnsEmptyIntent() throws Exception {
+ fillResponseAuthServiceHasNoDataTest(false, new Intent());
+ }
+
private void fillResponseAuthServiceHasNoDataTest(boolean canSave) throws Exception {
+ fillResponseAuthServiceHasNoDataTest(canSave, null);
+ }
+
+ private void fillResponseAuthServiceHasNoDataTest(boolean canSave, Intent responseIntent)
+ throws Exception {
// Set service.
enableService();
final MyAutofillCallback callback = mActivity.registerCallback();
@@ -989,8 +1003,12 @@
.build()
: CannedFillResponse.NO_RESPONSE;
- final IntentSender authentication =
- AuthenticationActivity.createSender(mContext, 1, response);
+ final IntentSender authentication;
+ if (responseIntent != null) {
+ authentication = AuthenticationActivity.createSender(mContext, responseIntent);
+ } else {
+ authentication = AuthenticationActivity.createSender(mContext, 1, response);
+ }
// Configure the service behavior
sReplier.addResponse(new CannedFillResponse.Builder()
diff --git a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
index 123e133..230b75a 100644
--- a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
+++ b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
@@ -25,16 +25,20 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import android.content.Context;
+import android.content.res.Resources;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
@@ -42,6 +46,8 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import java.util.HashSet;
+import java.util.Set;
import java.util.concurrent.Executor;
/** CTS tests for {@link DeviceStateManager} API(s). */
@@ -50,6 +56,8 @@
public static final int TIMEOUT = 2000;
+ private static final int INVALID_DEVICE_STATE = -1;
+
/**
* Tests that {@link DeviceStateManager#getSupportedStates()} returns at least one state and
* that none of the returned states are in the range
@@ -119,20 +127,27 @@
/**
* Tests that calling {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
- * DeviceStateRequest.Callback)} is successful and results in a registered callback being
- * triggered with a value equal to the requested state.
+ * DeviceStateRequest.Callback)} is not successful and results in a failure to change the
+ * state of the device due to the state requested not being available for apps to request.
*/
@Test
- public void testRequestStateSucceedsAsTopApp() throws IllegalArgumentException {
+ public void testRequestStateFailsAsTopApp_ifStateNotDefinedAsAvailableForAppsToRequest()
+ throws IllegalArgumentException {
final DeviceStateManager manager = getDeviceStateManager();
final int[] supportedStates = manager.getSupportedStates();
// We want to verify that the app can change device state
// So we only attempt if there are more than 1 possible state.
assumeTrue(supportedStates.length > 1);
+ Set<Integer> statesAvailableToRequest = getAvailableStatesToRequest(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), supportedStates);
+ // checks that not every state is available for an app to request
+ assumeTrue(statesAvailableToRequest.size() < supportedStates.length);
+
+ Set<Integer> availableDeviceStates = generateDeviceStateSet(supportedStates);
final StateTrackingCallback callback = new StateTrackingCallback();
manager.registerCallback(Runnable::run, callback);
- PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != -1);
+ PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != INVALID_DEVICE_STATE);
final TestActivitySession<DeviceStateTestActivity> activitySession =
createManagedTestActivitySession();
@@ -143,13 +158,65 @@
DeviceStateTestActivity activity = activitySession.getActivity();
- int newState = determineNewState(callback.mCurrentState, supportedStates);
- activity.requestDeviceStateChange(newState);
+ Set<Integer> possibleStates = possibleStates(false /* shouldSucceed */,
+ availableDeviceStates,
+ statesAvailableToRequest);
+ int nextState = calculateDifferentState(callback.mCurrentState, possibleStates);
+ // checks that we were able to find a valid state to request.
+ assumeTrue(nextState != INVALID_DEVICE_STATE);
- PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState == newState);
+ activity.requestDeviceStateChange(nextState);
- assertEquals(newState, callback.mCurrentState);
+ assertTrue(activity.requestStateFailed);
+ }
+
+ /**
+ * Tests that calling {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
+ * DeviceStateRequest.Callback)} is successful and results in a registered callback being
+ * triggered with a value equal to the requested state.
+ */
+ @Test
+ public void testRequestStateSucceedsAsTopApp_ifStateDefinedAsAvailableForAppsToRequest() {
+ final DeviceStateManager manager = getDeviceStateManager();
+ final int[] supportedStates = manager.getSupportedStates();
+
+ // We want to verify that the app can change device state
+ // So we only attempt if there are more than 1 possible state.
+ assumeTrue(supportedStates.length > 1);
+ Set<Integer> statesAvailableToRequest = getAvailableStatesToRequest(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), supportedStates);
+ assumeTrue(statesAvailableToRequest.size() > 0);
+
+ Set<Integer> availableDeviceStates = generateDeviceStateSet(supportedStates);
+
+ final StateTrackingCallback callback = new StateTrackingCallback();
+ manager.registerCallback(Runnable::run, callback);
+ PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != INVALID_DEVICE_STATE);
+ final TestActivitySession<DeviceStateTestActivity> activitySession =
+ createManagedTestActivitySession();
+
+ activitySession.launchTestActivityOnDisplaySync(
+ DeviceStateTestActivity.class,
+ DEFAULT_DISPLAY
+ );
+
+ DeviceStateTestActivity activity = activitySession.getActivity();
+
+ Set<Integer> possibleStates = possibleStates(true /* shouldSucceed */,
+ availableDeviceStates,
+ statesAvailableToRequest);
+ int nextState = calculateDifferentState(callback.mCurrentState, possibleStates);
+ // checks that we were able to find a valid state to request.
+ assumeTrue(nextState != INVALID_DEVICE_STATE);
+
+ activity.requestDeviceStateChange(nextState);
+
+ PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState == nextState);
+
+ assertEquals(nextState, callback.mCurrentState);
assertFalse(activity.requestStateFailed);
+
+ manager.cancelStateRequest(); // reset device state after successful request
}
/**
@@ -163,10 +230,15 @@
// We want to verify that the app can change device state
// So we only attempt if there are more than 1 possible state.
assumeTrue(supportedStates.length > 1);
+ Set<Integer> statesAvailableToRequest = getAvailableStatesToRequest(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), supportedStates);
+ assumeTrue(statesAvailableToRequest.size() > 0);
+
+ Set<Integer> availableDeviceStates = generateDeviceStateSet(supportedStates);
final StateTrackingCallback callback = new StateTrackingCallback();
manager.registerCallback(Runnable::run, callback);
- PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != -1);
+ PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != INVALID_DEVICE_STATE);
final TestActivitySession<DeviceStateTestActivity> activitySession =
createManagedTestActivitySession();
@@ -180,8 +252,14 @@
launchHomeActivity(); // places our test activity in the background
- int requestedState = determineNewState(callback.mCurrentState, supportedStates);
- activity.requestDeviceStateChange(requestedState);
+ Set<Integer> possibleStates = possibleStates(true /* shouldSucceed */,
+ availableDeviceStates,
+ statesAvailableToRequest);
+ int nextState = calculateDifferentState(callback.mCurrentState, possibleStates);
+ // checks that we were able to find a valid state to request.
+ assumeTrue(nextState != INVALID_DEVICE_STATE);
+
+ activity.requestDeviceStateChange(nextState);
assertTrue(activity.requestStateFailed);
}
@@ -197,10 +275,15 @@
// We want to verify that the app can change device state
// So we only attempt if there are more than 1 possible state.
assumeTrue(supportedStates.length > 1);
+ Set<Integer> statesAvailableToRequest = getAvailableStatesToRequest(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), supportedStates);
+ assumeFalse(statesAvailableToRequest.isEmpty());
+
+ Set<Integer> availableDeviceStates = generateDeviceStateSet(supportedStates);
final StateTrackingCallback callback = new StateTrackingCallback();
manager.registerCallback(Runnable::run, callback);
- PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != -1);
+ PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != INVALID_DEVICE_STATE);
final TestActivitySession<DeviceStateTestActivity> activitySession =
createManagedTestActivitySession();
@@ -212,12 +295,19 @@
DeviceStateTestActivity activity = activitySession.getActivity();
int originalState = callback.mCurrentState;
- int newState = determineNewState(callback.mCurrentState, supportedStates);
- activity.requestDeviceStateChange(newState);
- PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState == newState);
+ Set<Integer> possibleStates = possibleStates(true /* shouldSucceed */,
+ availableDeviceStates,
+ statesAvailableToRequest);
+ int nextState = calculateDifferentState(callback.mCurrentState, possibleStates);
+ // checks that we were able to find a valid state to request.
+ assumeTrue(nextState != INVALID_DEVICE_STATE);
- assertEquals(newState, callback.mCurrentState);
+ activity.requestDeviceStateChange(nextState);
+
+ PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState == nextState);
+
+ assertEquals(nextState, callback.mCurrentState);
assertFalse(activity.requestStateFailed);
activity.finish();
@@ -230,7 +320,7 @@
);
// verify that the overridden state is still active after finishing
// and launching the second activity.
- assertEquals(newState, callback.mCurrentState);
+ assertEquals(nextState, callback.mCurrentState);
activity = secondActivitySession.getActivity();
activity.cancelOverriddenState();
@@ -240,18 +330,113 @@
assertEquals(originalState, callback.mCurrentState);
}
- // determine what state we should request that isn't the current state
- // throws an IllegalArgumentException if there is no unique states available
- // in the list of supported states
- private static int determineNewState(int currentState, int[] states)
- throws IllegalArgumentException {
- for (int state : states) {
+
+ /**
+ * Reads in the states that are available to be requested by apps from the configuration file
+ * and returns a set of all valid states that are read in.
+ *
+ * @param context The context used to get the configuration values from {@link Resources}
+ * @param supportedStates The device states that are supported on that device.
+ * @return {@link Set} of valid device states that are read in.
+ */
+ private static Set<Integer> getAvailableStatesToRequest(Context context,
+ int[] supportedStates) {
+ Set<Integer> availableStatesToRequest = new HashSet<>();
+ String[] availableStateIdentifiers = context.getResources().getStringArray(
+ Resources.getSystem().getIdentifier("config_deviceStatesAvailableForAppRequests",
+ "array",
+ "android"));
+ for (String identifier : availableStateIdentifiers) {
+ int stateIdentifier = context.getResources()
+ .getIdentifier(identifier, "integer", "android");
+ int state = context.getResources().getInteger(stateIdentifier);
+ if (isValidState(state, supportedStates)) {
+ availableStatesToRequest.add(context.getResources().getInteger(stateIdentifier));
+ }
+ }
+ return availableStatesToRequest;
+ }
+
+ private static boolean isValidState(int state, int[] supportedStates) {
+ for (int i = 0; i < supportedStates.length; i++) {
+ if (state == supportedStates[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Generates a set of possible device states based on a {@link Set} of valid device states,
+ * {@code supportedDeviceStates}, and the set of device states available to be requested
+ * {@code availableStatesToRequest}, as well as if the request should succeed or not, given by
+ * {@code shouldSucceed}.
+ *
+ * If {@code shouldSucceed} is {@code true}, we only return device states that are available,
+ * and if it is {@code false}, we only return non available device states.
+ *
+ * @param availableStatesToRequest The states that are available to be requested from an app
+ * @param shouldSucceed Should the request succeed or not, to determine what states we return
+ * @param supportedDeviceStates All states supported on the device.
+ * {@throws} an {@link IllegalArgumentException} if {@code availableStatesToRequest} includes
+ * non-valid device states.
+ */
+ private static Set<Integer> possibleStates(boolean shouldSucceed,
+ Set<Integer> supportedDeviceStates,
+ Set<Integer> availableStatesToRequest) {
+
+ if (!supportedDeviceStates.containsAll(availableStatesToRequest)) {
+ throw new IllegalArgumentException("Available states include invalid device states");
+ }
+
+ Set<Integer> availableStates = new HashSet<>(supportedDeviceStates);
+
+ if (shouldSucceed) {
+ availableStates.retainAll(availableStatesToRequest);
+ } else {
+ availableStates.removeAll(availableStatesToRequest);
+ }
+
+ return availableStates;
+ }
+
+ /**
+ * Determines what state we should request that isn't the current state, and is included
+ * in {@code possibleStates}. If there is no state that fits these requirements, we return
+ * {@link INVALID_DEVICE_STATE}.
+ *
+ * @param currentState The current state of the device
+ * @param possibleStates States that we can request
+ */
+ private static int calculateDifferentState(int currentState, Set<Integer> possibleStates) {
+ if (possibleStates.isEmpty()) {
+ return INVALID_DEVICE_STATE;
+ }
+ if (possibleStates.size() == 1 && possibleStates.contains(currentState)) {
+ return INVALID_DEVICE_STATE;
+ }
+ for (int state: possibleStates) {
if (state != currentState) {
return state;
}
}
- throw new IllegalArgumentException(
- "No unique supported states besides our current state were found");
+ return INVALID_DEVICE_STATE;
+ }
+
+ /**
+ * Creates a {@link Set} of values that are in the {@code states} array.
+ *
+ * Used to create a {@link Set} from the available device states that {@link DeviceStateManager}
+ * returns as an array.
+ *
+ * @param states Device states that are supported on the device
+ */
+ private static Set<Integer> generateDeviceStateSet(int[] states) {
+ Set<Integer> supportedStates = new HashSet<>();
+ for (int i = 0; i < states.length; i++) {
+ supportedStates.add(states[i]);
+ }
+ return supportedStates;
}
/**
diff --git a/tests/framework/base/biometrics/Android.bp b/tests/framework/base/biometrics/Android.bp
index 66c8134..1ec7d5b 100644
--- a/tests/framework/base/biometrics/Android.bp
+++ b/tests/framework/base/biometrics/Android.bp
@@ -22,6 +22,7 @@
// Tag this module as a cts test artifact
test_suites: [
"cts",
+ "sts",
"vts10",
"general-tests",
],
@@ -45,6 +46,7 @@
srcs: ["src/**/*.java"],
data: [
":CtsBiometricServiceTestApp",
+ ":CtsBiometricServiceUtilTestApp",
":CtsFingerprintServiceTestApp",
],
sdk_version: "test_current",
diff --git a/tests/framework/base/biometrics/AndroidTest.xml b/tests/framework/base/biometrics/AndroidTest.xml
index 1e7854c..0c020d8 100644
--- a/tests/framework/base/biometrics/AndroidTest.xml
+++ b/tests/framework/base/biometrics/AndroidTest.xml
@@ -24,6 +24,7 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsBiometricsTestCases.apk" />
<option name="test-file-name" value="CtsBiometricServiceTestApp.apk" />
+ <option name="test-file-name" value="CtsBiometricServiceUtilTestApp.apk" />
<option name="test-file-name" value="CtsFingerprintServiceTestApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/framework/base/biometrics/OWNERS b/tests/framework/base/biometrics/OWNERS
index 14fbfd7..95749d0 100644
--- a/tests/framework/base/biometrics/OWNERS
+++ b/tests/framework/base/biometrics/OWNERS
@@ -5,4 +5,3 @@
jaggies@google.com
jbolinger@google.com
joshmccloskey@google.com
-kchyn@google.com
diff --git a/tests/framework/base/biometrics/apps/biometrics/OWNERS b/tests/framework/base/biometrics/apps/biometrics/OWNERS
index 15711bb..95749d0 100644
--- a/tests/framework/base/biometrics/apps/biometrics/OWNERS
+++ b/tests/framework/base/biometrics/apps/biometrics/OWNERS
@@ -1,2 +1,7 @@
# Bug component: 879035
-kchyn@google.com
\ No newline at end of file
+
+graciecheng@google.com
+ilyamaty@google.com
+jaggies@google.com
+jbolinger@google.com
+joshmccloskey@google.com
diff --git a/tests/framework/base/biometrics/apps/fingerprint/Android.bp b/tests/framework/base/biometrics/apps/fingerprint/Android.bp
index 295404e..ef7ceb4a 100644
--- a/tests/framework/base/biometrics/apps/fingerprint/Android.bp
+++ b/tests/framework/base/biometrics/apps/fingerprint/Android.bp
@@ -34,6 +34,7 @@
test_suites: [
"cts",
+ "sts",
"vts10",
"general-tests",
],
diff --git a/tests/framework/base/biometrics/apps/fingerprint/OWNERS b/tests/framework/base/biometrics/apps/fingerprint/OWNERS
index 15711bb..95749d0 100644
--- a/tests/framework/base/biometrics/apps/fingerprint/OWNERS
+++ b/tests/framework/base/biometrics/apps/fingerprint/OWNERS
@@ -1,2 +1,7 @@
# Bug component: 879035
-kchyn@google.com
\ No newline at end of file
+
+graciecheng@google.com
+ilyamaty@google.com
+jaggies@google.com
+jbolinger@google.com
+joshmccloskey@google.com
diff --git a/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java b/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java
index 85a9230..c870e6b 100644
--- a/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java
+++ b/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java
@@ -28,7 +28,6 @@
*/
@SuppressWarnings("deprecation")
public class AuthOnCreateActivity extends Activity {
- private static final String TAG = "AuthOnCreateActivity";
@Override
protected void onCreate(@Nullable Bundle bundle) {
diff --git a/tests/framework/base/biometrics/apps/util/Android.bp b/tests/framework/base/biometrics/apps/util/Android.bp
new file mode 100644
index 0000000..5fa1367
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/util/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"],
+}
+
+android_test {
+ name: "CtsBiometricServiceUtilTestApp",
+ defaults: ["cts_support_defaults"],
+
+ static_libs: [
+ "cts-biometric-util",
+ "cts-wm-app-base",
+ ],
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ sdk_version: "test_current",
+ per_testcase_directory: true,
+
+ test_suites: [
+ "cts",
+ "sts",
+ "vts10",
+ "general-tests",
+ ],
+}
diff --git a/tests/framework/base/biometrics/apps/util/AndroidManifest.xml b/tests/framework/base/biometrics/apps/util/AndroidManifest.xml
new file mode 100644
index 0000000..05d39f2
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/util/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.biometrics.util">
+
+ <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
+
+ <application>
+ <activity
+ android:name="android.server.biometrics.util.EmptyActivity"
+ android:exported="true"/>
+ </application>
+
+</manifest>
diff --git a/tests/framework/base/biometrics/apps/util/OWNERS b/tests/framework/base/biometrics/apps/util/OWNERS
new file mode 100644
index 0000000..95749d0
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/util/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 879035
+
+graciecheng@google.com
+ilyamaty@google.com
+jaggies@google.com
+jbolinger@google.com
+joshmccloskey@google.com
diff --git a/tests/framework/base/biometrics/apps/util/res/layout/empty_activity.xml b/tests/framework/base/biometrics/apps/util/res/layout/empty_activity.xml
new file mode 100644
index 0000000..b7c8acb
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/util/res/layout/empty_activity.xml
@@ -0,0 +1,21 @@
+<?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:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+</LinearLayout>
diff --git a/tests/framework/base/biometrics/apps/util/src/android/server/biometrics/util/EmptyActivity.java b/tests/framework/base/biometrics/apps/util/src/android/server/biometrics/util/EmptyActivity.java
new file mode 100644
index 0000000..b5f3d28
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/util/src/android/server/biometrics/util/EmptyActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.biometrics.util;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/** Blank activity. */
+public class EmptyActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.empty_activity);
+ }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
index 6fa9022..1bb7eea 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
@@ -72,7 +72,7 @@
/**
* Base class containing useful functionality. Actual tests should be done in subclasses.
*/
-abstract class BiometricTestBase extends ActivityManagerTestBase {
+abstract class BiometricTestBase extends ActivityManagerTestBase implements TestSessionList.Idler {
private static final String TAG = "BiometricTestBase";
private static final String DUMPSYS_BIOMETRIC = Utils.DUMPSYS_BIOMETRIC;
@@ -108,7 +108,8 @@
super.launchActivity(componentName);
}
- void waitForIdleSensors() {
+ @Override
+ public void waitForIdleSensors() {
try {
Utils.waitForIdleService(this::getSensorStates);
} catch (Exception e) {
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/TestSessionList.java b/tests/framework/base/biometrics/src/android/server/biometrics/TestSessionList.java
index 7ff7431..fda30f9 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/TestSessionList.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/TestSessionList.java
@@ -33,12 +33,18 @@
* Prefer to simply use a try block with a single session when possible.
*/
public class TestSessionList implements AutoCloseable {
- private final BiometricTestBase mTest;
+ private final Idler mIdler;
private final List<BiometricTestSession> mSessions = new ArrayList<>();
private final Map<Integer, BiometricTestSession> mSessionMap = new HashMap<>();
- public TestSessionList(@NonNull BiometricTestBase test) {
- mTest = test;
+ public interface Idler {
+ /** Wait for all sensor to be idle. */
+ void waitForIdleSensors();
+ }
+
+ /** Create a list with the given idler. */
+ public TestSessionList(@NonNull Idler idler) {
+ mIdler = idler;
}
/** Add a session. */
@@ -69,6 +75,6 @@
for (BiometricTestSession session : mSessions) {
session.close();
}
- mTest.waitForIdleSensors();
+ mIdler.waitForIdleSensors();
}
}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
index a81d5c6..7868109 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
@@ -16,12 +16,16 @@
package android.server.biometrics.fingerprint;
-import static android.server.biometrics.SensorStates.SensorState;
-import static android.server.biometrics.SensorStates.UserState;
+import static android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_CANCELED;
+import static android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED;
import static android.server.biometrics.fingerprint.Components.AUTH_ON_CREATE_ACTIVITY;
+import static android.server.biometrics.util.Components.EMPTY_ACTIVITY;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -33,15 +37,21 @@
import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
+import android.platform.test.annotations.AsbSecurityTest;
import android.platform.test.annotations.Presubmit;
import android.server.biometrics.BiometricServiceState;
import android.server.biometrics.SensorStates;
+import android.server.biometrics.TestSessionList;
import android.server.biometrics.Utils;
import android.server.wm.ActivityManagerTestBase;
import android.server.wm.TestJournalProvider.TestJournal;
import android.server.wm.TestJournalProvider.TestJournalContainer;
import android.server.wm.UiDeviceUtils;
-import android.server.wm.WindowManagerState;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -54,16 +64,19 @@
import org.junit.Before;
import org.junit.Test;
-import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
@SuppressWarnings("deprecation")
@Presubmit
-public class FingerprintServiceTest extends ActivityManagerTestBase {
+public class FingerprintServiceTest extends ActivityManagerTestBase
+ implements TestSessionList.Idler {
private static final String TAG = "FingerprintServiceTest";
private static final String DUMPSYS_FINGERPRINT = "dumpsys fingerprint --proto --state";
+ private static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000;
+ private static final long WAIT_MS = 2000;
+ private static final BySelector SELECTOR_BIOMETRIC_PROMPT =
+ By.res("com.android.systemui", "biometric_scrollview");
private SensorStates getSensorStates() throws Exception {
final byte[] dump = Utils.executeShellCommand(DUMPSYS_FINGERPRINT);
@@ -71,6 +84,15 @@
return SensorStates.parseFrom(proto);
}
+ @Override
+ public void waitForIdleSensors() {
+ try {
+ Utils.waitForIdleService(this::getSensorStates);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception when waiting for idle", e);
+ }
+ }
+
@Nullable
private static FingerprintCallbackHelper.State getCallbackState(@NonNull TestJournal journal) {
Utils.waitFor("Waiting for authentication callback",
@@ -94,10 +116,12 @@
@NonNull private Instrumentation mInstrumentation;
@Nullable private FingerprintManager mFingerprintManager;
@NonNull private List<SensorProperties> mSensorProperties;
+ @NonNull private UiDevice mDevice;
@Before
public void setUp() throws Exception {
mInstrumentation = getInstrumentation();
+ mDevice = UiDevice.getInstance(mInstrumentation);
mFingerprintManager = mInstrumentation.getContext()
.getSystemService(FingerprintManager.class);
@@ -110,35 +134,21 @@
// Tests can be skipped on devices without fingerprint sensors
assumeTrue(!mSensorProperties.isEmpty());
+
+ // Turn screen on and dismiss keyguard
+ UiDeviceUtils.pressWakeupButton();
+ UiDeviceUtils.pressUnlockButton();
}
@After
public void cleanup() throws Exception {
- if (mFingerprintManager == null || mSensorProperties.isEmpty()) {
- // The tests were skipped anyway, nothing to clean up. Maybe we can use JUnit test
- // annotations in the future.
+ if (mFingerprintManager == null) {
return;
}
-
mInstrumentation.waitForIdleSync();
Utils.waitForIdleService(this::getSensorStates);
- final SensorStates sensorStates = getSensorStates();
- for (Map.Entry<Integer, SensorState> sensorEntry : sensorStates.sensorStates.entrySet()) {
- for (Map.Entry<Integer, UserState> userEntry
- : sensorEntry.getValue().getUserStates().entrySet()) {
- if (userEntry.getValue().numEnrolled != 0) {
- Log.w(TAG, "Cleaning up for sensor: " + sensorEntry.getKey()
- + ", user: " + userEntry.getKey());
- BiometricTestSession session =
- mFingerprintManager.createTestSession(sensorEntry.getKey());
- session.cleanupInternalState(userEntry.getKey());
- session.close();
- }
- }
- }
-
mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
}
@@ -174,75 +184,157 @@
@Test
public void testAuthenticateFromForegroundActivity() throws Exception {
assumeTrue(Utils.isFirstApiLevel29orGreater());
- // Turn screen on and dismiss keyguard
- UiDeviceUtils.pressWakeupButton();
- UiDeviceUtils.pressUnlockButton();
// Manually keep track and close the sessions, since we want to enroll all sensors before
// requesting auth.
- final List<BiometricTestSession> testSessions = new ArrayList<>();
-
final int userId = 0;
- for (SensorProperties prop : mSensorProperties) {
- BiometricTestSession session =
- mFingerprintManager.createTestSession(prop.getSensorId());
- testSessions.add(session);
+ try (TestSessionList testSessions = createTestSessionsWithEnrollments(userId)) {
+ final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
- session.startEnroll(userId);
+ // Launch test activity
+ launchActivity(AUTH_ON_CREATE_ACTIVITY);
+ mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY, STATE_RESUMED);
mInstrumentation.waitForIdleSync();
- Utils.waitForIdleService(this::getSensorStates);
- session.finishEnroll(userId);
+ // At least one sensor should be authenticating
+ assertFalse(getSensorStates().areAllSensorsIdle());
+
+ // Nothing happened yet
+ FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
+ assertNotNull(callbackState);
+ assertEquals(0, callbackState.mNumAuthRejected);
+ assertEquals(0, callbackState.mNumAuthAccepted);
+ assertEquals(0, callbackState.mAcquiredReceived.size());
+ assertEquals(0, callbackState.mErrorsReceived.size());
+
+ // Auth and check again now
+ testSessions.first().acceptAuthentication(userId);
mInstrumentation.waitForIdleSync();
- Utils.waitForIdleService(this::getSensorStates);
- }
-
- final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
-
- // Launch test activity
- launchActivity(AUTH_ON_CREATE_ACTIVITY);
- mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY, WindowManagerState.STATE_RESUMED);
- mInstrumentation.waitForIdleSync();
-
- // At least one sensor should be authenticating
- assertFalse(getSensorStates().areAllSensorsIdle());
-
- // Nothing happened yet
- FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
- assertNotNull(callbackState);
- assertEquals(0, callbackState.mNumAuthRejected);
- assertEquals(0, callbackState.mNumAuthAccepted);
- assertEquals(0, callbackState.mAcquiredReceived.size());
- assertEquals(0, callbackState.mErrorsReceived.size());
-
- // Auth and check again now
- testSessions.get(0).acceptAuthentication(userId);
- mInstrumentation.waitForIdleSync();
- callbackState = getCallbackState(journal);
- assertNotNull(callbackState);
- assertTrue(callbackState.mErrorsReceived.isEmpty());
- assertTrue(callbackState.mAcquiredReceived.isEmpty());
- assertEquals(1, callbackState.mNumAuthAccepted);
- assertEquals(0, callbackState.mNumAuthRejected);
-
- // Cleanup
- for (BiometricTestSession session : testSessions) {
- session.close();
+ callbackState = getCallbackState(journal);
+ assertNotNull(callbackState);
+ assertTrue(callbackState.mErrorsReceived.isEmpty());
+ assertTrue(callbackState.mAcquiredReceived.isEmpty());
+ assertEquals(1, callbackState.mNumAuthAccepted);
+ assertEquals(0, callbackState.mNumAuthRejected);
}
}
@Test
public void testRejectThenErrorFromForegroundActivity() throws Exception {
assumeTrue(Utils.isFirstApiLevel29orGreater());
- // Turn screen on and dismiss keyguard
- UiDeviceUtils.pressWakeupButton();
- UiDeviceUtils.pressUnlockButton();
// Manually keep track and close the sessions, since we want to enroll all sensors before
// requesting auth.
- final List<BiometricTestSession> testSessions = new ArrayList<>();
+ final int userId = 0;
+ try (TestSessionList testSessions = createTestSessionsWithEnrollments(userId)) {
+ final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
+
+ // Launch test activity
+ launchActivity(AUTH_ON_CREATE_ACTIVITY);
+ mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY,
+ STATE_RESUMED);
+ mInstrumentation.waitForIdleSync();
+ FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
+ assertNotNull(callbackState);
+
+ // Fingerprint rejected
+ testSessions.first().rejectAuthentication(userId);
+ mInstrumentation.waitForIdleSync();
+ callbackState = getCallbackState(journal);
+ assertNotNull(callbackState);
+ assertEquals(1, callbackState.mNumAuthRejected);
+ assertEquals(0, callbackState.mNumAuthAccepted);
+ assertEquals(0, callbackState.mAcquiredReceived.size());
+ assertEquals(0, callbackState.mErrorsReceived.size());
+
+ // Send an acquire message
+ // skip this check on devices with UDFPS because they prompt to try again
+ // and do not dispatch an acquired event via BiometricPrompt
+ final boolean verifyPartial = !hasUdfps();
+ if (verifyPartial) {
+ testSessions.first().notifyAcquired(userId,
+ FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL);
+ mInstrumentation.waitForIdleSync();
+ callbackState = getCallbackState(journal);
+ assertNotNull(callbackState);
+ assertEquals(1, callbackState.mNumAuthRejected);
+ assertEquals(0, callbackState.mNumAuthAccepted);
+ assertEquals(1, callbackState.mAcquiredReceived.size());
+ assertEquals(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
+ (int) callbackState.mAcquiredReceived.get(0));
+ assertEquals(0, callbackState.mErrorsReceived.size());
+ }
+
+ // Send an error
+ testSessions.first().notifyError(userId, FINGERPRINT_ERROR_CANCELED);
+ mInstrumentation.waitForIdleSync();
+ callbackState = getCallbackState(journal);
+ assertNotNull(callbackState);
+ assertEquals(1, callbackState.mNumAuthRejected);
+ assertEquals(0, callbackState.mNumAuthAccepted);
+ if (verifyPartial) {
+ assertEquals(1, callbackState.mAcquiredReceived.size());
+ assertEquals(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
+ (int) callbackState.mAcquiredReceived.get(0));
+ } else {
+ assertEquals(0, callbackState.mAcquiredReceived.size());
+ }
+ assertEquals(1, callbackState.mErrorsReceived.size());
+ assertEquals(FINGERPRINT_ERROR_CANCELED,
+ (int) callbackState.mErrorsReceived.get(0));
+
+ // Authentication lifecycle is done
+ assertTrue(getSensorStates().areAllSensorsIdle());
+ }
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 214261879)
+ public void testAuthCancelsWhenAppSwitched() throws Exception {
+ assumeTrue(Utils.isFirstApiLevel29orGreater());
final int userId = 0;
+ try (TestSessionList testSessions = createTestSessionsWithEnrollments(userId)) {
+ launchActivity(AUTH_ON_CREATE_ACTIVITY);
+ final UiObject2 prompt = mDevice.wait(
+ Until.findObject(SELECTOR_BIOMETRIC_PROMPT), WAIT_MS);
+ if (prompt == null) {
+ // some devices do not show a prompt (i.e. rear sensor)
+ mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY, STATE_RESUMED);
+ }
+ assertThat(getSensorStates().areAllSensorsIdle()).isFalse();
+
+ launchActivity(EMPTY_ACTIVITY);
+ if (prompt != null) {
+ assertThat(mDevice.wait(Until.gone(SELECTOR_BIOMETRIC_PROMPT), WAIT_MS)).isTrue();
+ } else {
+ // devices that do not show a sysui prompt may not cancel until an attempt is made
+ mWmState.waitForActivityState(EMPTY_ACTIVITY, STATE_RESUMED);
+ testSessions.first().acceptAuthentication(userId);
+ mInstrumentation.waitForIdleSync();
+ }
+ waitForIdleSensors();
+
+ final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
+ FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
+ assertThat(callbackState).isNotNull();
+ assertThat(callbackState.mNumAuthAccepted).isEqualTo(0);
+ assertThat(callbackState.mNumAuthRejected).isEqualTo(0);
+
+ // FingerprintUtils#isKnownErrorCode does not recognize FINGERPRINT_ERROR_USER_CANCELED
+ // so accept this error as a vendor error or the normal value
+ assertThat(callbackState.mErrorsReceived).hasSize(1);
+ assertThat(callbackState.mErrorsReceived.get(0)).isAnyOf(
+ FINGERPRINT_ERROR_CANCELED,
+ FINGERPRINT_ERROR_USER_CANCELED,
+ FINGERPRINT_ERROR_VENDOR_BASE + FINGERPRINT_ERROR_USER_CANCELED);
+
+ assertThat(getSensorStates().areAllSensorsIdle()).isTrue();
+ }
+ }
+
+ private TestSessionList createTestSessionsWithEnrollments(int userId) {
+ final TestSessionList testSessions = new TestSessionList(this);
for (SensorProperties prop : mSensorProperties) {
BiometricTestSession session =
mFingerprintManager.createTestSession(prop.getSensorId());
@@ -250,76 +342,13 @@
session.startEnroll(userId);
mInstrumentation.waitForIdleSync();
- Utils.waitForIdleService(this::getSensorStates);
+ waitForIdleSensors();
session.finishEnroll(userId);
mInstrumentation.waitForIdleSync();
- Utils.waitForIdleService(this::getSensorStates);
+ waitForIdleSensors();
}
-
- final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
-
- // Launch test activity
- launchActivity(AUTH_ON_CREATE_ACTIVITY);
- mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY, WindowManagerState.STATE_RESUMED);
- mInstrumentation.waitForIdleSync();
- FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
- assertNotNull(callbackState);
-
- // Fingerprint rejected
- testSessions.get(0).rejectAuthentication(userId);
- mInstrumentation.waitForIdleSync();
- callbackState = getCallbackState(journal);
- assertNotNull(callbackState);
- assertEquals(1, callbackState.mNumAuthRejected);
- assertEquals(0, callbackState.mNumAuthAccepted);
- assertEquals(0, callbackState.mAcquiredReceived.size());
- assertEquals(0, callbackState.mErrorsReceived.size());
-
- // Send an acquire message
- // skip this check on devices with UDFPS because they prompt to try again
- // and do not dispatch an acquired event via BiometricPrompt
- final boolean verifyPartial = !hasUdfps();
- if (verifyPartial) {
- testSessions.get(0).notifyAcquired(userId,
- FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL);
- mInstrumentation.waitForIdleSync();
- callbackState = getCallbackState(journal);
- assertNotNull(callbackState);
- assertEquals(1, callbackState.mNumAuthRejected);
- assertEquals(0, callbackState.mNumAuthAccepted);
- assertEquals(1, callbackState.mAcquiredReceived.size());
- assertEquals(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
- (int) callbackState.mAcquiredReceived.get(0));
- assertEquals(0, callbackState.mErrorsReceived.size());
- }
-
- // Send an error
- testSessions.get(0).notifyError(userId,
- FingerprintManager.FINGERPRINT_ERROR_CANCELED);
- mInstrumentation.waitForIdleSync();
- callbackState = getCallbackState(journal);
- assertNotNull(callbackState);
- assertEquals(1, callbackState.mNumAuthRejected);
- assertEquals(0, callbackState.mNumAuthAccepted);
- if (verifyPartial) {
- assertEquals(1, callbackState.mAcquiredReceived.size());
- assertEquals(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
- (int) callbackState.mAcquiredReceived.get(0));
- } else {
- assertEquals(0, callbackState.mAcquiredReceived.size());
- }
- assertEquals(1, callbackState.mErrorsReceived.size());
- assertEquals(FingerprintManager.FINGERPRINT_ERROR_CANCELED,
- (int) callbackState.mErrorsReceived.get(0));
-
- // Authentication lifecycle is done
- assertTrue(getSensorStates().areAllSensorsIdle());
-
- // Cleanup
- for (BiometricTestSession session : testSessions) {
- session.close();
- }
+ return testSessions;
}
private boolean hasUdfps() throws Exception {
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/util/Components.java b/tests/framework/base/biometrics/src/android/server/biometrics/util/Components.java
new file mode 100644
index 0000000..5b232e5
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/util/Components.java
@@ -0,0 +1,28 @@
+/*
+ * 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.server.biometrics.util;
+
+import android.content.ComponentName;
+import android.server.wm.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+ public static final ComponentName EMPTY_ACTIVITY = component("EmptyActivity");
+
+ private static ComponentName component(String className) {
+ return component(Components.class, className);
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
index 6ef3ddc..083e72c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
@@ -102,12 +102,9 @@
AtomicLong transitionStartTime = new AtomicLong();
AtomicLong transitionEndTime = new AtomicLong();
- final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
- transitionStartTime.set(SystemClock.elapsedRealtime());
- };
-
- final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
- transitionEndTime.set(SystemClock.elapsedRealtime());
+ final ActivityOptions.OnAnimationStartedListener startedListener = transitionStartTime::set;
+ final ActivityOptions.OnAnimationFinishedListener finishedListener = (t) -> {
+ transitionEndTime.set(t);
latch.countDown();
};
@@ -139,12 +136,9 @@
AtomicLong transitionStartTime = new AtomicLong();
AtomicLong transitionEndTime = new AtomicLong();
- final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
- transitionStartTime.set(SystemClock.elapsedRealtime());
- };
-
- final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
- transitionEndTime.set(SystemClock.elapsedRealtime());
+ final ActivityOptions.OnAnimationStartedListener startedListener = transitionStartTime::set;
+ final ActivityOptions.OnAnimationFinishedListener finishedListener = (t) -> {
+ transitionEndTime.set(t);
latch.countDown();
};
@@ -174,12 +168,9 @@
AtomicLong transitionStartTime = new AtomicLong();
AtomicLong transitionEndTime = new AtomicLong();
- final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
- transitionStartTime.set(SystemClock.elapsedRealtime());
- };
-
- final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
- transitionEndTime.set(SystemClock.elapsedRealtime());
+ final ActivityOptions.OnAnimationStartedListener startedListener = transitionStartTime::set;
+ final ActivityOptions.OnAnimationFinishedListener finishedListener = (t) -> {
+ transitionEndTime.set(t);
latch.countDown();
};
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
index 633fa27..449f28a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
@@ -956,7 +956,7 @@
@AnimRes int enterAnim, @AnimRes int exitAnim) {
ConditionVariable animationsStarted = new ConditionVariable(false);
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, enterAnim, exitAnim,
- 0, mMainHandler, animationsStarted::open, /* finishedListener */ null);
+ 0, mMainHandler, (t) -> animationsStarted.open(), /* finishedListener */ null);
// We're testing the opacity coming from the animation here, not the one declared in the
// activity, so we set its opacity to 1
addActivityOverlay(packageName, /* opacity */ 1, touchable, options.toBundle());
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 87cd773..c5ef7d0 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -53,12 +53,15 @@
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresPermission;
import com.android.compatibility.common.util.PollingCheck;
import org.junit.AssumptionViolatedException;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -85,6 +88,8 @@
private final HandlerThread mHandlerThread = new HandlerThread("EventReceiver");
+ private final List<Intent> mStickyBroadcasts = new ArrayList<>();
+
private static final class EventStore {
private static final int INITIAL_ARRAY_SIZE = 32;
@@ -319,6 +324,9 @@
public void close() throws Exception {
mActive.set(false);
+ mStickyBroadcasts.forEach(mContext::removeStickyBroadcast);
+ mStickyBroadcasts.clear();
+
executeShellCommand(mUiAutomation, "ime reset");
PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
@@ -345,14 +353,43 @@
private ImeCommand callCommandInternal(@NonNull String commandName, @NonNull Bundle params) {
final ImeCommand command = new ImeCommand(
commandName, SystemClock.elapsedRealtimeNanos(), true, params);
+ final Intent intent = createCommandIntent(command);
+ mContext.sendBroadcast(intent);
+ return command;
+ }
+
+ /**
+ * A variant of {@link #callCommandInternal} that uses
+ * {@link Context#sendStickyBroadcast(android.content.Intent) sendStickyBroadcast} to ensure
+ * that the command is received even if the IME is not running at the time of sending
+ * (e.g. when {@code config_preventImeStartupUnlessTextEditor} is set).
+ * <p>
+ * The caller requires the {@link android.Manifest.permission#BROADCAST_STICKY BROADCAST_STICKY}
+ * permission.
+ */
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY)
+ private ImeCommand callCommandInternalSticky(
+ @NonNull String commandName,
+ @NonNull Bundle params) {
+ final ImeCommand command = new ImeCommand(
+ commandName, SystemClock.elapsedRealtimeNanos(), true, params);
+ final Intent intent = createCommandIntent(command);
+ mStickyBroadcasts.add(intent);
+ mContext.sendStickyBroadcast(intent);
+ return command;
+ }
+
+ @NonNull
+ private Intent createCommandIntent(@NonNull ImeCommand command) {
final Intent intent = new Intent();
intent.setPackage(MockIme.getComponentName().getPackageName());
intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
intent.putExtras(command.toBundle());
- mContext.sendBroadcast(intent);
- return command;
+ return intent;
}
+
/**
* Lets {@link MockIme} suspend {@link MockIme.AbstractInputMethodImpl#createSession(
* android.view.inputmethod.InputMethod.SessionCallback)} until {@link #resumeCreateSession()}.
@@ -1395,8 +1432,9 @@
}
@NonNull
+ @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY)
public ImeCommand callSetInlineSuggestionsExtras(@NonNull Bundle bundle) {
- return callCommandInternal("setInlineSuggestionsExtras", bundle);
+ return callCommandInternalSticky("setInlineSuggestionsExtras", bundle);
}
@NonNull
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
index c0e1546..9292f6a9 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
@@ -92,6 +92,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -363,6 +364,22 @@
}
/**
+ * A utility method to run a unit test for {@link InputConnection}.
+ *
+ * <p>This utility method enables you to avoid boilerplate code when writing unit tests for
+ * {@link InputConnection}.</p>
+ *
+ * @param inputConnectionWrapperProvider {@link Function} to install custom hooks to the
+ * original {@link InputConnection}.
+ * @param testProcedure Test body.
+ */
+ private void testInputConnection(
+ Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
+ TestProcedureForMixedImes testProcedure) throws Exception {
+ testInputConnection(inputConnectionWrapperProvider, testProcedure, null);
+ }
+
+ /**
* A utility method to run a unit test for {@link InputConnection} with
* {@link android.accessibilityservice.InputMethod}.
*
@@ -2607,6 +2624,75 @@
}
/**
+ * Test {@link android.accessibilityservice.InputMethod.AccessibilityInputConnection#commitText(
+ * CharSequence, int, TextAttribute)} finishes any existing composing text.
+ */
+ @Test
+ public void testCommitTextFromA11yFinishesExistingComposition() throws Exception {
+ final MethodCallVerifier endBatchEditVerifier = new MethodCallVerifier();
+ final CopyOnWriteArrayList<String> callHistory = new CopyOnWriteArrayList<>();
+
+ final class Wrapper extends InputConnectionWrapper {
+ private int mBatchEditCount = 0;
+
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition,
+ TextAttribute textAttribute) {
+ callHistory.add("setComposingText");
+ return true;
+ }
+
+ @Override
+ public boolean beginBatchEdit() {
+ callHistory.add("beginBatchEdit");
+ ++mBatchEditCount;
+ return true;
+ }
+
+ @Override
+ public boolean finishComposingText() {
+ callHistory.add("finishComposingText");
+ return true;
+ }
+
+ @Override
+ public boolean commitText(
+ CharSequence text, int newCursorPosition, TextAttribute textAttribute) {
+ callHistory.add("commitText");
+ return true;
+ }
+
+ @Override
+ public boolean endBatchEdit() {
+ callHistory.add("endBatchEdit");
+ --mBatchEditCount;
+ final boolean batchEditStillInProgress = mBatchEditCount > 0;
+ if (!batchEditStillInProgress) {
+ endBatchEditVerifier.onMethodCalled(args -> { });
+ }
+ return batchEditStillInProgress;
+ }
+ }
+
+ testInputConnection(Wrapper::new, (imeSession, imeStream, a11ySession, a11yStream) -> {
+ expectCommand(imeStream, imeSession.callSetComposingText("fromIme", 1, null), TIMEOUT);
+ expectA11yImeCommand(a11yStream, a11ySession.callCommitText("fromA11y", 1, null),
+ TIMEOUT);
+ endBatchEditVerifier.expectCalledOnce(args -> { }, TIMEOUT);
+ assertThat(callHistory).containsExactly(
+ "setComposingText",
+ "beginBatchEdit",
+ "finishComposingText",
+ "commitText",
+ "endBatchEdit").inOrder();
+ });
+ }
+
+ /**
* Test {@link InputConnection#setComposingText(CharSequence, int)} works as expected.
*/
@Test
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 771739b..3591ffa 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -104,6 +104,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -336,6 +338,40 @@
}
@Test
+ public void testShowHideKeyboardWithInterval() throws Exception {
+ final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
+ .getTargetContext().getSystemService(InputMethodManager.class);
+
+ try (MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+ final String marker = getTestMarker();
+ final EditText editText = launchTestActivity(marker);
+ expectImeInvisible(TIMEOUT);
+
+ runOnMainSync(() -> imm.showSoftInput(editText, 0));
+ expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+ expectImeVisible(TIMEOUT);
+
+ // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
+ final List<Integer> intervals = new ArrayList<>();
+ for (int i = 10; i < 100; i += 10) intervals.add(i);
+ for (int i = 100; i < 500; i += 50) intervals.add(i);
+ // Regression test for b/221483132.
+ // WindowInsetsController tries to clean up IME window after IME hide animation is done.
+ // Makes sure that IMM#showSoftInput during IME hide animation cancels the cleanup.
+ for (int intervalMillis : intervals) {
+ runOnMainSync(() -> imm.hideSoftInputFromWindow(editText.getWindowToken(), 0));
+ SystemClock.sleep(intervalMillis);
+ runOnMainSync(() -> imm.showSoftInput(editText, 0));
+ expectImeVisible(TIMEOUT, "IME should be visible. Interval = " + intervalMillis);
+ }
+ }
+ }
+
+ @Test
public void testShowSoftInputWithShowForcedFlagWhenAppIsLeaving() throws Exception {
final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
.getTargetContext().getSystemService(InputMethodManager.class);
diff --git a/tests/inputmethod/util/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java b/tests/inputmethod/util/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
index c04035a..0185cd8 100644
--- a/tests/inputmethod/util/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
+++ b/tests/inputmethod/util/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
@@ -102,6 +102,17 @@
}
/**
+ * Asserts that {@link com.android.cts.mockime.MockIme} is visible to the user.
+ *
+ * @param timeout timeout in milliseconds.
+ * @param message error message shown on failure.
+ * @see #expectImeVisible(long)
+ */
+ public static void expectImeVisible(long timeout, String message) {
+ assertTrue(message, waitUntil(timeout, InputMethodVisibilityVerifier::containsWatermark));
+ }
+
+ /**
* Asserts that {@link com.android.cts.mockime.MockIme} is not visible to the user.
*
* <p>This always succeeds when
diff --git a/tests/quickaccesswallet/AndroidTest.xml b/tests/quickaccesswallet/AndroidTest.xml
index 98876d7..bc25c3c 100644
--- a/tests/quickaccesswallet/AndroidTest.xml
+++ b/tests/quickaccesswallet/AndroidTest.xml
@@ -30,6 +30,7 @@
<option name="test-file-name" value="CtsQuickAccessWalletTestCases.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.quickaccesswallet.cts" />
+ <option name="package" value="android.quickaccesswallet.cts" />
+ <option name="test-timeout" value="300000" />
</test>
</configuration>
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 c16bd04..2bc63f7 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
@@ -19,10 +19,9 @@
import android.app.Instrumentation;
import android.signature.cts.ApiComplianceChecker;
import android.signature.cts.ApiDocumentParser;
+import android.signature.cts.JDiffClassDescription;
import android.signature.cts.VirtualPath;
-import android.signature.cts.VirtualPath.LocalFilePath;
import androidx.test.platform.app.InstrumentationRegistry;
-import java.io.IOException;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
@@ -61,6 +60,21 @@
}
/**
+ * Return a stream of {@link JDiffClassDescription} that are expected to be provided by the
+ * shared libraries which are installed on this device.
+ *
+ * @param apiDocumentParser the parser to use.
+ * @param apiResources the list of API resource files.
+ * @return a stream of {@link JDiffClassDescription}.
+ */
+ private Stream<JDiffClassDescription> parseActiveSharedLibraryApis(
+ ApiDocumentParser apiDocumentParser, String[] apiResources) {
+ return retrieveApiResourcesAsStream(getClass().getClassLoader(), apiResources)
+ .filter(this::checkLibrary)
+ .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
@@ -73,7 +87,7 @@
ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
- parseApiResourcesAsStream(apiDocumentParser, expectedApiFiles)
+ parseActiveSharedLibraryApis(apiDocumentParser, expectedApiFiles)
.forEach(complianceChecker::checkSignatureCompliance);
// After done parsing all expected API files, perform any deferred checks.
@@ -92,7 +106,7 @@
ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
- parseApiResourcesAsStream(apiDocumentParser, previousApiFiles)
+ parseActiveSharedLibraryApis(apiDocumentParser, previousApiFiles)
.map(clazz -> clazz.setPreviousApiFlag(true))
.forEach(complianceChecker::checkSignatureCompliance);
@@ -105,10 +119,11 @@
* Check to see if the supplied name is an API file for a shared library that is available on
* this device.
*
- * @param name the name of the possible API file for a shared library.
- * @return true if it is, false otherwise.
+ * @param path the path of the API file.
+ * @return true if the API corresponds to a shared library on the device, false otherwise.
*/
- private boolean checkLibrary (String name) {
+ private boolean checkLibrary (VirtualPath path) {
+ String name = path.toString();
String libraryName = name.substring(name.lastIndexOf('/') + 1).split("-")[0];
boolean matched = libraries.contains(libraryName);
if (matched) {
@@ -122,19 +137,4 @@
}
return matched;
}
-
- /**
- * Override the method that gets the files from a supplied zip file to filter out any file that
- * does not correspond to a shared library available on the device.
- *
- * @param path the path to the zip file.
- * @return a stream of paths in the zip file that contain APIs that should be available to this
- * tests.
- * @throws IOException if there was an issue reading the zip file.
- */
- @Override
- protected Stream<VirtualPath> getZipEntryFiles(LocalFilePath path) throws IOException {
- // Only return entries corresponding to shared libraries.
- return super.getZipEntryFiles(path).filter(p -> checkLibrary(p.toString()));
- }
}
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 5bfe0bb..f4d364b 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
@@ -41,6 +41,7 @@
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
+import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.zip.ZipFile;
import org.junit.Before;
@@ -191,10 +192,10 @@
return argument.split(",");
}
- private Stream<VirtualPath> readResource(String resourceName) {
+ private static Stream<VirtualPath> readResource(ClassLoader classLoader, String resourceName) {
try {
ResourcePath resourcePath =
- VirtualPath.get(getClass().getClassLoader(), resourceName);
+ VirtualPath.get(classLoader, resourceName);
if (resourceName.endsWith(".zip")) {
// Extract to a temporary file and read from there.
Path file = extractResourceToFile(resourceName, resourcePath.newInputStream());
@@ -207,7 +208,7 @@
}
}
- Path extractResourceToFile(String resourceName, InputStream is) throws IOException {
+ 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);
@@ -220,7 +221,7 @@
* Given a path in the local file system (possibly of a zip file) flatten it into a stream of
* virtual paths.
*/
- private Stream<VirtualPath> flattenPaths(LocalFilePath path) {
+ private static Stream<VirtualPath> flattenPaths(LocalFilePath path) {
try {
if (path.toString().endsWith(".zip")) {
return getZipEntryFiles(path);
@@ -232,20 +233,46 @@
}
}
+ /**
+ * Create a stream of {@link JDiffClassDescription} by parsing a set of API resource files.
+ *
+ * @param apiDocumentParser the parser to use.
+ * @param apiResources the list of API resource files.
+ *
+ * @return the stream of {@link JDiffClassDescription}.
+ */
Stream<JDiffClassDescription> parseApiResourcesAsStream(
ApiDocumentParser apiDocumentParser, String[] apiResources) {
- return Stream.of(apiResources)
- .flatMap(this::readResource)
+ return retrieveApiResourcesAsStream(getClass().getClassLoader(), apiResources)
.flatMap(apiDocumentParser::parseAsStream);
}
/**
+ * Retrieve a stream of {@link VirtualPath} from a list of API resource files.
+ *
+ * <p>Any zip files are flattened, i.e. if a resource name ends with {@code .zip} then it is
+ * unpacked into a temporary directory and the paths to the unpacked files are returned instead
+ * of the path to the zip file.</p>
+ *
+ * @param classLoader the {@link ClassLoader} from which the resources will be loaded.
+ * @param apiResources the list of API resource files.
+ *
+ * @return the stream of {@link VirtualPath}.
+ */
+ static Stream<VirtualPath> retrieveApiResourcesAsStream(
+ 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
*/
- protected Stream<VirtualPath> getZipEntryFiles(LocalFilePath path) throws IOException {
+ 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));
diff --git a/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java b/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
index efd574e..ddbc1d5 100644
--- a/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
+++ b/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
@@ -91,6 +91,12 @@
for (String activeIntent : activeIntents) {
String intent = activeIntent.trim();
+
+ // STOPSHIP Remove this. b/230099874
+ if ("android.intent.action.EXPERIMENTAL_IS_ALIAS".equals(intent)) {
+ continue;
+ }
+
if (!platformIntents.contains(intent) &&
intent.startsWith(ANDROID_INTENT_PREFIX)) {
invalidIntents.add(activeIntent);
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
index bb4e6e3..0f1599d 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -36,6 +36,7 @@
import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.MODE_IGNORED
import android.app.AppOpsManager.OnOpChangedListener
+import android.app.AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS
import android.app.AppOpsManager.OPSTR_FINE_LOCATION
import android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA
import android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE
@@ -51,6 +52,7 @@
import android.platform.test.annotations.AppModeFull
import androidx.test.runner.AndroidJUnit4
import androidx.test.InstrumentationRegistry
+import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -608,6 +610,57 @@
assertEquals(MODE_IGNORED, cameraReturn)
}
+ @Test
+ fun testRestrictedSettingsOpsRead() {
+ // Apps without manage appops permission will get security exception if it tries to access
+ // restricted settings ops.
+ Assert.assertThrows(SecurityException::class.java) {
+ mAppOps.unsafeCheckOpRawNoThrow(OPSTR_ACCESS_RESTRICTED_SETTINGS, Process.myUid(),
+ mOpPackageName)
+ }
+ // Apps with manage appops permission (shell) should be able to read restricted settings op
+ // successfully.
+ runWithShellPermissionIdentity {
+ mAppOps.unsafeCheckOpRawNoThrow(OPSTR_ACCESS_RESTRICTED_SETTINGS, Process.myUid(),
+ mOpPackageName)
+ }
+
+ // Normal apps should not receive op change callback when op is changed.
+ val watcher = mock(OnOpChangedListener::class.java)
+ try {
+ setOpMode(mOpPackageName, OPSTR_ACCESS_RESTRICTED_SETTINGS, MODE_ERRORED)
+
+ mAppOps.startWatchingMode(OPSTR_ACCESS_RESTRICTED_SETTINGS, mOpPackageName, watcher)
+
+ // Make a change to the app op's mode.
+ Mockito.reset(watcher)
+ setOpMode(mOpPackageName, OPSTR_ACCESS_RESTRICTED_SETTINGS, MODE_ALLOWED)
+ verifyZeroInteractions(watcher)
+ } finally {
+ // Clean up registered watcher.
+ mAppOps.stopWatchingMode(watcher)
+ }
+
+ // Apps with manage ops permission (shell) should be able to receive op change callback.
+ runWithShellPermissionIdentity {
+ try {
+ setOpMode(mOpPackageName, OPSTR_ACCESS_RESTRICTED_SETTINGS, MODE_ERRORED)
+
+ mAppOps.startWatchingMode(OPSTR_ACCESS_RESTRICTED_SETTINGS, mOpPackageName,
+ watcher)
+
+ // Make a change to the app op's mode.
+ Mockito.reset(watcher)
+ setOpMode(mOpPackageName, OPSTR_ACCESS_RESTRICTED_SETTINGS, MODE_ALLOWED)
+ verify(watcher, timeout(TIMEOUT_MS))
+ .onOpChanged(OPSTR_ACCESS_RESTRICTED_SETTINGS, mOpPackageName)
+ } finally {
+ // Clean up registered watcher.
+ mAppOps.stopWatchingMode(watcher)
+ }
+ }
+ }
+
private fun runWithShellPermissionIdentity(command: () -> Unit) {
val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
uiAutomation.adoptShellPermissionIdentity()
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
index 8fcd24d..80f4bbd 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
@@ -102,8 +102,8 @@
Parcel out = Parcel.obtain();
out.writeInt(presetIndex);
out.writeString(presetName);
- out.writeBoolean(isAvailable);
out.writeBoolean(isWritable);
+ out.writeBoolean(isAvailable);
out.setDataPosition(0); // reset position of parcel before passing to constructor
return BluetoothHapPresetInfo.CREATOR.createFromParcel(out);
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
index 0153953..b40b52d 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
@@ -48,7 +48,7 @@
// See Page 5 of Generic Audio assigned number specification
private static final byte[] TEST_METADATA_BYTES = {
// length = 0x05, type = 0x03, value = 0x00000001 (front left)
- 0x05, 0x03, 0x00, 0x00, 0x00, 0x01
+ 0x05, 0x03, 0x01, 0x00, 0x00, 0x00
};
private Context mContext;
@@ -126,7 +126,7 @@
new BluetoothLeAudioCodecConfigMetadata.Builder(codecMetadata).build();
assertEquals(codecMetadata, codecMetadataCopy);
assertEquals(TEST_AUDIO_LOCATION_FRONT_LEFT, codecMetadataCopy.getAudioLocation());
- assertArrayEquals(TEST_METADATA_BYTES, codecMetadata.getRawMetadata());
+ assertArrayEquals(codecMetadata.getRawMetadata(), codecMetadataCopy.getRawMetadata());
}
@Test
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java b/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
index a45a377..8667328 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
@@ -83,14 +83,14 @@
case BluetoothProfile.HID_HOST:
return BluetoothProperties.isProfileHidHostEnabled().orElse(false);
case BluetoothProfile.LE_AUDIO:
- return BluetoothProperties.isProfileBapUnicastServerEnabled().orElse(false);
+ return BluetoothProperties.isProfileBapUnicastClientEnabled().orElse(false);
case BluetoothProfile.LE_AUDIO_BROADCAST:
return BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false);
case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT:
return BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false);
// Hidden profile
// case BluetoothProfile.LE_CALL_CONTROL:
- // return BluetoothProperties.isProfileTbsServerEnabled().orElse(false);
+ // return BluetoothProperties.isProfileCcpServerEnabled().orElse(false);
case BluetoothProfile.MAP:
return BluetoothProperties.isProfileMapServerEnabled().orElse(false);
case BluetoothProfile.MAP_CLIENT:
@@ -110,7 +110,7 @@
case BluetoothProfile.SAP:
return BluetoothProperties.isProfileSapServerEnabled().orElse(false);
case BluetoothProfile.VOLUME_CONTROL:
- return BluetoothProperties.isProfileVcServerEnabled().orElse(false);
+ return BluetoothProperties.isProfileVcpControllerEnabled().orElse(false);
default:
return false;
}
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
index b47e25f..3af4c95 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
@@ -891,6 +891,49 @@
}).build().verify(mCarPropertyManager);
}
+ @Test
+ public void testEvChargeSwitchIfSupported() {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_SWITCH,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ }
+
+ @Test
+ public void testEvChargeTimeRemainingIfSupported() {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_TIME_REMAINING,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+ Integer.class).setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> {
+ assertWithMessage(
+ "FUEL_LEVEL Integer value must be greater than or equal 0").that(
+ (Integer) carPropertyValue.getValue()).isAtLeast(0);
+
+ }).build().verify(mCarPropertyManager);
+ }
+
+ @Test
+ public void testEvRegenerativeBrakingStateIfSupported() {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_REGENERATIVE_BRAKING_STATE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> {
+ Integer evRegenerativeBrakingState = (Integer) carPropertyValue.getValue();
+ assertWithMessage("EV_REGENERATIVE_BRAKING_STATE must be a defined state: "
+ + evRegenerativeBrakingState).that(evRegenerativeBrakingState).isIn(
+ ImmutableSet.of(/*EvRegenerativeBrakingState.UNKNOWN=*/0,
+ /*EvRegenerativeBrakingState.DISABLED=*/1,
+ /*EvRegenerativeBrakingState.PARTIALLY_ENABLED=*/2,
+ /*EvRegenerativeBrakingState.FULLY_ENABLED=*/3));
+ }).build().verify(mCarPropertyManager);
+ }
+
+
@SuppressWarnings("unchecked")
@Test
public void testGetProperty() {
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/SystemPropertiesHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SystemPropertiesHelperTest.java
index 9b915f3..2bebd3d 100644
--- a/tests/tests/car_builtin/src/android/car/cts/builtin/os/SystemPropertiesHelperTest.java
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SystemPropertiesHelperTest.java
@@ -16,11 +16,9 @@
package android.car.cts.builtin.os;
-import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import android.car.builtin.os.SystemPropertiesHelper;
-import android.os.SystemProperties;
-import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
@@ -31,15 +29,17 @@
public final class SystemPropertiesHelperTest {
private static final String TAG = SystemPropertiesHelperTest.class.getSimpleName();
- // a temporary SystemProperty for CTS. it will be cleared after device reboot.
- private static final String CTS_TEST_PROPERTY_KEY = "cts.car.builtin_property_helper.String";
+ // a temporary SystemProperty for CTS.
+ private static final String CTS_TEST_PROPERTY_KEY = "dev.android.car.test.cts.builtin_test";
private static final String CTS_TEST_PROPERTY_VAL = "SystemPropertiesHelperTest";
@Test
- public void testSet() {
- SystemPropertiesHelper.set(CTS_TEST_PROPERTY_KEY, CTS_TEST_PROPERTY_VAL);
- String val = SystemProperties.get(CTS_TEST_PROPERTY_KEY);
- Log.d(TAG, val);
- assertThat(val).isEqualTo(CTS_TEST_PROPERTY_VAL);
+ public void testSet_throwsException() {
+ // system properties are protected by SELinux policies. Though properties with "dev."
+ // prefix are accessible via the shell domain and car shell (carservice_app) domain,
+ // they are not accessible via CTS. The java RuntimeException is expected due to access
+ // permission deny.
+ assertThrows(RuntimeException.class,
+ () -> SystemPropertiesHelper.set(CTS_TEST_PROPERTY_KEY, CTS_TEST_PROPERTY_VAL));
}
}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/AssistUtilsHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/AssistUtilsHelperTest.java
index eb5b0e9..a08e050 100644
--- a/tests/tests/car_builtin/src/android/car/cts/builtin/util/AssistUtilsHelperTest.java
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/AssistUtilsHelperTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeTrue;
+
import android.app.Instrumentation;
import android.car.builtin.util.AssistUtilsHelper;
import android.content.Context;
@@ -59,7 +61,10 @@
@Test
public void testOnShownCallback() throws Exception {
SessionShowCallbackHelperImpl callbackHelperImpl = new SessionShowCallbackHelperImpl();
- AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+ boolean isAssistantComponentAvailable = AssistUtilsHelper
+ .showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+ assumeTrue(isAssistantComponentAvailable);
+
callbackHelperImpl.waitForCallback();
assertWithMessage("Voice session shown")
@@ -77,7 +82,10 @@
@Test
public void isSessionRunning_whenSessionIsShown_succeeds() throws Exception {
SessionShowCallbackHelperImpl callbackHelperImpl = new SessionShowCallbackHelperImpl();
- AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+ boolean isAssistantComponentAvailable = AssistUtilsHelper
+ .showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+ assumeTrue(isAssistantComponentAvailable);
+
callbackHelperImpl.waitForCallback();
assertWithMessage("Voice interaction session running")
@@ -92,7 +100,10 @@
AssistUtilsHelper.registerVoiceInteractionSessionListenerHelper(mContext, listener);
SessionShowCallbackHelperImpl callbackHelperImpl = new SessionShowCallbackHelperImpl();
- AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+ boolean isAssistantComponentAvailable = AssistUtilsHelper
+ .showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+ assumeTrue(isAssistantComponentAvailable);
+
callbackHelperImpl.waitForCallback();
listener.waitForSessionChange();
@@ -110,7 +121,10 @@
AssistUtilsHelper.registerVoiceInteractionSessionListenerHelper(mContext, listener);
SessionShowCallbackHelperImpl callbackHelperImpl = new SessionShowCallbackHelperImpl();
- AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+ boolean isAssistantComponentAvailable = AssistUtilsHelper
+ .showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+ assumeTrue(isAssistantComponentAvailable);
+
callbackHelperImpl.waitForCallback();
listener.waitForSessionChange();
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index ffe417c..90aa734 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -1217,7 +1217,10 @@
onBroadcastThread.set(thread);
});
- onBroadcastThread.get().join();
+ final Thread thread = onBroadcastThread.get();
+ if (thread != null) {
+ thread.join();
+ }
}
private void runPackageVerifierTestSync(String expectedResultStartsWith,
diff --git a/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java b/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
index 98de3ee..dcd6855 100644
--- a/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
@@ -311,7 +311,7 @@
private static class RemoteTest implements AutoCloseable {
private static final int SPIN_SLEEP_MS = 500;
- private static final long RESPONSE_TIMEOUT_MS = 60 * 1000;
+ private static final long RESPONSE_TIMEOUT_MS = 120 * 1000;
private final ShellInstallSession mSession;
private final String mTestName;
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 fc5d459..541c68f 100644
--- a/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java
+++ b/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java
@@ -15,25 +15,41 @@
*/
package android.media.cts;
-import android.media.cts.R;
-
import android.app.Activity;
+import android.content.Intent;
+import android.media.cts.R;
+import android.os.Build;
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);
@@ -64,4 +80,26 @@
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
new file mode 100644
index 0000000..394a779
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderPushBlankBuffersOnStopTest.java
@@ -0,0 +1,256 @@
+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/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java b/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java
index 7f38ea4..4afc7aa 100644
--- a/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java
+++ b/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java
@@ -65,6 +65,7 @@
import org.junit.runners.Parameterized;
import java.io.IOException;
+import java.lang.Throwable;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -177,21 +178,36 @@
}
public void onInputBufferAvailable(MediaCodec codec, int ix) {
if (it.hasNext()) {
- Pair<ByteBuffer, BufferInfo> el = it.next();
- el.first.clear();
try {
- codec.getInputBuffer(ix).put(el.first);
- } catch (java.nio.BufferOverflowException e) {
- Log.e(TAG, "cannot fit " + el.first.limit()
- + "-byte encoded buffer into "
- + codec.getInputBuffer(ix).remaining()
- + "-byte input buffer of " + codec.getName()
- + " configured for " + codec.getInputFormat());
- throw e;
+ Pair<ByteBuffer, BufferInfo> el = it.next();
+ el.first.clear();
+ try {
+ codec.getInputBuffer(ix).put(el.first);
+ } catch (java.nio.BufferOverflowException e) {
+ String diagnostic = "cannot fit " + el.first.limit()
+ + "-byte encoded buffer into "
+ + codec.getInputBuffer(ix).remaining()
+ + "-byte input buffer of " + codec.getName()
+ + " configured for " + codec.getInputFormat();
+ Log.e(TAG, diagnostic);
+ errorMsg.set(diagnostic + e);
+ synchronized (condition) {
+ condition.notifyAll();
+ }
+ // no sense trying to enqueue the failed buffer
+ return;
+ }
+ BufferInfo info = el.second;
+ codec.queueInputBuffer(
+ ix, 0, info.size, info.presentationTimeUs, info.flags);
+ } catch (Throwable t) {
+ errorMsg.set("exception in onInputBufferAvailable( "
+ + codec.getName() + "," + ix
+ + "): " + t);
+ synchronized (condition) {
+ condition.notifyAll();
+ }
}
- BufferInfo info = el.second;
- codec.queueInputBuffer(
- ix, 0, info.size, info.presentationTimeUs, info.flags);
}
}
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
diff --git a/tests/tests/os/src/android/os/cts/VibratorManagerTest.java b/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
index 75275d6..67c6003 100644
--- a/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
@@ -19,11 +19,9 @@
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -40,9 +38,9 @@
import android.os.vibrator.VibratorFrequencyProfile;
import android.util.SparseArray;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
@@ -61,8 +59,8 @@
@RunWith(AndroidJUnit4.class)
public class VibratorManagerTest {
@Rule
- public ActivityTestRule<SimpleTestActivity> mActivityRule = new ActivityTestRule<>(
- SimpleTestActivity.class);
+ public ActivityScenarioRule<SimpleTestActivity> mActivityRule =
+ new ActivityScenarioRule<>(SimpleTestActivity.class);
@Rule
public final AdoptShellPermissionsRule mAdoptShellPermissionsRule =
@@ -75,10 +73,6 @@
private static final float TEST_TOLERANCE = 1e-5f;
- private static final float MINIMUM_ACCEPTED_MEASUREMENT_INTERVAL_FREQUENCY = 1f;
- private static final float MINIMUM_ACCEPTED_FREQUENCY = 1f;
- private static final float MAXIMUM_ACCEPTED_FREQUENCY = 1_000f;
-
private static final long CALLBACK_TIMEOUT_MILLIS = 5_000;
private static final VibrationAttributes VIBRATION_ATTRIBUTES =
new VibrationAttributes.Builder()
@@ -134,6 +128,32 @@
}
@Test
+ public void testGetVibratorIds() {
+ // Just make sure it doesn't crash or return null when this is called; we don't really have
+ // a way to test which vibrators will be returned.
+ assertThat(mVibratorManager.getVibratorIds()).isNotNull();
+ assertThat(mVibratorManager.getVibratorIds()).asList().containsNoDuplicates();
+ }
+
+ @Test
+ public void testGetNonExistentVibratorId() {
+ int missingId = Arrays.stream(mVibratorManager.getVibratorIds()).max().orElse(0) + 1;
+ Vibrator vibrator = mVibratorManager.getVibrator(missingId);
+ assertThat(vibrator).isNotNull();
+ assertThat(vibrator.hasVibrator()).isFalse();
+ }
+
+ @Test
+ public void testGetDefaultVibratorIsSameAsVibratorService() {
+ // Note that VibratorTest parameterization relies on these two vibrators being identical.
+ // It only runs vibrator tests on the result of one of the APIs.
+ Vibrator systemVibrator =
+ InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
+ Vibrator.class);
+ assertThat(mVibratorManager.getDefaultVibrator()).isSameInstanceAs(systemVibrator);
+ }
+
+ @Test
public void testCancel() {
mVibratorManager.vibrate(CombinedVibration.createParallel(
VibrationEffect.createOneShot(10_000, VibrationEffect.DEFAULT_AMPLITUDE)));
@@ -145,7 +165,7 @@
@LargeTest
@Test
- public void testVibrateOneShotStartsAndFinishesVibration() {
+ public void testCombinedVibrationOneShotStartsAndFinishesVibration() {
VibrationEffect oneShot =
VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE);
mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot));
@@ -153,7 +173,7 @@
}
@Test
- public void testVibrateOneShotMaxAmplitude() {
+ public void testCombinedVibrationOneShotMaxAmplitude() {
VibrationEffect oneShot = VibrationEffect.createOneShot(500, 255 /* Max amplitude */);
mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot));
assertStartsVibrating();
@@ -163,7 +183,7 @@
}
@Test
- public void testVibrateOneShotMinAmplitude() {
+ public void testCombinedVibrationOneShotMinAmplitude() {
VibrationEffect oneShot = VibrationEffect.createOneShot(100, 1 /* Min amplitude */);
mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot),
VIBRATION_ATTRIBUTES);
@@ -172,7 +192,7 @@
@LargeTest
@Test
- public void testVibrateWaveformStartsAndFinishesVibration() {
+ public void testCombinedVibrationWaveformStartsAndFinishesVibration() {
final long[] timings = new long[]{100, 200, 300, 400, 500};
final int[] amplitudes = new int[]{64, 128, 255, 128, 64};
VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, -1);
@@ -182,7 +202,7 @@
@LargeTest
@Test
- public void testVibrateWaveformRepeats() {
+ public void testCombinedVibrationWaveformRepeats() {
final long[] timings = new long[]{100, 200, 300, 400, 500};
final int[] amplitudes = new int[]{64, 128, 255, 128, 64};
VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
@@ -192,68 +212,60 @@
SystemClock.sleep(2000);
int[] vibratorIds = mVibratorManager.getVibratorIds();
for (int vibratorId : vibratorIds) {
- assertTrue(mVibratorManager.getVibrator(vibratorId).isVibrating());
+ assertThat(mVibratorManager.getVibrator(vibratorId).isVibrating()).isTrue();
}
mVibratorManager.cancel();
assertStopsVibrating();
}
-
@LargeTest
@Test
- public void testVibrateWaveformWithFrequencyStartsAndFinishesVibration() {
- int[] vibratorIds = mVibratorManager.getVibratorIds();
- for (int vibratorId : vibratorIds) {
- Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
- if (!vibrator.hasFrequencyControl()) {
- continue;
- }
- VibratorFrequencyProfile frequencyProfile = vibrator.getFrequencyProfile();
+ public void testCombinedVibrationWaveformWithFrequencyStartsAndFinishesVibration() {
+ Vibrator defaultVibrator = mVibratorManager.getDefaultVibrator();
+ assumeTrue(defaultVibrator.hasFrequencyControl());
- float minFrequency = frequencyProfile.getMinFrequency();
- float maxFrequency = frequencyProfile.getMaxFrequency();
- float resonantFrequency = vibrator.getResonantFrequency();
- float sustainFrequency = Float.isNaN(resonantFrequency)
- ? (maxFrequency + minFrequency) / 2
- : resonantFrequency;
+ VibratorFrequencyProfile frequencyProfile = defaultVibrator.getFrequencyProfile();
+ float minFrequency = frequencyProfile.getMinFrequency();
+ float maxFrequency = frequencyProfile.getMaxFrequency();
+ float resonantFrequency = defaultVibrator.getResonantFrequency();
+ float sustainFrequency = Float.isNaN(resonantFrequency)
+ ? (maxFrequency + minFrequency) / 2
+ : resonantFrequency;
- // Then ramp to zero amplitude at fixed frequency.
- VibrationEffect waveform =
- VibrationEffect.startWaveform(targetAmplitude(0), targetFrequency(minFrequency))
- // Ramp from min to max frequency and from zero to max amplitude.
- .addTransition(Duration.ofMillis(10),
- targetAmplitude(1), targetFrequency(maxFrequency))
- // Ramp back to min frequency and zero amplitude.
- .addTransition(Duration.ofMillis(10),
- targetAmplitude(0), targetFrequency(minFrequency))
- // Then sustain at a fixed frequency and half amplitude.
- .addTransition(Duration.ZERO,
- targetAmplitude(0.5f), targetFrequency(sustainFrequency))
- .addSustain(Duration.ofMillis(20))
- // Ramp from min to max frequency and at max amplitude.
- .addTransition(Duration.ZERO,
- targetAmplitude(1), targetFrequency(minFrequency))
- .addTransition(Duration.ofMillis(10), targetFrequency(maxFrequency))
- // Ramp from max to min amplitude at max frequency.
- .addTransition(Duration.ofMillis(10), targetAmplitude(0))
- .build();
- vibrator.vibrate(waveform);
- assertStartsVibrating(vibratorId);
- assertStopsVibrating();
- }
+ // Then ramp to zero amplitude at fixed frequency.
+ VibrationEffect waveform =
+ VibrationEffect.startWaveform(targetAmplitude(0), targetFrequency(minFrequency))
+ // Ramp from min to max frequency and from zero to max amplitude.
+ .addTransition(Duration.ofMillis(10),
+ targetAmplitude(1), targetFrequency(maxFrequency))
+ // Ramp back to min frequency and zero amplitude.
+ .addTransition(Duration.ofMillis(10),
+ targetAmplitude(0), targetFrequency(minFrequency))
+ // Then sustain at a fixed frequency and half amplitude.
+ .addTransition(Duration.ZERO,
+ targetAmplitude(0.5f), targetFrequency(sustainFrequency))
+ .addSustain(Duration.ofMillis(20))
+ // Ramp from min to max frequency and at max amplitude.
+ .addTransition(Duration.ZERO,
+ targetAmplitude(1), targetFrequency(minFrequency))
+ .addTransition(Duration.ofMillis(10), targetFrequency(maxFrequency))
+ // Ramp from max to min amplitude at max frequency.
+ .addTransition(Duration.ofMillis(10), targetAmplitude(0))
+ .build();
+ mVibratorManager.vibrate(CombinedVibration.createParallel(waveform));
+ assertStartsThenStopsVibrating(50);
}
@Test
- public void testVibrateSingleVibrator() {
+ public void testCombinedVibrationTargetingSingleVibrator() {
int[] vibratorIds = mVibratorManager.getVibratorIds();
- if (vibratorIds.length < 2) {
- return;
- }
+ assumeTrue(vibratorIds.length >= 2);
VibrationEffect oneShot =
VibrationEffect.createOneShot(10_000, VibrationEffect.DEFAULT_AMPLITUDE);
+ // Vibrate each vibrator in turn, and assert that all the others are off.
for (int vibratorId : vibratorIds) {
Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
mVibratorManager.vibrate(
@@ -264,7 +276,8 @@
for (int otherVibratorId : vibratorIds) {
if (otherVibratorId != vibratorId) {
- assertFalse(mVibratorManager.getVibrator(otherVibratorId).isVibrating());
+ assertThat(mVibratorManager.getVibrator(otherVibratorId).isVibrating())
+ .isFalse();
}
}
@@ -273,130 +286,6 @@
}
}
- @Test
- public void testGetVibratorIds() {
- // Just make sure it doesn't crash or return null when this is called; we don't really have
- // a way to test which vibrators will be returned.
- assertNotNull(mVibratorManager.getVibratorIds());
- }
-
- @Test
- public void testGetNonExistentVibratorId() {
- int missingId = Arrays.stream(mVibratorManager.getVibratorIds()).max().orElse(0) + 1;
- Vibrator vibrator = mVibratorManager.getVibrator(missingId);
- assertNotNull(vibrator);
- assertFalse(vibrator.hasVibrator());
- }
-
- @Test
- public void testGetDefaultVibrator() {
- Vibrator systemVibrator =
- InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
- Vibrator.class);
- assertSame(systemVibrator, mVibratorManager.getDefaultVibrator());
- }
-
- @Test
- public void testSingleVibratorIsPresent() {
- for (int vibratorId : mVibratorManager.getVibratorIds()) {
- Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
- assertNotNull(vibrator);
- assertEquals(vibratorId, vibrator.getId());
- assertTrue(vibrator.hasVibrator());
- }
- }
-
- @Test
- public void testSingleVibratorAmplitudeAndFrequencyControls() {
- for (int vibratorId : mVibratorManager.getVibratorIds()) {
- Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
- assertNotNull(vibrator);
-
- // Just check this method will not crash.
- vibrator.hasAmplitudeControl();
-
- // Single vibrators should return the frequency profile when it has frequency control.
- assertEquals(vibrator.hasFrequencyControl(),
- vibrator.getFrequencyProfile() != null);
- }
- }
-
- @Test
- public void testSingleVibratorFrequencyProfile() {
- for (int vibratorId : mVibratorManager.getVibratorIds()) {
- Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
- VibratorFrequencyProfile frequencyProfile = vibrator.getFrequencyProfile();
- if (frequencyProfile == null) {
- continue;
- }
-
- float measurementIntervalHz = frequencyProfile.getMaxAmplitudeMeasurementInterval();
- assertTrue(measurementIntervalHz >= MINIMUM_ACCEPTED_MEASUREMENT_INTERVAL_FREQUENCY);
-
- float resonantFrequency = vibrator.getResonantFrequency();
- float minFrequencyHz = frequencyProfile.getMinFrequency();
- float maxFrequencyHz = frequencyProfile.getMaxFrequency();
-
- assertTrue(minFrequencyHz >= MINIMUM_ACCEPTED_FREQUENCY);
- assertTrue(maxFrequencyHz > minFrequencyHz);
- assertTrue(maxFrequencyHz <= MAXIMUM_ACCEPTED_FREQUENCY);
-
- if (!Float.isNaN(resonantFrequency)) {
- // If the device has a resonant frequency, then it should be within the supported
- // frequency range described by the profile.
- assertTrue(resonantFrequency >= minFrequencyHz);
- assertTrue(resonantFrequency <= maxFrequencyHz);
- }
-
- float[] measurements = frequencyProfile.getMaxAmplitudeMeasurements();
-
- // There should be at least 3 points for a valid profile.
- assertTrue(measurements.length > 2);
- assertEquals(maxFrequencyHz,
- minFrequencyHz + ((measurements.length - 1) * measurementIntervalHz),
- TEST_TOLERANCE);
-
- boolean hasPositiveMeasurement = false;
- for (float measurement : measurements) {
- assertTrue(measurement >= 0);
- assertTrue(measurement <= 1);
- hasPositiveMeasurement |= measurement > 0;
- }
- assertTrue(hasPositiveMeasurement);
- }
- }
-
- @Test
- public void testSingleVibratorEffectAndPrimitiveSupport() {
- for (int vibratorId : mVibratorManager.getVibratorIds()) {
- Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
- assertNotNull(vibrator);
-
- // Just check these methods return valid support arrays.
- // We don't really have a way to test if the device supports each effect or not.
- assertEquals(2, vibrator.areEffectsSupported(
- VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_CLICK).length);
- assertEquals(2, vibrator.arePrimitivesSupported(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- VibrationEffect.Composition.PRIMITIVE_TICK).length);
- }
- }
-
- @Test
- public void testSingleVibratorVibrateAndCancel() {
- for (int vibratorId : mVibratorManager.getVibratorIds()) {
- Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
- assertNotNull(vibrator);
-
- vibrator.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
- assertStartsVibrating(vibratorId);
- assertTrue(vibrator.isVibrating());
-
- vibrator.cancel();
- assertStopsVibrating(vibratorId);
- }
- }
-
private void assertStartsThenStopsVibrating(long duration) {
for (int i = 0; i < mStateListeners.size(); i++) {
assertVibratorState(mStateListeners.keyAt(i), true);
diff --git a/tests/tests/os/src/android/os/cts/VibratorTest.java b/tests/tests/os/src/android/os/cts/VibratorTest.java
index 7df216c..c026296 100644
--- a/tests/tests/os/src/android/os/cts/VibratorTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorTest.java
@@ -19,10 +19,11 @@
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.eq;
@@ -39,20 +40,23 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.Vibrator.OnVibratorStateChangedListener;
+import android.os.VibratorManager;
import android.os.vibrator.VibratorFrequencyProfile;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.google.common.collect.Range;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -62,11 +66,17 @@
import java.util.List;
import java.util.concurrent.Executors;
-@RunWith(AndroidJUnit4.class)
+/**
+ * Verifies the Vibrator API for all surfaces that present it, as enumerated by the {@link #data()}
+ * method.
+ */
+@RunWith(Parameterized.class)
public class VibratorTest {
+ private static final String SYSTEM_VIBRATOR_LABEL = "SystemVibrator";
+
@Rule
- public ActivityTestRule<SimpleTestActivity> mActivityRule = new ActivityTestRule<>(
- SimpleTestActivity.class);
+ public ActivityScenarioRule<SimpleTestActivity> mActivityRule =
+ new ActivityScenarioRule<>(SimpleTestActivity.class);
@Rule
public final AdoptShellPermissionsRule mAdoptShellPermissionsRule =
@@ -77,6 +87,43 @@
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ /**
+ * Provides the vibrator accessed with the given vibrator ID, at the time of test running.
+ * A vibratorId of -1 indicates to use the system default vibrator.
+ */
+ private interface VibratorProvider {
+ Vibrator getVibrator();
+ }
+
+ /** Helper to add test parameters more readably and without explicit casting. */
+ private static void addTestParameter(ArrayList<Object[]> data, String testLabel,
+ VibratorProvider vibratorProvider) {
+ data.add(new Object[] { testLabel, vibratorProvider });
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable<Object[]> data() {
+ // Test params are Name,Vibrator pairs. All vibrators on the system should conform to this
+ // test.
+ ArrayList<Object[]> data = new ArrayList<>();
+ // These vibrators should be identical, but verify both APIs explicitly.
+ addTestParameter(data, SYSTEM_VIBRATOR_LABEL,
+ () -> InstrumentationRegistry.getInstrumentation().getContext()
+ .getSystemService(Vibrator.class));
+ // VibratorManager also presents getDefaultVibrator, but in VibratorManagerTest
+ // it is asserted that the Vibrator system service and getDefaultVibrator are
+ // the same object, so we don't test it twice here.
+
+ VibratorManager vibratorManager = InstrumentationRegistry.getInstrumentation().getContext()
+ .getSystemService(VibratorManager.class);
+ for (int vibratorId : vibratorManager.getVibratorIds()) {
+ addTestParameter(data, "vibratorId:" + vibratorId,
+ () -> InstrumentationRegistry.getInstrumentation().getContext()
+ .getSystemService(VibratorManager.class).getVibrator(vibratorId));
+ }
+ return data;
+ }
+
private static final float TEST_TOLERANCE = 1e-5f;
private static final float MINIMUM_ACCEPTED_MEASUREMENT_INTERVAL_FREQUENCY = 1f;
@@ -125,6 +172,9 @@
VibrationAttributes.USAGE_TOUCH,
};
+ private final String mVibratorLabel;
+ private final Vibrator mVibrator;
+
/**
* This listener is used for test helper methods like asserting it starts/stops vibrating.
* It's not strongly required that the interactions with this mock are validated by all tests.
@@ -132,15 +182,18 @@
@Mock
private OnVibratorStateChangedListener mStateListener;
- private Vibrator mVibrator;
/** Keep track of any listener created to be added to the vibrator, for cleanup purposes. */
private List<OnVibratorStateChangedListener> mStateListenersCreated = new ArrayList<>();
+ // vibratorLabel is used by the parameterized test infrastructure.
+ public VibratorTest(String vibratorLabel, VibratorProvider vibratorProvider) {
+ mVibratorLabel = vibratorLabel;
+ mVibrator = vibratorProvider.getVibrator();
+ assertThat(mVibrator).isNotNull();
+ }
+
@Before
public void setUp() {
- mVibrator = InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
- Vibrator.class);
-
mVibrator.addVibratorStateListener(mStateListener);
// Adding a listener to the Vibrator should trigger the callback once with the current
// vibrator state, so reset mocks to clear it for tests.
@@ -168,17 +221,36 @@
}
@Test
+ public void testSystemVibratorGetIdAndMaybeHasVibrator() {
+ assumeTrue(isSystemVibrator());
+
+ // The system vibrator should not be mapped to any physical vibrator and use a default id.
+ assertThat(mVibrator.getId()).isEqualTo(-1);
+ // The system vibrator always exists, but may not actually have a vibrator. Just make sure
+ // the API doesn't throw.
+ mVibrator.hasVibrator();
+ }
+
+ @Test
+ public void testNonSystemVibratorGetIdAndAlwaysHasVibrator() {
+ assumeFalse(isSystemVibrator());
+ assertThat(mVibrator.hasVibrator()).isTrue();
+ }
+
+ @Test
public void getDefaultVibrationIntensity_returnsValidIntensityForAllUsages() {
for (int usage : VIBRATION_USAGES) {
int intensity = mVibrator.getDefaultVibrationIntensity(usage);
- assertTrue("Error for usage " + usage + " with default intensity " + intensity,
- (intensity >= Vibrator.VIBRATION_INTENSITY_OFF)
- && (intensity <= Vibrator.VIBRATION_INTENSITY_HIGH));
+ assertWithMessage("Default intensity invalid for usage " + usage)
+ .that(intensity)
+ .isIn(Range.closed(
+ Vibrator.VIBRATION_INTENSITY_OFF, Vibrator.VIBRATION_INTENSITY_HIGH));
}
- assertEquals("Invalid usage expected to have same default as USAGE_UNKNOWN",
- mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_UNKNOWN),
- mVibrator.getDefaultVibrationIntensity(-1));
+ assertWithMessage("Invalid usage expected to have same default as USAGE_UNKNOWN")
+ .that(mVibrator.getDefaultVibrationIntensity(-1))
+ .isEqualTo(
+ mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_UNKNOWN));
}
@Test
@@ -196,31 +268,23 @@
mVibrator.vibrate(pattern, 3);
assertStartsVibrating();
- try {
- mVibrator.vibrate(pattern, 10);
- fail("Should throw ArrayIndexOutOfBoundsException");
- } catch (ArrayIndexOutOfBoundsException expected) {
- }
+ // Repeat index is invalid.
+ assertThrows(ArrayIndexOutOfBoundsException.class, () -> mVibrator.vibrate(pattern, 10));
}
@Test
- public void testVibrateMultiThread() {
- new Thread(() -> {
- try {
- mVibrator.vibrate(500);
- } catch (Exception e) {
- fail("MultiThread fail1");
- }
+ public void testVibrateMultiThread() throws Exception {
+ ThreadHelper thread1 = new ThreadHelper(() -> {
+ mVibrator.vibrate(200);
}).start();
- new Thread(() -> {
- try {
- // This test only get two threads to run vibrator at the same time for a functional
- // test, but it can not verify if the second thread get the precedence.
- mVibrator.vibrate(1000);
- } catch (Exception e) {
- fail("MultiThread fail2");
- }
+ ThreadHelper thread2 = new ThreadHelper(() -> {
+ // This test only get two threads to run vibrator at the same time for a functional
+ // test, but can't assert ordering.
+ mVibrator.vibrate(100);
}).start();
+ thread1.joinSafely();
+ thread2.joinSafely();
+
assertStartsVibrating();
}
@@ -270,7 +334,7 @@
assertStartsVibrating();
SystemClock.sleep(2000);
- assertTrue(!mVibrator.hasVibrator() || mVibrator.isVibrating());
+ assertIsVibrating(true);
mVibrator.cancel();
assertStopsVibrating();
@@ -283,13 +347,18 @@
VibratorFrequencyProfile frequencyProfile = mVibrator.getFrequencyProfile();
assumeNotNull(frequencyProfile);
- float minFrequency = Math.max(1f, frequencyProfile.getMinFrequency());
+ float minFrequency = frequencyProfile.getMinFrequency();
float maxFrequency = frequencyProfile.getMaxFrequency();
float resonantFrequency = mVibrator.getResonantFrequency();
float sustainFrequency = Float.isNaN(resonantFrequency)
- ? (maxFrequency - minFrequency) / 2
+ ? (maxFrequency + minFrequency) / 2
: resonantFrequency;
+ // Ensure the values can be used as a targetFrequency.
+ assertThat(minFrequency).isAtLeast(MINIMUM_ACCEPTED_FREQUENCY);
+ assertThat(maxFrequency).isAtLeast(minFrequency);
+ assertThat(maxFrequency).isAtMost(MAXIMUM_ACCEPTED_FREQUENCY);
+
// Ramp from min to max frequency and from zero to max amplitude.
// Then ramp to a fixed frequency at max amplitude.
// Then ramp to zero amplitude at fixed frequency.
@@ -349,19 +418,6 @@
}
@Test
- public void testGetId() {
- // The system vibrator should not be mapped to any physical vibrator and use a default id.
- assertEquals(-1, mVibrator.getId());
- }
-
- @Test
- public void testHasVibrator() {
- // Just make sure it doesn't crash when this is called; we don't really have a way to test
- // if the device has vibrator or not.
- mVibrator.hasVibrator();
- }
-
- @Test
public void testVibratorHasAmplitudeControl() {
// Just make sure it doesn't crash when this is called; we don't really have a way to test
// if the amplitude control works or not.
@@ -372,16 +428,25 @@
public void testVibratorHasFrequencyControl() {
// Just make sure it doesn't crash when this is called; we don't really have a way to test
// if the frequency control works or not.
- mVibrator.hasFrequencyControl();
+ if (mVibrator.hasFrequencyControl()) {
+ // If it's a multi-vibrator device, the system vibrator presents a merged frequency
+ // profile, which may in turn be empty, and hence null. But otherwise, it should not
+ // be null.
+ if (!isMultiVibratorDevice() || !isSystemVibrator()) {
+ assertThat(mVibrator.getFrequencyProfile()).isNotNull();
+ }
+ } else {
+ assertThat(mVibrator.getFrequencyProfile()).isNull();
+ }
}
@Test
public void testVibratorEffectsAreSupported() {
// Just make sure it doesn't crash when this is called and that it returns all queries;
// We don't really have a way to test if the device supports each effect or not.
- assertEquals(PREDEFINED_EFFECTS.length,
- mVibrator.areEffectsSupported(PREDEFINED_EFFECTS).length);
- assertEquals(0, mVibrator.areEffectsSupported().length);
+ assertThat(mVibrator.areEffectsSupported(PREDEFINED_EFFECTS))
+ .hasLength(PREDEFINED_EFFECTS.length);
+ assertThat(mVibrator.areEffectsSupported()).isEmpty();
}
@Test
@@ -389,16 +454,17 @@
// Just make sure it doesn't crash when this is called;
// We don't really have a way to test if the device supports each effect or not.
mVibrator.areAllEffectsSupported(PREDEFINED_EFFECTS);
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES, mVibrator.areAllEffectsSupported());
+ assertThat(mVibrator.areAllEffectsSupported())
+ .isEqualTo(Vibrator.VIBRATION_EFFECT_SUPPORT_YES);
}
@Test
public void testVibratorPrimitivesAreSupported() {
// Just make sure it doesn't crash when this is called;
// We don't really have a way to test if the device supports each effect or not.
- assertEquals(PRIMITIVE_EFFECTS.length,
- mVibrator.arePrimitivesSupported(PRIMITIVE_EFFECTS).length);
- assertEquals(0, mVibrator.arePrimitivesSupported().length);
+ assertThat(mVibrator.arePrimitivesSupported(PRIMITIVE_EFFECTS)).hasLength(
+ PRIMITIVE_EFFECTS.length);
+ assertThat(mVibrator.arePrimitivesSupported()).isEmpty();
}
@Test
@@ -406,29 +472,36 @@
// Just make sure it doesn't crash when this is called;
// We don't really have a way to test if the device supports each effect or not.
mVibrator.areAllPrimitivesSupported(PRIMITIVE_EFFECTS);
- assertTrue(mVibrator.areAllPrimitivesSupported());
+ assertThat(mVibrator.areAllPrimitivesSupported()).isTrue();
}
@Test
public void testVibratorPrimitivesDurations() {
int[] durations = mVibrator.getPrimitiveDurations(PRIMITIVE_EFFECTS);
boolean[] supported = mVibrator.arePrimitivesSupported(PRIMITIVE_EFFECTS);
- assertEquals(PRIMITIVE_EFFECTS.length, durations.length);
+ assertThat(durations).hasLength(PRIMITIVE_EFFECTS.length);
for (int i = 0; i < durations.length; i++) {
- assertEquals("Primitive " + PRIMITIVE_EFFECTS[i]
- + " expected to have " + (supported[i] ? "positive" : "zero")
- + " duration, found " + durations[i] + "ms",
- supported[i], durations[i] > 0);
+ if (supported[i]) {
+ assertWithMessage("Supported primitive " + PRIMITIVE_EFFECTS[i]
+ + " should have positive duration")
+ .that(durations[i]).isGreaterThan(0);
+ } else {
+ assertWithMessage("Unsupported primitive " + PRIMITIVE_EFFECTS[i]
+ + " should have zero duration")
+ .that(durations[i]).isEqualTo(0);
+
+ }
}
- assertEquals(0, mVibrator.getPrimitiveDurations().length);
+ assertThat(mVibrator.getPrimitiveDurations()).isEmpty();
}
@Test
public void testVibratorResonantFrequency() {
// Check that the resonant frequency provided is NaN, or if it's a reasonable value.
float resonantFrequency = mVibrator.getResonantFrequency();
- assertTrue(Float.isNaN(resonantFrequency)
- || (resonantFrequency > 0 && resonantFrequency < MAXIMUM_ACCEPTED_FREQUENCY));
+ if (!Float.isNaN(resonantFrequency)) {
+ assertThat(resonantFrequency).isIn(Range.open(0f, MAXIMUM_ACCEPTED_FREQUENCY));
+ }
}
@Test
@@ -444,7 +517,7 @@
// If the frequency profile is present then the vibrator must have frequency control.
// The other implication is not true if the default vibrator represents multiple vibrators.
- assertTrue(mVibrator.hasFrequencyControl());
+ assertThat(mVibrator.hasFrequencyControl()).isTrue();
}
@Test
@@ -453,7 +526,8 @@
assumeNotNull(frequencyProfile);
float measurementIntervalHz = frequencyProfile.getMaxAmplitudeMeasurementInterval();
- assertTrue(measurementIntervalHz >= MINIMUM_ACCEPTED_MEASUREMENT_INTERVAL_FREQUENCY);
+ assertThat(measurementIntervalHz)
+ .isAtLeast(MINIMUM_ACCEPTED_MEASUREMENT_INTERVAL_FREQUENCY);
}
@Test
@@ -465,15 +539,15 @@
float minFrequencyHz = frequencyProfile.getMinFrequency();
float maxFrequencyHz = frequencyProfile.getMaxFrequency();
- assertTrue(minFrequencyHz >= MINIMUM_ACCEPTED_FREQUENCY);
- assertTrue(maxFrequencyHz > minFrequencyHz);
- assertTrue(maxFrequencyHz <= MAXIMUM_ACCEPTED_FREQUENCY);
+ assertThat(minFrequencyHz).isAtLeast(MINIMUM_ACCEPTED_FREQUENCY);
+ assertThat(maxFrequencyHz).isGreaterThan(minFrequencyHz);
+ assertThat(maxFrequencyHz).isAtMost(MAXIMUM_ACCEPTED_FREQUENCY);
if (!Float.isNaN(resonantFrequency)) {
// If the device has a resonant frequency, then it should be within the supported
// frequency range described by the profile.
- assertTrue(resonantFrequency >= minFrequencyHz);
- assertTrue(resonantFrequency <= maxFrequencyHz);
+ assertThat(resonantFrequency).isAtLeast(minFrequencyHz);
+ assertThat(resonantFrequency).isAtMost(maxFrequencyHz);
}
}
@@ -488,33 +562,31 @@
float[] measurements = frequencyProfile.getMaxAmplitudeMeasurements();
// There should be at least 3 points for a valid profile: min, center and max frequencies.
- assertTrue(measurements.length > 2);
- assertEquals(maxFrequencyHz,
- minFrequencyHz + ((measurements.length - 1) * measurementIntervalHz),
- TEST_TOLERANCE);
+ assertThat(measurements.length).isAtLeast(3);
+ assertThat(minFrequencyHz + ((measurements.length - 1) * measurementIntervalHz))
+ .isWithin(TEST_TOLERANCE).of(maxFrequencyHz);
boolean hasPositiveMeasurement = false;
for (float measurement : measurements) {
- assertTrue(measurement >= 0);
- assertTrue(measurement <= 1);
+ assertThat(measurement).isIn(Range.closed(0f, 1f));
hasPositiveMeasurement |= measurement > 0;
}
- assertTrue(hasPositiveMeasurement);
+ assertThat(hasPositiveMeasurement).isTrue();
}
@Test
public void testVibratorIsVibrating() {
assumeTrue(mVibrator.hasVibrator());
- assertFalse(mVibrator.isVibrating());
+ assertThat(mVibrator.isVibrating()).isFalse();
mVibrator.vibrate(5000);
assertStartsVibrating();
- assertTrue(mVibrator.isVibrating());
+ assertThat(mVibrator.isVibrating()).isTrue();
mVibrator.cancel();
assertStopsVibrating();
- assertFalse(mVibrator.isVibrating());
+ assertThat(mVibrator.isVibrating()).isFalse();
}
@LargeTest
@@ -526,7 +598,7 @@
assertStartsVibrating();
SystemClock.sleep(1500);
- assertFalse(mVibrator.isVibrating());
+ assertThat(mVibrator.isVibrating()).isFalse();
}
@LargeTest
@@ -580,6 +652,15 @@
verify(listener2, never()).onVibratorStateChanged(true);
}
+ private boolean isSystemVibrator() {
+ return mVibratorLabel.equals(SYSTEM_VIBRATOR_LABEL);
+ }
+
+ private boolean isMultiVibratorDevice() {
+ return InstrumentationRegistry.getInstrumentation().getContext()
+ .getSystemService(VibratorManager.class).getVibratorIds().length > 1;
+ }
+
private OnVibratorStateChangedListener newMockStateListener() {
OnVibratorStateChangedListener listener = mock(OnVibratorStateChangedListener.class);
mStateListenersCreated.add(listener);
@@ -594,6 +675,12 @@
}
}
+ private void assertIsVibrating(boolean expectedIsVibrating) {
+ if (mVibrator.hasVibrator()) {
+ assertThat(mVibrator.isVibrating()).isEqualTo(expectedIsVibrating);
+ }
+ }
+
private void assertStartsVibrating() {
assertVibratorState(true);
}
@@ -608,4 +695,54 @@
.onVibratorStateChanged(eq(expected));
}
}
+
+ /**
+ * Supervises a thread execution with a custom uncaught exception handler.
+ *
+ * <p>{@link #joinSafely()} should be called for all threads to ensure that the thread didn't
+ * have an uncaught exception. Without this custom handler, the default uncaught handler kills
+ * the whole test instrumentation, causing all tests to appear failed, making debugging harder.
+ */
+ private class ThreadHelper implements Thread.UncaughtExceptionHandler {
+ private final Thread mThread;
+ private boolean mStarted;
+ private volatile Throwable mUncaughtException;
+
+ /**
+ * Creates the thread with the {@link Runnable}. {@link #start()} should still be called
+ * after this.
+ */
+ ThreadHelper(Runnable runnable) {
+ mThread = new Thread(runnable);
+ mThread.setUncaughtExceptionHandler(this);
+ }
+
+ /** Start the thread. This is mainly so the helper usage looks more thread-like. */
+ ThreadHelper start() {
+ assertThat(mStarted).isFalse();
+ mThread.start();
+ mStarted = true;
+ return this;
+ }
+
+ /** Join the thread and assert that there was no uncaught exception in it. */
+ void joinSafely() throws InterruptedException {
+ assertThat(mStarted).isTrue();
+ mThread.join();
+ assertThat(mUncaughtException).isNull();
+ }
+
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ // The default android handler kills the whole test instrumentation, which is
+ // why this class implements a softer version.
+ if (t != mThread || mUncaughtException != null) {
+ // The thread should always match, but we propagate if it doesn't somehow.
+ // We can't throw an exception here directly, as it would be ignored.
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e);
+ } else {
+ mUncaughtException = e;
+ }
+ }
+ }
}
diff --git a/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt
index 7555a6f..b29e99f 100644
--- a/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt
@@ -64,14 +64,10 @@
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
class NotificationPermissionTest : BaseUsePermissionTest() {
- // b/220968160: Notification permission is not enabled on TV devices.
- @Before
- fun assumeNotTv() = assumeFalse(isTv)
-
private val cr = callWithShellPermissionIdentity {
context.createContextAsUser(UserHandle.SYSTEM, 0).contentResolver
}
- private var previousEnableState = 0
+ private var previousEnableState = -1
private var countDown: CountDownLatch = CountDownLatch(1)
private var allowedGroups = listOf<String>()
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
@@ -84,6 +80,8 @@
@Before
fun setLatchAndEnablePermission() {
+ // b/220968160: Notification permission is not enabled on TV devices.
+ assumeFalse(isTv)
runWithShellPermissionIdentity {
previousEnableState = Settings.Secure.getInt(cr, NOTIFICATION_PERMISSION_ENABLED, 0)
Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, 1)
@@ -95,10 +93,12 @@
@After
fun resetPermissionAndRemoveReceiver() {
- runWithShellPermissionIdentity {
- Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, previousEnableState)
+ if (previousEnableState >= 0) {
+ runWithShellPermissionIdentity {
+ Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, previousEnableState)
+ }
+ context.unregisterReceiver(receiver)
}
- context.unregisterReceiver(receiver)
}
@Test
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionAllServicesTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionAllServicesTest.kt
index d882b51..a0220be 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionAllServicesTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionAllServicesTest.kt
@@ -22,8 +22,10 @@
import android.content.Intent
import android.location.LocationManager
import android.net.Uri
+import android.os.Build
import android.provider.Settings
import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
import com.android.compatibility.common.util.AppOpsUtils.setOpMode
import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
import com.android.compatibility.common.util.SystemUtil.eventually
@@ -37,6 +39,7 @@
import org.junit.Test
@CtsDownstreamingTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
class PermissionAllServicesTest : BasePermissionTest() {
// "All services" screen is not supported on Auto in T
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
index db59919..8c62f21 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
@@ -41,6 +41,7 @@
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
@@ -50,6 +51,60 @@
private static final String TEST_NUMBER = "5625698388";
private static final long CONTENT_RESOLVER_TIMEOUT_MS = 5000;
+ private static final Uri INVALID_CALL_LOG_URI = Uri.parse(
+ "content://call_log/call_composer/%2fdata%2fdata%2fcom.android.providers"
+ + ".contacts%2fshared_prefs%2fContactsUpgradeReceiver.xml");
+
+ private static final String TEST_FAIL_DID_NOT_TRHOW_SE =
+ "fail test because Security Exception was not throw";
+
+ /**
+ * Tests scenario where an app gives {@link ContentResolver} a file to open that is not in the
+ * Call Log directory.
+ */
+ public void testOpenFileOutsideOfScopeThrowsException() throws FileNotFoundException {
+ try {
+ Context context = getInstrumentation().getContext();
+ ContentResolver resolver = context.getContentResolver();
+ resolver.openFile(INVALID_CALL_LOG_URI, "w", null);
+ // previous line should throw exception
+ fail(TEST_FAIL_DID_NOT_TRHOW_SE);
+ } catch (SecurityException e) {
+ assertNotNull(e.toString());
+ }
+ }
+
+ /**
+ * Tests scenario where an app gives {@link ContentResolver} a file to delete that is not in the
+ * Call Log directory.
+ */
+ public void testDeleteFileOutsideOfScopeThrowsException() {
+ try {
+ Context context = getInstrumentation().getContext();
+ ContentResolver resolver = context.getContentResolver();
+ resolver.delete(INVALID_CALL_LOG_URI, "w", null);
+ // previous line should throw exception
+ fail(TEST_FAIL_DID_NOT_TRHOW_SE);
+ } catch (SecurityException e) {
+ assertNotNull(e.toString());
+ }
+ }
+
+ /**
+ * Tests scenario where an app gives {@link ContentResolver} a file to insert outside the
+ * Call Log directory.
+ */
+ public void testInsertFileOutsideOfScopeThrowsException() {
+ try {
+ Context context = getInstrumentation().getContext();
+ ContentResolver resolver = context.getContentResolver();
+ resolver.insert(INVALID_CALL_LOG_URI, new ContentValues());
+ // previous line should throw exception
+ fail(TEST_FAIL_DID_NOT_TRHOW_SE);
+ } catch (SecurityException e) {
+ assertNotNull(e.toString());
+ }
+ }
public void testGetLastOutgoingCall() {
// Clear call log and ensure there are no outgoing calls
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index 863da91..9a691c1 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -5296,6 +5296,10 @@
Log.i(TAG, "testSetVoiceServiceStateOverride: originalSS = " + originalServiceState);
assertNotEquals(ServiceState.STATE_IN_SERVICE, originalServiceState);
+ // Wait for device to finish processing RADIO_POWER_OFF.
+ // Otherwise, Telecom will clear the voice state override before SST processes it.
+ waitForMs(10000);
+
// We should see the override reflected by both ServiceStateListener and getServiceState
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mTelephonyManager,
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
index efd0edb..81850a8 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
@@ -56,6 +56,7 @@
private static TelephonyManager sTelephonyManager;
private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
private static final boolean DEBUG = !"user".equals(Build.TYPE);
+ private static boolean sIsMultiSimDevice;
@BeforeClass
public static void beforeAllTests() throws Exception {
@@ -69,6 +70,14 @@
sTelephonyManager =
(TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+ sIsMultiSimDevice = isMultiSim(sTelephonyManager);
+
+ //TODO: Support DSDS b/210073692
+ if (sIsMultiSimDevice) {
+ Log.d(TAG, "Not support MultiSIM");
+ return;
+ }
+
sMockModemManager = new MockModemManager();
assertNotNull(sMockModemManager);
assertTrue(sMockModemManager.connectMockModemService());
@@ -82,6 +91,11 @@
return;
}
+ //TODO: Support DSDS b/210073692
+ if (sIsMultiSimDevice) {
+ return;
+ }
+
// Rebind all interfaces which is binding to MockModemService to default.
assertNotNull(sMockModemManager);
assertTrue(sMockModemManager.disconnectMockModemService());
@@ -91,6 +105,8 @@
@Before
public void beforeTest() {
assumeTrue(hasTelephonyFeature());
+ //TODO: Support DSDS b/210073692
+ assumeTrue(!sIsMultiSimDevice);
}
private static Context getContext() {
@@ -106,6 +122,10 @@
return true;
}
+ private static boolean isMultiSim(TelephonyManager tm) {
+ return tm != null && tm.getPhoneCount() > 1;
+ }
+
private static void enforceMockModemDeveloperSetting() throws Exception {
boolean isAllowed = SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false);
// Check for developer settings for user build. Always allow for debug builds
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
index 62f0f01..d678fc0 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
@@ -85,7 +85,7 @@
public static final int WAIT_FOR_CALL_STATE = 10000;
public static final int WAIT_FOR_CALL_DISCONNECT = 1000;
public static final int WAIT_FOR_CALL_CONNECT = 5000;
- public static final int WAIT_FOR_CALL_STATE_HOLD = 2000;
+ public static final int WAIT_FOR_CALL_STATE_HOLD = 1000;
public static final int WAIT_FOR_CALL_STATE_RESUME = 1000;
public static final int WAIT_FOR_CALL_STATE_ACTIVE = 15000;
public static final int LATCH_WAIT = 0;
@@ -380,8 +380,10 @@
}
if (sServiceConnector != null && sIsBound) {
+ TestImsService imsService = sServiceConnector.getCarrierService();
sServiceConnector.disconnectCarrierImsService();
sIsBound = false;
+ imsService.waitForExecutorFinish();
}
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
index 5d7f1e8..d41f98a 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
@@ -42,7 +42,7 @@
private static final int LATCH_WAIT = 0;
private static final int LATCH_MAX = 1;
private static final int WAIT_FOR_STATE_CHANGE = 1500;
- private static final int WAIT_FOR_ESTABLISHING = 2500;
+ private static final int WAIT_FOR_ESTABLISHING = 2000;
private final String mCallId = String.valueOf(this.hashCode());
private final Object mLock = new Object();
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
index 97fa8f2..88934c6 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
@@ -83,7 +83,9 @@
public static final int LATCH_UCE_LISTENER_SET = 11;
public static final int LATCH_UCE_REQUEST_PUBLISH = 12;
public static final int LATCH_ON_UNBIND = 13;
- private static final int LATCH_MAX = 14;
+ public static final int LATCH_LAST_MESSAGE_EXECUTE = 14;
+ private static final int LATCH_MAX = 15;
+ private static final int WAIT_FOR_EXIT_TEST = 2000;
protected static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
static {
for (int i = 0; i < LATCH_MAX; i++) {
@@ -587,7 +589,7 @@
}
if (sMessageExecutor != null) {
- sMessageExecutor.getLooper().quit();
+ sMessageExecutor.getLooper().quitSafely();
sMessageExecutor = null;
}
mSubIDs.clear();
@@ -608,6 +610,14 @@
}
}
+ public void waitForExecutorFinish() {
+ if (mIsTestTypeExecutor && sMessageExecutor != null) {
+ sMessageExecutor.postDelayed(() -> countDownLatch(LATCH_LAST_MESSAGE_EXECUTE), null ,
+ WAIT_FOR_EXIT_TEST);
+ waitForLatchCountdown(LATCH_LAST_MESSAGE_EXECUTE);
+ }
+ }
+
public void setImsServiceCompat() {
synchronized (mLock) {
mIsImsServiceCompat = true;
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
index 84f0803..c9bb848 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
@@ -964,6 +964,9 @@
}
private fun handleRotation(original: Bitmap, image: String): Bitmap {
+ // ExifInterface does not support GIF.
+ if (image.endsWith("gif")) return original
+
val inputStream = getAssets().open(image)
val exifInterface = ExifInterface(inputStream)
var rotation = 0
diff --git a/tests/tests/view/src/android/view/cts/TextureViewTest.java b/tests/tests/view/src/android/view/cts/TextureViewTest.java
index e9b2429..35a7c12 100644
--- a/tests/tests/view/src/android/view/cts/TextureViewTest.java
+++ b/tests/tests/view/src/android/view/cts/TextureViewTest.java
@@ -258,6 +258,7 @@
screenshot.getPixel(texturePos.right - 10, texturePos.bottom - 10));
}
+ // TODO(b/229173479): understand why DCI_P3 and BT2020 do not match with certain colors.
private static Object[] testDataSpaces() {
return new Integer[]{
DataSpace.DATASPACE_SCRGB_LINEAR,
@@ -275,7 +276,7 @@
@Test
@Parameters(method = "testDataSpaces")
public void testSDRFromSurfaceViewAndTextureView(int dataSpace) throws Throwable {
- final int grayishYellow = 0xFFBABAB5;
+ final int grayishYellow = 0xFFBABAB9;
long converted = Color.convert(grayishYellow, ColorSpace.getFromDataSpace(dataSpace));
final SDRTestActivity activity =
diff --git a/tests/tests/virtualdevice/AndroidTest.xml b/tests/tests/virtualdevice/AndroidTest.xml
index c7db7f5..2f800b8 100644
--- a/tests/tests/virtualdevice/AndroidTest.xml
+++ b/tests/tests/virtualdevice/AndroidTest.xml
@@ -14,6 +14,9 @@
limitations under the License.
-->
<configuration description="Configuration for Virtual Device Manager Tests">
+ <!-- Only run tests if the device under test is SDK version 33 (Android 13) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
+
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
@@ -26,6 +29,7 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsVirtualDevicesTestCases.apk" />
<option name="test-file-name" value="CtsVirtualDeviceStreamedTestApp.apk" />
+ <option name="check-min-sdk" value="true" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
index 0f1f0e8..a99c533 100644
--- a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
@@ -64,6 +64,7 @@
// Th notification privacy indicator
private final String PRIVACY_CHIP_PACKAGE_NAME = "com.android.systemui";
private final String PRIVACY_CHIP_ID = "privacy_chip";
+ private final String CAR_MIC_PRIVACY_CHIP_ID = "mic_privacy_chip";
private final String PRIVACY_DIALOG_PACKAGE_NAME = "com.android.systemui";
private final String PRIVACY_DIALOG_CONTENT_ID = "text";
private final String CAR_PRIVACY_DIALOG_CONTENT_ID = "mic_privacy_panel";
@@ -217,8 +218,9 @@
mUiDevice.openQuickSettings();
SystemClock.sleep(UI_WAIT_TIMEOUT);
+ String chipId = isCar() ? CAR_MIC_PRIVACY_CHIP_ID : PRIVACY_CHIP_ID;
final UiObject2 privacyChip =
- mUiDevice.findObject(By.res(PRIVACY_CHIP_PACKAGE_NAME, PRIVACY_CHIP_ID));
+ mUiDevice.findObject(By.res(PRIVACY_CHIP_PACKAGE_NAME, chipId));
assertWithMessage("Can not find mic indicator").that(privacyChip).isNotNull();
// Click the privacy indicator and verify the calling app name display status in the dialog.
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
index 1c700b5..dae4da9 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -989,6 +989,8 @@
return;
}
+ if (!mWifiP2pManager.isSetVendorElementsSupported()) return;
+
List<ScanResult.InformationElement> ies = new ArrayList<>();
ies.add(new ScanResult.InformationElement(221, 0,
new byte[WifiP2pManager.getP2pMaxAllowedVendorElementsLengthBytes() + 1]));
diff --git a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
index 7a0d6f1..cee9088 100644
--- a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
@@ -38,6 +38,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -808,6 +809,185 @@
}
@Test
+ public void testCallbackCalledOnceAfterDuplicateCalls() throws Throwable {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
+
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
+
+ UiTranslationManager manager =
+ sContext.getSystemService(UiTranslationManager.class);
+ // Set response
+ sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+ // Register callback
+ final Executor executor = Executors.newSingleThreadExecutor();
+ UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+ manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+ // Call startTranslation() multiple times; callback should only be called once.
+ // Note: The locales don't change with each call.
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+ // Call pauseTranslation() multiple times; callback should only be called once.
+ pauseUiTranslation(contentCaptureContext);
+ pauseUiTranslation(contentCaptureContext);
+ pauseUiTranslation(contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+ // Call resumeUiTranslation() multiple times; callback should only be called once.
+ resumeUiTranslation(contentCaptureContext);
+ resumeUiTranslation(contentCaptureContext);
+ resumeUiTranslation(contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+ }
+
+ @Test
+ public void testCallbackCalledForStartTranslationWithDifferentLocales() throws Throwable {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
+
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
+
+ UiTranslationManager manager =
+ sContext.getSystemService(UiTranslationManager.class);
+ // Set response
+ sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+ // Register callback
+ final Executor executor = Executors.newSingleThreadExecutor();
+ UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+ manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+ ULocale.ENGLISH, ULocale.FRENCH);
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+ ULocale.ENGLISH, ULocale.GERMAN);
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+ ULocale.ITALIAN, ULocale.GERMAN);
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+ ULocale.JAPANESE, ULocale.KOREAN);
+
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onStarted(eq(ULocale.ENGLISH), eq(ULocale.FRENCH), any(String.class));
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onStarted(eq(ULocale.ENGLISH), eq(ULocale.GERMAN), any(String.class));
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onStarted(eq(ULocale.ITALIAN), eq(ULocale.GERMAN), any(String.class));
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onStarted(eq(ULocale.JAPANESE), eq(ULocale.KOREAN), any(String.class));
+
+ // Calling startTranslation() after pauseTranslation() should invoke the callback even if
+ // the locales are the same as it was before.
+ pauseUiTranslation(contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+ ULocale.JAPANESE, ULocale.KOREAN);
+
+ Mockito.verify(mockCallback, Mockito.times(2))
+ .onStarted(eq(ULocale.JAPANESE), eq(ULocale.KOREAN), any(String.class));
+ }
+
+ @Test
+ public void testCallbackCalledOnStartTranslationAfterPause() throws Throwable {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
+
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
+
+ UiTranslationManager manager =
+ sContext.getSystemService(UiTranslationManager.class);
+ // Set response
+ sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+ // Register callback
+ final Executor executor = Executors.newSingleThreadExecutor();
+ UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+ manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+ pauseUiTranslation(contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+ // Start after pause invokes onResumed(), NOT onStarted().
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+ }
+
+ @Test
+ public void testCallbackNotCalledOnResumeTranslationAfterStart() throws Throwable {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
+
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
+
+ UiTranslationManager manager =
+ sContext.getSystemService(UiTranslationManager.class);
+ // Set response
+ sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+ // Register callback
+ final Executor executor = Executors.newSingleThreadExecutor();
+ UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+ manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+ resumeUiTranslation(contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.never())
+ .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+ }
+
+ @Test
+ public void testCallbackNotCalledOnPauseOrResumeTranslationWithoutStart() throws Throwable {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
+
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
+
+ UiTranslationManager manager =
+ sContext.getSystemService(UiTranslationManager.class);
+ // Set response
+ sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+ // Register callback
+ final Executor executor = Executors.newSingleThreadExecutor();
+ UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+ manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+ pauseUiTranslation(contentCaptureContext);
+ resumeUiTranslation(contentCaptureContext);
+
+ Mockito.verifyZeroInteractions(mockCallback);
+ }
+
+ @Test
public void testVirtualViewUiTranslation() throws Throwable {
// Enable CTS ContentCaptureService
CtsContentCaptureService contentcaptureService = enableContentCaptureService();
@@ -956,14 +1136,19 @@
private void startUiTranslation(boolean shouldPadContent, List<AutofillId> views,
ContentCaptureContext contentCaptureContext) {
+ startUiTranslation(shouldPadContent, views, contentCaptureContext, ULocale.ENGLISH,
+ ULocale.FRENCH);
+ }
+
+ private void startUiTranslation(boolean shouldPadContent, List<AutofillId> views,
+ ContentCaptureContext contentCaptureContext, ULocale sourceLocale,
+ ULocale targetLocale) {
final UiTranslationManager manager = sContext.getSystemService(UiTranslationManager.class);
runWithShellPermissionIdentity(() -> {
// Call startTranslation API
manager.startTranslation(
- new TranslationSpec(ULocale.ENGLISH,
- TranslationSpec.DATA_FORMAT_TEXT),
- new TranslationSpec(ULocale.FRENCH,
- TranslationSpec.DATA_FORMAT_TEXT),
+ new TranslationSpec(sourceLocale, TranslationSpec.DATA_FORMAT_TEXT),
+ new TranslationSpec(targetLocale, TranslationSpec.DATA_FORMAT_TEXT),
views, contentCaptureContext.getActivityId(),
shouldPadContent ? new UiTranslationSpec.Builder().setShouldPadContentForCompat(
true).build() : new UiTranslationSpec.Builder().build());