Merge "wifi: Add Radio stats in WifiUsabilityStatsEntry" into sc-dev
diff --git a/hostsidetests/car/AndroidTest.xml b/hostsidetests/car/AndroidTest.xml
index d72f51a..3699a2b 100644
--- a/hostsidetests/car/AndroidTest.xml
+++ b/hostsidetests/car/AndroidTest.xml
@@ -19,6 +19,10 @@
<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.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsCarApp.apk" />
+ </target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsCarHostTestCases.jar" />
<option name="runtime-hint" value="1m" />
diff --git a/hostsidetests/car/app/Android.bp b/hostsidetests/car/app/Android.bp
index 0c54753..0a0dd6a 100644
--- a/hostsidetests/car/app/Android.bp
+++ b/hostsidetests/car/app/Android.bp
@@ -21,4 +21,13 @@
defaults: ["cts_defaults"],
srcs: ["src/**/*.java"],
sdk_version: "current",
+
+ enforce_uses_libs: false,
+ static_libs: [
+ "android.frameworks.automotive.powerpolicy-V1-java",
+ "android.hardware.automotive.vehicle-V2.0-java",
+ "androidx.test.rules",
+ ],
+
+ libs: ["android.car"],
}
diff --git a/hostsidetests/car/app/AndroidManifest.xml b/hostsidetests/car/app/AndroidManifest.xml
index 17b2685..cd8e6b7 100755
--- a/hostsidetests/car/app/AndroidManifest.xml
+++ b/hostsidetests/car/app/AndroidManifest.xml
@@ -18,7 +18,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.car.cts.app">
+ <uses-permission android:name="android.car.permission.CAR_POWER"/>
+ <uses-permission android:name="android.car.permission.READ_CAR_POWER_POLICY"/>
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <uses-permission android:name="android.permission.STORAGE_INTERNAL"/>
+
<application>
+ <activity android:name=".PowerPolicyTestActivity"
+ android:launchMode="singleTask"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
<activity android:name=".SimpleActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestActivity.java b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestActivity.java
new file mode 100644
index 0000000..1bab9f6
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestActivity.java
@@ -0,0 +1,130 @@
+/*
+ * 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.car.cts.app;
+
+import android.app.Activity;
+import android.car.Car;
+import android.car.hardware.power.CarPowerManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * To test car power policy:
+ * <pre class="prettyprint">
+ * adb shell am force-stop android.car.cts.app
+ * adb shell am start -n android.car.cts.app/.PowerPolicyTestActivity \
+ * --es "powerpolicy" "testcase,action[,data]"
+ * - testcase: suite | 1 | 2 | 3 | 4 | 5 | 6
+ * - action: start | end | dumpstate | dumppolicy | applypolicy | closefile
+ * - data: policyId
+ * </pre>
+ */
+public final class PowerPolicyTestActivity extends Activity {
+ private static final String TAG = PowerPolicyTestActivity.class.getSimpleName();
+ private static final String SHARED_DATA_FILE = "/storage/emulated/obb/PowerPolicyData.txt";
+
+ private Car mCarApi;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private CarPowerManager mPowerManager;
+ private PrintWriter mPrintWriter;
+
+ @Nullable
+ public CarPowerManager getPowerManager() {
+ synchronized (mLock) {
+ return mPowerManager;
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ Log.d(TAG, "onNewIntent");
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ Log.w(TAG, "onNewIntent: empty extras");
+ return;
+ }
+ PowerPolicyTestCommand cmd = PowerPolicyTestClient.parseCommand(extras);
+ if (cmd == null) {
+ Log.w(TAG, "onNewIntent: invalid power policy test command");
+ return;
+ }
+ cmd.setCar(mCarApi);
+ cmd.setPrintWriter(mPrintWriter);
+ PowerPolicyTestClient.handleCommand(cmd);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ initCarApi();
+ Context ctx = getApplicationContext();
+ try {
+ mPrintWriter = new PrintWriter(new File(ctx.getFilesDir(), SHARED_DATA_FILE));
+ } catch (IOException e) {
+ Log.e(TAG, "onCreate: failed to open PowerPolicyData.txt");
+ mPrintWriter = null;
+ }
+
+ Log.d(TAG, "onCreate");
+ onNewIntent(getIntent());
+ }
+
+ private void initCarApi() {
+ if (mCarApi != null && mCarApi.isConnected()) {
+ mCarApi.disconnect();
+ mCarApi = null;
+ }
+ mCarApi = Car.createCar(this, null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+ (Car car, boolean ready) -> {
+ initManagers(car, ready);
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.d(TAG, "onDestroy");
+ if (mCarApi != null) {
+ mCarApi.disconnect();
+ }
+ super.onDestroy();
+ }
+
+ private void initManagers(Car car, boolean ready) {
+ synchronized (mLock) {
+ if (ready) {
+ mPowerManager = (CarPowerManager) car.getCarManager(
+ android.car.Car.POWER_SERVICE);
+ Log.d(TAG, "initManagers() completed");
+ } else {
+ mPowerManager = null;
+ Log.wtf(TAG, "initManagers() set to be null");
+ }
+ }
+ }
+}
diff --git a/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestClient.java b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestClient.java
new file mode 100644
index 0000000..71ffda0
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestClient.java
@@ -0,0 +1,126 @@
+/*
+ * 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.car.cts.app;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+
+public final class PowerPolicyTestClient {
+ private static final String TAG = PowerPolicyTestClient.class.getSimpleName();
+
+ private static final String POWERPOLICY_TEST_CMD_IDENTIFIER = "powerpolicy";
+ private static final String TEST_CMD_START = "start";
+ private static final String TEST_CMD_END = "end";
+ private static final String TEST_CMD_DUMP_STATE = "dumpstate";
+ private static final String TEST_CMD_DUMP_POLICY = "dumppolicy";
+ private static final String TEST_CMD_APPLY_POLICY = "applypolicy";
+ private static final String TEST_CMD_CLOSE_DATAFILE = "closefile";
+
+ private static PowerPolicyTestClient sPowerPolicyTestClient = new PowerPolicyTestClient();
+
+ private long mClientStartTime;
+
+ // This method is not intended for multi-threaded calls.
+ public static void handleCommand(PowerPolicyTestCommand cmd) {
+ switch (cmd.getType()) {
+ case START:
+ if (sPowerPolicyTestClient != null) {
+ Log.w(TAG, "can not restart the test without ending the previous test first");
+ return;
+ }
+ break;
+ default:
+ if (sPowerPolicyTestClient == null) {
+ Log.w(TAG, "execute test start command first");
+ return;
+ }
+ break;
+ }
+ cmd.execute(sPowerPolicyTestClient);
+
+ if (cmd.getType() == PowerPolicyTestCommand.TestCommandType.END) {
+ sPowerPolicyTestClient = null;
+ }
+ }
+
+ public static PowerPolicyTestCommand parseCommand(Bundle intentExtras) {
+ String testcase;
+ String action;
+ String data;
+ PowerPolicyTestCommand cmd = null;
+ String powertest = intentExtras.getString(POWERPOLICY_TEST_CMD_IDENTIFIER);
+ if (powertest == null) {
+ Log.d(TAG, "empty power test command");
+ return cmd;
+ }
+
+ String[] tokens = powertest.split(",");
+ int paramCount = tokens.length;
+ if (paramCount != 2 && paramCount != 3) {
+ throw new IllegalArgumentException("invalid command syntax");
+ }
+
+ testcase = tokens[0];
+ action = tokens[1];
+ if (paramCount == 3) {
+ data = tokens[2];
+ } else {
+ data = null;
+ }
+
+ switch (testcase) {
+ case TEST_CMD_START:
+ cmd = new PowerPolicyTestCommand.StartTestcaseCommand(testcase);
+ break;
+ case TEST_CMD_END:
+ cmd = new PowerPolicyTestCommand.EndTestcaseCommand(testcase);
+ break;
+ case TEST_CMD_DUMP_STATE:
+ cmd = new PowerPolicyTestCommand.DumpStateCommand(testcase);
+ break;
+ case TEST_CMD_DUMP_POLICY:
+ cmd = new PowerPolicyTestCommand.DumpPolicyCommand(testcase);
+ break;
+ case TEST_CMD_APPLY_POLICY:
+ if (paramCount != 3) {
+ throw new IllegalArgumentException("invalid command syntax");
+ }
+ cmd = new PowerPolicyTestCommand.ApplyPolicyCommand(testcase);
+ cmd.mPolicyId = data;
+ break;
+ case TEST_CMD_CLOSE_DATAFILE:
+ cmd = new PowerPolicyTestCommand.CloseDataFileCommand(testcase);
+ break;
+ default:
+ throw new IllegalArgumentException("invalid power policy test command: "
+ + action);
+ }
+
+ Log.i(TAG, "testcase=" + testcase + ", command=" + action);
+ return cmd;
+ }
+
+ public void cleanup() {
+ //TODO(b/183134882): add any necessary cleanup activities here
+ }
+
+ public void registerAndGo() {
+ mClientStartTime = SystemClock.uptimeMillis();
+ //TODO(b/183134882): here is the place to add listeners
+ }
+}
diff --git a/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestCommand.java b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestCommand.java
new file mode 100644
index 0000000..82d01db
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestCommand.java
@@ -0,0 +1,173 @@
+/*
+ * 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.car.cts.app;
+
+import android.car.Car;
+import android.car.hardware.power.CarPowerManager;
+import android.car.hardware.power.CarPowerPolicy;
+import android.util.Log;
+
+import java.io.PrintWriter;
+
+public abstract class PowerPolicyTestCommand {
+ private static final String TAG = PowerPolicyTestCommand.class.getSimpleName();
+
+ private final String mTestcase;
+ private final TestCommandType mType;
+
+ protected String mPolicyId;
+ protected Car mCar;
+ protected CarPowerManager mCarPowerManager;
+ protected PrintWriter mPrintWriter;
+
+ PowerPolicyTestCommand(String tc, TestCommandType type) {
+ mTestcase = tc;
+ mType = type;
+ }
+
+ void setCar(Car c) {
+ mCar = c;
+ mCarPowerManager = (CarPowerManager) mCar.getCarManager(Car.POWER_SERVICE);
+ }
+
+ String getTestcase() {
+ return mTestcase;
+ }
+
+ Car getCar() {
+ return mCar;
+ }
+
+ TestCommandType getType() {
+ return mType;
+ }
+
+ PrintWriter getPrintWriter() {
+ return mPrintWriter;
+ }
+
+ void setPrintWriter(PrintWriter fw) {
+ mPrintWriter = fw;
+ }
+
+ abstract void execute(PowerPolicyTestClient testClient);
+
+ enum TestCommandType {
+ START,
+ END,
+ DUMP_STATE,
+ DUMP_POLICY,
+ APPLY_POLICY,
+ CLOSE_DATAFILE
+ }
+
+ static final class StartTestcaseCommand extends PowerPolicyTestCommand {
+ StartTestcaseCommand(String tc) {
+ super(tc, TestCommandType.START);
+ }
+
+ void execute(PowerPolicyTestClient testClient) {
+ testClient.registerAndGo();
+ }
+ }
+
+ static final class EndTestcaseCommand extends PowerPolicyTestCommand {
+ EndTestcaseCommand(String tc) {
+ super(tc, TestCommandType.END);
+ }
+
+ @Override
+ void execute(PowerPolicyTestClient testClient) {
+ mPrintWriter.flush();
+ testClient.cleanup();
+ }
+ }
+
+ static final class DumpStateCommand extends PowerPolicyTestCommand {
+ DumpStateCommand(String tc) {
+ super(tc, TestCommandType.DUMP_STATE);
+ }
+
+ @Override
+ void execute(PowerPolicyTestClient testClient) {
+ int curState = mCarPowerManager.getPowerState();
+ mPrintWriter.printf("%s: Current Power State: %s\n", getTestcase(), curState);
+ Log.d(TAG, "Current Power State: " + curState);
+ }
+ }
+
+ static final class DumpPolicyCommand extends PowerPolicyTestCommand {
+ DumpPolicyCommand(String tc) {
+ super(tc, TestCommandType.DUMP_POLICY);
+ }
+
+ @Override
+ void execute(PowerPolicyTestClient testClient) {
+ CarPowerPolicy cpp = mCarPowerManager.getCurrentPowerPolicy();
+ if (cpp == null) {
+ Log.d(TAG, "null current power policy");
+ return;
+ }
+ String policyId = cpp.getPolicyId();
+ int[] enabledComponents = cpp.getEnabledComponents();
+ int[] disabledComponents = cpp.getDisabledComponents();
+
+ mPrintWriter.printf("%s: Current Power Policy: id=%s", getTestcase(), policyId);
+ mPrintWriter.printf(", enabledComponents=[");
+ for (int enabled : enabledComponents) {
+ mPrintWriter.printf("%d ", enabled);
+ }
+ mPrintWriter.printf("], disabledComponents=[");
+ for (int disabled : disabledComponents) {
+ mPrintWriter.printf("%d ", disabled);
+ }
+ mPrintWriter.println("]");
+ Log.d(TAG, "Dumped Policy Id: " + policyId);
+ }
+ }
+
+ static final class ApplyPolicyCommand extends PowerPolicyTestCommand {
+ ApplyPolicyCommand(String tc) {
+ super(tc, TestCommandType.APPLY_POLICY);
+ }
+
+ @Override
+ void execute(PowerPolicyTestClient testClient) {
+ if (mPolicyId == null) {
+ Log.w(TAG, "missing policy id for applying policy");
+ return;
+ }
+
+ mCarPowerManager.applyPowerPolicy(mPolicyId);
+ mPrintWriter.printf("%s : Apply Power Policy:%s\n", getTestcase(), mPolicyId);
+ Log.d(TAG, "apply policy with Id: " + mPolicyId);
+ }
+ }
+
+ static final class CloseDataFileCommand extends PowerPolicyTestCommand {
+ CloseDataFileCommand(String tc) {
+ super(tc, TestCommandType.CLOSE_DATAFILE);
+ }
+
+ @Override
+ void execute(PowerPolicyTestClient testClient) {
+ mPrintWriter.close();
+ Log.d(TAG, "close the data file");
+ }
+ }
+}
+
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index 5aea564..37f3e78 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -16,8 +16,6 @@
package android.os.cts;
-import static com.google.common.truth.Truth.assertThat;
-
import android.platform.test.annotations.AppModeFull;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -29,15 +27,11 @@
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.testtype.IBuildReceiver;
-import com.android.tradefed.util.AbiUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -56,15 +50,6 @@
private static final String FILTER_FG_SERVICE_REGEXP =
"TestFgService starting foreground: pid=([0-9]*)";
- // Testing the intent filter verification mechanism
- private static final String HOST_VERIFICATION_APK = "CtsHostLinkVerificationApp.apk";
- private static final String HOST_VERIFICATION_PKG = "com.android.cts.openlinksskeleton";
- private static final String FILTER_VERIFIER_REGEXP =
- "Verifying IntentFilter\\..* package:\"" + HOST_VERIFICATION_PKG + "\"";
- private static final Pattern HOST_PATTERN = Pattern.compile(".*hosts:\"(.*?)\"");
- // domains that should be validated against given our test apk
- private static final String HOST_WILDCARD = "wildcard.tld";
-
/**
* A reference to the device under test.
*/
@@ -148,59 +133,4 @@
assertTrue("Looking for nonexistence of service process " + pid,
lsOut.contains("No such file"));
}
-
- public void testIntentFilterHostValidation() throws Exception {
- String line = null;
- try {
- // Clean slate in case of earlier aborted run
- mDevice.uninstallPackage(HOST_VERIFICATION_PKG);
-
- final String[] options = { AbiUtils.createAbiFlag(mAbi.getName()) };
- final int currentUser = mDevice.getCurrentUser();
-
- mDevice.clearLogcat();
-
- final String errorString;
- errorString = mDevice.installPackageForUser(getTestAppFile(HOST_VERIFICATION_APK),
- false /* = reinstall? */, currentUser, options);
-
- assertNull("Couldn't install web intent filter sample apk in user " +
- currentUser + " : " + errorString, errorString);
-
- String logs = mDevice.executeAdbCommand("logcat", "-v", "brief", "-d");
- boolean foundVerifierOutput = false;
- Pattern verifierPattern = Pattern.compile(FILTER_VERIFIER_REGEXP);
- Scanner scanner = new Scanner(logs);
- while (scanner.hasNextLine()) {
- line = scanner.nextLine();
- Matcher verifierMatcher = verifierPattern.matcher(line);
- if (verifierMatcher.find()) {
- Matcher m = HOST_PATTERN.matcher(line);
- assertTrue(m.find());
- final String hostgroup = m.group(1);
- HashSet<String> allHosts = new HashSet<>(
- Arrays.asList(hostgroup.split(" ")));
- assertThat(allHosts).containsExactly(HOST_WILDCARD);
- foundVerifierOutput = true;
- break;
- }
- }
-
- assertTrue(foundVerifierOutput);
- } catch (Exception e) {
- fail("Unable to parse verification results: " + e.getMessage()
- + " line=" + line);
- } finally {
- // Finally, uninstall the app
- mDevice.uninstallPackage(HOST_VERIFICATION_PKG);
- }
- }
-
- /*
- * Helper: find a test apk
- */
- private File getTestAppFile(String fileName) throws FileNotFoundException {
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
- return buildHelper.getTestFile(fileName);
- }
}
diff --git a/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp b/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp
deleted file mode 100644
index 3fb73fe..0000000
--- a/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
- name: "CtsHostLinkVerificationApp",
- defaults: ["cts_support_defaults"],
- sdk_version: "current",
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- ],
-}
diff --git a/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml b/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
deleted file mode 100644
index ace8c82..0000000
--- a/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-<!-- Declare the contents of this Android application. The namespace
- attribute brings in the Android platform namespace, and the package
- supplies a unique name for the application. When writing your
- own application, the package name must be changed from "com.example.*"
- to come from a domain that you own or have control over. -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.openlinksskeleton"
- android:versionCode="1"
- android:versionName="1.0">
-
- <application android:label="Open Links Skeleton"
- android:hasCode="false">
-
- <activity android:name="DummyWebLinkActivity"
- android:exported="true">
- <intent-filter android:autoVerify="true">
- <action android:name="android.intent.action.VIEW"/>
- <category android:name="android.intent.category.DEFAULT"/>
- <category android:name="android.intent.category.BROWSABLE"/>
- <data android:scheme="http"/>
- <data android:scheme="https"/>
- <data android:host="*.wildcard.tld"/>
- </intent-filter>
-
- <!-- Also make sure that verification picks up web navigation
- handling even when the filter matches non-web schemes -->
- <intent-filter>
- <action android:name="android.intent.action.VIEW"/>
- <category android:name="android.intent.category.DEFAULT"/>
- <category android:name="android.intent.category.BROWSABLE"/>
- <data android:scheme="http"/>
- <data android:scheme="https"/>
- <data android:scheme="nonweb"/>
- <data android:host="explicit.example.com"/>
- </intent-filter>
- </activity>
-
- </application>
-</manifest>
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index edd6f7a..8bba0ef 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -47,6 +47,16 @@
test_suites: ["device-tests", "mts-mediaprovider", "cts"],
}
android_test_helper_app {
+ name: "CtsScopedStorageTestAppC30",
+ manifest: "ScopedStorageTestHelper/TestAppC.xml",
+ static_libs: ["cts-scopedstorage-lib"],
+ sdk_version: "test_current",
+ target_sdk_version: "30",
+ srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
name: "CtsScopedStorageTestAppCLegacy",
manifest: "ScopedStorageTestHelper/TestAppCLegacy.xml",
static_libs: ["cts-scopedstorage-lib"],
@@ -79,6 +89,34 @@
// Tag as a CTS artifact
test_suites: ["device-tests", "mts-mediaprovider", "cts"],
}
+android_test_helper_app {
+ name: "CtsScopedStorageTestAppFileManagerBypassDB",
+ manifest: "ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml",
+ static_libs: ["cts-scopedstorage-lib"],
+ sdk_version: "test_current",
+ srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
+ name: "CtsScopedStorageTestAppSystemGalleryBypassDB",
+ manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml",
+ static_libs: ["cts-scopedstorage-lib"],
+ sdk_version: "test_current",
+ srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
+ name: "CtsScopedStorageTestAppSystemGallery30BypassDB",
+ manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml",
+ static_libs: ["cts-scopedstorage-lib"],
+ sdk_version: "test_current",
+ target_sdk_version: "30",
+ srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
+}
android_test_helper_app {
name: "CtsLegacyStorageTestAppRequestLegacy",
@@ -177,8 +215,12 @@
":CtsScopedStorageTestAppA",
":CtsScopedStorageTestAppB",
":CtsScopedStorageTestAppC",
+ ":CtsScopedStorageTestAppC30",
":CtsScopedStorageTestAppCLegacy",
":CtsScopedStorageTestAppDLegacy",
":CtsScopedStorageTestAppFileManager",
+ ":CtsScopedStorageTestAppFileManagerBypassDB",
+ ":CtsScopedStorageTestAppSystemGalleryBypassDB",
+ ":CtsScopedStorageTestAppSystemGallery30BypassDB",
]
}
diff --git a/hostsidetests/scopedstorage/AndroidManifest.xml b/hostsidetests/scopedstorage/AndroidManifest.xml
index da158c3..b05c1a9 100644
--- a/hostsidetests/scopedstorage/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
- <application>
+ <application android:requestOptimizedExternalStorageAccess="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml
new file mode 100644
index 0000000..b997bc9
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.scopedstorage.cts.testapp.filemanagerbypassdb"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+ <application android:label="TestAppFileManagerBypassDB" android:requestOptimizedExternalStorageAccess="true">
+ <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="android.scopedstorage.cts.testapp.filemanagerbypassdb"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
+ </application>
+</manifest>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml
new file mode 100644
index 0000000..b2dcf6e
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml
@@ -0,0 +1,47 @@
+<?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.scopedstorage.cts.testapp.SystemGalleryBypassDB"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+ <application android:label="TestAppSystemGalleryBypassDB" android:requestOptimizedExternalStorageAccess="true">
+ <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="android.scopedstorage.cts.testapp.SystemGalleryBypassDB"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
+ </application>
+</manifest>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index 11efea1..4c1e8ec 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -20,6 +20,7 @@
import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_READ_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_WRITE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.CAN_READ_WRITE_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.CHECK_DATABASE_ROW_EXISTS_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY;
@@ -30,8 +31,11 @@
import static android.scopedstorage.cts.lib.TestUtils.OPEN_FILE_FOR_WRITE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.QUERY_TYPE;
import static android.scopedstorage.cts.lib.TestUtils.READDIR_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_PARAMS_SEPARATOR;
+import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.canOpen;
+import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
import android.app.Activity;
@@ -94,6 +98,12 @@
case CREATE_IMAGE_ENTRY_QUERY:
returnIntent = createImageEntry(queryType);
break;
+ case RENAME_FILE_QUERY:
+ returnIntent = renameFile(queryType);
+ break;
+ case CHECK_DATABASE_ROW_EXISTS_QUERY:
+ returnIntent = checkDatabaseRowExists(queryType);
+ break;
case "null":
default:
throw new IllegalStateException(
@@ -214,6 +224,36 @@
}
}
+ private Intent renameFile(String queryType) {
+ if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
+ String[] paths = getIntent().getStringExtra(INTENT_EXTRA_PATH)
+ .split(RENAME_FILE_PARAMS_SEPARATOR);
+ File src = new File(paths[0]);
+ File dst = new File(paths[1]);
+ boolean result = src.renameTo(dst);
+ final Intent intent = new Intent(queryType);
+ intent.putExtra(queryType, result);
+ return intent;
+ } else {
+ throw new IllegalStateException(
+ queryType + ": File paths not set from launcher app");
+ }
+ }
+
+ private Intent checkDatabaseRowExists(String queryType) {
+ if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
+ final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
+ boolean result =
+ getFileRowIdFromDatabase(getContentResolver(), new File(filePath)) != -1;
+ final Intent intent = new Intent(queryType);
+ intent.putExtra(queryType, result);
+ return intent;
+ } else {
+ throw new IllegalStateException(
+ queryType + ": File path not set from launcher app");
+ }
+ }
+
private void maybeCreateParentDirInAndroid(File file) {
final String ownedPathType = getOwnedDirectoryType(file);
if (ownedPathType == null) {
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java
new file mode 100644
index 0000000..086a0d4
--- /dev/null
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java
@@ -0,0 +1,318 @@
+/*
+ * 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.scopedstorage.cts.device;
+
+import static android.app.AppOpsManager.permissionToOp;
+import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
+import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
+import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
+import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
+import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
+import static android.scopedstorage.cts.lib.TestUtils.installApp;
+import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions;
+import static android.scopedstorage.cts.lib.TestUtils.renameFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.provider.MediaStore;
+import android.scopedstorage.cts.lib.TestUtils;
+import android.util.Log;
+
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+
+/**
+ * Device-side test suite to verify file path operations optionally bypassing database operations.
+ */
+@RunWith(Parameterized.class)
+public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest {
+ static final String TAG = "BypassDatabaseOperationsTest";
+ // An app with READ_EXTERNAL_STORAGE permission. Targets current SDK and is preinstalled
+ private static final TestApp APP_SYSTEM_GALLERY_DEFAULT = new TestApp("TestAppA",
+ "android.scopedstorage.cts.testapp.A.withres", 1, false,
+ "CtsScopedStorageTestAppA.apk");
+ // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets current SDK and has
+ // requestOptimizedExternalStorageAccess=true
+ private static final TestApp APP_SYSTEM_GALLERY_BYPASS_DB = new TestApp(
+ "TestAppSystemGalleryBypassDB",
+ "android.scopedstorage.cts.testapp.SystemGalleryBypassDB", 1, false,
+ "CtsScopedStorageTestAppSystemGalleryBypassDB.apk");
+ // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets targetSDK=30.
+ private static final TestApp APP_SYSTEM_GALLERY_30 = new TestApp("TestAppC",
+ "android.scopedstorage.cts.testapp.C", 1, false,
+ "CtsScopedStorageTestAppC30.apk");
+ // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets targetSDK=30 and has
+ // requestOptimizedExternalStorageAccess=true
+ private static final TestApp APP_SYSTEM_GALLERY_30_BYPASS_DB = new TestApp(
+ "TestAppSystemGalleryBypassDB",
+ "android.scopedstorage.cts.testapp.SystemGalleryBypassDB", 1, false,
+ "CtsScopedStorageTestAppSystemGallery30BypassDB.apk");
+ // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission.
+ // Targets current SDK and preinstalled
+ private static final TestApp APP_FM_DEFAULT = new TestApp(
+ "TestAppFileManager", "android.scopedstorage.cts.testapp.filemanager", 1, false,
+ "CtsScopedStorageTestAppFileManager.apk");
+ // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission.
+ // Targets current SDK and has requestOptimizedExternalStorageAccess=true
+ private static final TestApp APP_FM_BYPASS_DATABASE_OPS = new TestApp(
+ "TestAppFileManagerBypassDB", "android.scopedstorage.cts.testapp.filemanagerbypassdb",
+ 1, false, "CtsScopedStorageTestAppFileManagerBypassDB.apk");
+ // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission and targets targetSDK=30
+ private static final TestApp APP_FM_TARGETS_30 = new TestApp("TestAppC",
+ "android.scopedstorage.cts.testapp.C", 1, false,
+ "CtsScopedStorageTestAppC30.apk");
+
+ private static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
+ permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+ private static final String[] SYSTEM_GALLERY_APPOPS = {
+ AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
+
+ /**
+ * To help avoid flaky tests, give ourselves a unique nonce to be used for
+ * all filesystem paths, so that we don't risk conflicting with previous
+ * test runs.
+ */
+ static final String NONCE = String.valueOf(System.nanoTime());
+
+ static final String IMAGE_FILE_NAME = "BypassDatabaseOperations_file_" + NONCE + ".jpg";
+
+ @BeforeClass
+ public static void setupApps() throws Exception {
+ // File manager needs to be explicitly granted MES app op.
+ final int fmUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_FM_DEFAULT.getPackageName(), 0);
+ allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+ }
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters(name = "volume={0}")
+ public static Iterable<? extends Object> data() {
+ return ScopedStorageBaseDeviceTest.getTestParameters();
+ }
+
+ @Before
+ public void setupExternalStorage() {
+ super.setupExternalStorage(mVolumeName);
+ Log.i(TAG, "Using volume : " + mVolumeName);
+ }
+
+
+ /**
+ * Test that app with MANAGE_EXTERNAL_STORAGE permission and targeting
+ * targetSDK=31 or higher will not bypass database operations by default.
+ */
+ @Test
+ public void testManageExternalStorage_DoesntBypassDatabase() throws Exception {
+ testAppDoesntBypassDatabaseOps(APP_FM_DEFAULT);
+ }
+
+ /**
+ * Test that app with MANAGE_EXTERNAL_STORAGE permission, targeting
+ * targetSDK=31 or higher and with requestOptimizedExternalStorageAccess=true
+ * will bypass database operations.
+ */
+ @Test
+ public void testManageExternalStorage_WithBypassFlag_BypassesDatabase() throws Exception {
+ installApp(APP_FM_BYPASS_DATABASE_OPS);
+ try {
+ final int fmUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_FM_BYPASS_DATABASE_OPS.getPackageName(), 0);
+ allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+ testAppBypassesDatabaseOps(APP_FM_BYPASS_DATABASE_OPS);
+ } finally {
+ uninstallAppNoThrow(APP_FM_BYPASS_DATABASE_OPS);
+ }
+ }
+
+ /**
+ * Test that app with MANAGE_EXTERNAL_STORAGE permission and targeting
+ * targetSDK=30 or lower will bypass database operations by default.
+ */
+ @Test
+ public void testManageExternalStorage_targets30_BypassesDatabase() throws Exception {
+ installApp(APP_FM_TARGETS_30);
+ try {
+ final int fmUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_FM_TARGETS_30.getPackageName(), 0);
+ allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+ testAppBypassesDatabaseOps(APP_FM_TARGETS_30);
+ } finally {
+ uninstallAppNoThrow(APP_FM_TARGETS_30);
+ }
+ }
+
+ /**
+ * Test that app with SYSTEM_GALLERY role and targeting
+ * targetSDK=current or higher will not bypass database operations by default.
+ */
+ @Test
+ public void testSystemGallery_DoesntBypassDatabase() throws Exception {
+ final int sgUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_SYSTEM_GALLERY_DEFAULT.getPackageName(), 0);
+ try {
+ allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+ testAppDoesntBypassDatabaseOps(APP_SYSTEM_GALLERY_DEFAULT);
+ } finally {
+ denyAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+ }
+ }
+
+
+ /**
+ * Test that app with SYSTEM_GALLERY role, targeting
+ * targetSDK=current or higher and with requestOptimizedSystemGalleryAccess=true
+ * will bypass database operations.
+ */
+ @Test
+ public void testSystemGallery_WithBypassFlag_BypassesDatabase() throws Exception {
+ installAppWithStoragePermissions(APP_SYSTEM_GALLERY_BYPASS_DB);
+ try {
+ final int sgUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_SYSTEM_GALLERY_BYPASS_DB.getPackageName(), 0);
+ allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+ testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_BYPASS_DB);
+ } finally {
+ uninstallAppNoThrow(APP_SYSTEM_GALLERY_BYPASS_DB);
+ }
+ }
+
+ /**
+ * Test that app with SYSTEM_GALLERY role and targeting
+ * targetSDK=30 or higher will not bypass database operations by default.
+ */
+ @Test
+ public void testSystemGallery_targets30_DoesntBypassDatabase() throws Exception {
+ installAppWithStoragePermissions(APP_SYSTEM_GALLERY_30);
+ try {
+ final int sgUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_SYSTEM_GALLERY_30.getPackageName(), 0);
+ allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+ testAppDoesntBypassDatabaseOps(APP_SYSTEM_GALLERY_30);
+ } finally {
+ uninstallAppNoThrow(APP_SYSTEM_GALLERY_30);
+ }
+ }
+
+ /**
+ * Test that app with SYSTEM_GALLERY role, targeting
+ * targetSDK=30 or higher and with requestOptimizedSystemGalleryAccess=true
+ * will bypass database operations.
+ */
+ @Test
+ public void testSystemGallery_targets30_WithBypassFlag_BypassesDatabase() throws Exception {
+ installAppWithStoragePermissions(APP_SYSTEM_GALLERY_30_BYPASS_DB);
+ try {
+ final int sgUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_SYSTEM_GALLERY_30_BYPASS_DB.getPackageName(), 0);
+ allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+ testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_30_BYPASS_DB);
+ } finally {
+ uninstallAppNoThrow(APP_SYSTEM_GALLERY_30_BYPASS_DB);
+ }
+ }
+
+ private void testAppDoesntBypassDatabaseOps(TestApp app) throws Exception {
+ final File file = new File(getDcimDir(), IMAGE_FILE_NAME);
+ final File renamedFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+ try {
+ assertThat(createFileAs(app, file.getAbsolutePath())).isTrue();
+ // File path create() added file to database.
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isTrue();
+
+ assertThat(renameFileAs(app, file, renamedFile)).isTrue();
+ // File path rename() also updates the database row
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isFalse();
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue();
+
+ assertThat(deleteFileAs(app, renamedFile.getAbsolutePath())).isTrue();
+ // File path delete() removes database row.
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isFalse();
+ } finally {
+ if (file.exists()) {
+ deleteFileAsNoThrow(app, file.getAbsolutePath());
+ }
+ if (renamedFile.exists()) {
+ deleteFileAsNoThrow(app, renamedFile.getAbsolutePath());
+ }
+ }
+ }
+
+ private void testAppBypassesDatabaseOps(TestApp app) throws Exception {
+ final File file = new File(getDcimDir(), IMAGE_FILE_NAME);
+ final File renamedFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+ try {
+ assertThat(createFileAs(app, file.getAbsolutePath())).isTrue();
+ // File path create() didn't add the file to database.
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isFalse();
+
+ // Ensure file is added to database.
+ assertNotNull(MediaStore.scanFile(getContentResolver(), file));
+
+ assertThat(renameFileAs(app, file, renamedFile)).isTrue();
+ // Rename() didn't update the database row.
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isTrue();
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isFalse();
+
+ // Ensure database is updated with renamed path
+ assertNull(MediaStore.scanFile(getContentResolver(), file));
+ assertNotNull(MediaStore.scanFile(getContentResolver(), renamedFile));
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue();
+
+ assertThat(deleteFileAs(app, renamedFile.getAbsolutePath())).isTrue();
+ // Unlink() didn't remove the database row.
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue();
+ } finally {
+ if (file.exists()) {
+ deleteFileAsNoThrow(app, file.getAbsolutePath());
+ }
+ if (renamedFile.exists()) {
+ deleteFileAsNoThrow(app, renamedFile.getAbsolutePath());
+ }
+ MediaStore.scanFile(getContentResolver(), file);
+ MediaStore.scanFile(getContentResolver(), renamedFile);
+ }
+ }
+}
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index ef8fc14..12c1b2c 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -107,6 +107,9 @@
"android.scopedstorage.cts.can_read_and_write";
public static final String READDIR_QUERY = "android.scopedstorage.cts.readdir";
public static final String SETATTR_QUERY = "android.scopedstorage.cts.setattr";
+ public static final String CHECK_DATABASE_ROW_EXISTS_QUERY =
+ "android.scopedstorage.cts.check_database_row_exists";
+ public static final String RENAME_FILE_QUERY = "android.scopedstorage.cts.renamefile";
public static final String STR_DATA1 = "Just some random text";
public static final String STR_DATA2 = "More arbitrary stuff";
@@ -114,6 +117,8 @@
public static final byte[] BYTES_DATA1 = STR_DATA1.getBytes();
public static final byte[] BYTES_DATA2 = STR_DATA2.getBytes();
+ public static final String RENAME_FILE_PARAMS_SEPARATOR = ";";
+
// Root of external storage
private static File sExternalStorageDirectory = Environment.getExternalStorageDirectory();
private static String sStorageVolumeName = MediaStore.VOLUME_EXTERNAL;
@@ -298,6 +303,30 @@
return getResultFromTestApp(testApp, file.getPath(), actionName);
}
+ /**
+ * Makes the given {@code testApp} rename give {@code src} to {@code dst}.
+ *
+ * The method concatenates source and destination paths while sending the request to
+ * {@code testApp}. Hence, {@link TestUtils#RENAME_FILE_PARAMS_SEPARATOR} shouldn't be used
+ * in path names.
+ *
+ * <p>This method drops shell permission identity.
+ */
+ public static boolean renameFileAs(TestApp testApp, File src, File dst) throws Exception {
+ final String paths = String.format("%s%s%s",
+ src.getAbsolutePath(), RENAME_FILE_PARAMS_SEPARATOR, dst.getAbsolutePath());
+ return getResultFromTestApp(testApp, paths, RENAME_FILE_QUERY);
+ }
+
+ /**
+ * Makes the given {@code testApp} check if a database row exists for given {@code file}
+ *
+ * <p>This method drops shell permission identity.
+ */
+ public static boolean checkDatabaseRowExistsAs(TestApp testApp, File file) throws Exception {
+ return getResultFromTestApp(testApp, file.getPath(), CHECK_DATABASE_ROW_EXISTS_QUERY);
+ }
+
public static Uri insertFileFromExternalMedia(boolean useRelative) throws IOException {
ContentValues values = new ContentValues();
String filePath =
@@ -579,8 +608,16 @@
* entry in the database. Returns {@code -1} if file is not found.
*/
public static int getFileRowIdFromDatabase(@NonNull File file) {
+ return getFileRowIdFromDatabase(getContentResolver(), file);
+ }
+
+ /**
+ * Queries given {@link ContentResolver} for a file and returns the corresponding row ID for
+ * its entry in the database. Returns {@code -1} if file is not found.
+ */
+ public static int getFileRowIdFromDatabase(ContentResolver cr, @NonNull File file) {
int id = -1;
- try (Cursor c = queryFile(file, MediaStore.MediaColumns._ID)) {
+ try (Cursor c = queryFile(cr, file, MediaStore.MediaColumns._ID)) {
if (c.moveToFirst()) {
id = c.getInt(0);
}
@@ -624,7 +661,8 @@
*/
@NonNull
public static Cursor queryVideoFile(File file, String... projection) {
- return queryFile(MediaStore.Video.Media.getContentUri(sStorageVolumeName), file,
+ return queryFile(getContentResolver(),
+ MediaStore.Video.Media.getContentUri(sStorageVolumeName), file,
/*includePending*/ true, projection);
}
@@ -634,7 +672,8 @@
*/
@NonNull
public static Cursor queryImageFile(File file, String... projection) {
- return queryFile(MediaStore.Images.Media.getContentUri(sStorageVolumeName), file,
+ return queryFile(getContentResolver(),
+ MediaStore.Images.Media.getContentUri(sStorageVolumeName), file,
/*includePending*/ true, projection);
}
@@ -1282,19 +1321,25 @@
*/
@NonNull
public static Cursor queryFileExcludingPending(@NonNull File file, String... projection) {
- return queryFile(MediaStore.Files.getContentUri(sStorageVolumeName), file,
- /*includePending*/ false, projection);
+ return queryFile(getContentResolver(), MediaStore.Files.getContentUri(sStorageVolumeName),
+ file, /*includePending*/ false, projection);
+ }
+
+ @NonNull
+ public static Cursor queryFile(ContentResolver cr, @NonNull File file, String... projection) {
+ return queryFile(cr, MediaStore.Files.getContentUri(sStorageVolumeName),
+ file, /*includePending*/ true, projection);
}
@NonNull
public static Cursor queryFile(@NonNull File file, String... projection) {
- return queryFile(MediaStore.Files.getContentUri(sStorageVolumeName), file,
- /*includePending*/ true, projection);
+ return queryFile(getContentResolver(), MediaStore.Files.getContentUri(sStorageVolumeName),
+ file, /*includePending*/ true, projection);
}
@NonNull
- private static Cursor queryFile(@NonNull Uri uri, @NonNull File file, boolean includePending,
- String... projection) {
+ private static Cursor queryFile(ContentResolver cr, @NonNull Uri uri, @NonNull File file,
+ boolean includePending, String... projection) {
Bundle queryArgs = new Bundle();
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
MediaStore.MediaColumns.DATA + " = ?");
@@ -1308,7 +1353,7 @@
queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_EXCLUDE);
}
- final Cursor c = getContentResolver().query(uri, projection, queryArgs, null);
+ final Cursor c = cr.query(uri, projection, queryArgs, null);
assertThat(c).isNotNull();
return c;
}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
index 5dc4590..d643084 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
@@ -20,6 +20,7 @@
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
@@ -68,6 +69,11 @@
private static Map<ITestDevice, String> sTestName = new HashMap<>();
private static Map<ITestDevice, PocPusher> sPocPusher = new HashMap<>();
+ @Option(name = "set-kptr_restrict",
+ description = "If kptr_restrict should be set to 2 after every reboot")
+ private boolean setKptr_restrict = false;
+ private boolean ignoreKernelAddress = false;
+
/**
* Waits for device to be online, marks the most recent boottime of the device
*/
@@ -85,6 +91,17 @@
pocPusher.setDevice(getDevice()).setBuild(getBuild()).setAbi(getAbi());
sPocPusher.put(getDevice(), pocPusher);
+
+ if (setKptr_restrict) {
+ if (getDevice().enableAdbRoot()) {
+ CLog.i("setting kptr_restrict to 2");
+ getDevice().executeShellCommand("echo 2 > /proc/sys/kernel/kptr_restrict");
+ getDevice().disableAdbRoot();
+ } else {
+ // not a rootable device
+ ignoreKernelAddress = true;
+ }
+ }
}
/**
@@ -160,6 +177,7 @@
*/
public void assertNotKernelPointer(Callable<String> getPtrFunction, ITestDevice deviceToReboot)
throws Exception {
+ assumeFalse("Cannot set kptr_restrict to 2, ignoring kptr test.", ignoreKernelAddress);
String ptr = null;
for (int i = 0; i < 4; i++) { // ~0.4% chance of false positive
ptr = getPtrFunction.call();
diff --git a/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java b/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
index 3b6c7da..879286f 100644
--- a/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
+++ b/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
@@ -133,12 +133,18 @@
restoreDropboxDefaults();
}
- private void sendExcessiveDropBoxEntries(String tag, int count, long delayPerEntry)
+ private void sendExcessiveDropBoxEntries(String tag, int count, long interval)
throws Exception {
+ // addText() can take dozens of milliseconds. In order to ensure addText is called at the
+ // given interval, we keep track of the timestamp when the next addText call should occur.
+ long nextTime = SystemClock.elapsedRealtime();
int i = 0;
mDropBoxManager.addText(tag, String.valueOf(i++));
for (; i < count; i++) {
- Thread.sleep(delayPerEntry);
+ nextTime += interval;
+ // Sleep until when we should send the next entry.
+ final long delay = nextTime - SystemClock.elapsedRealtime();
+ if (delay > 0) Thread.sleep(delay);
mDropBoxManager.addText(tag, String.valueOf(i));
}
}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index 8cd1cc4..d9ecad5 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -290,6 +290,52 @@
mTestAppInterface.getLastParams().getStopReason());
}
+ @Test
+ public void testEJStoppedWhenRestricted() throws Exception {
+ mTestAppInterface.scheduleJob(false, false, true);
+ runJob();
+ assertTrue("Job did not start after scheduling",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ setTestPackageRestricted(true);
+ assertTrue("Job did not stop after test app was restricted",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ mTestAppInterface.getLastParams().getStopReason());
+ }
+
+ @Test
+ public void testRestrictedEJStartedWhenUnrestricted() throws Exception {
+ setTestPackageRestricted(true);
+ mTestAppInterface.scheduleJob(false, false, true);
+ assertFalse("Job started for restricted app",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ setTestPackageRestricted(false);
+ assertTrue("Job did not start when app was unrestricted",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ }
+
+ @Test
+ public void testRestrictedEJAllowedWhenUidActive() throws Exception {
+ setTestPackageRestricted(true);
+ mTestAppInterface.scheduleJob(false, false, true);
+ assertFalse("Job started for restricted app",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ // Turn the screen on to ensure the app gets into the TOP state.
+ setScreenState(true);
+ mTestAppInterface.startAndKeepTestActivity(true);
+ assertTrue("Job did not start when app had an activity",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ mTestAppInterface.closeActivity();
+ // Don't put full minute as the timeout to give some leeway with test timing/processing.
+ assertFalse("Job stopped within grace period after activity closed",
+ mTestAppInterface.awaitJobStop(55_000L));
+ assertTrue("Job did not stop after grace period ended",
+ mTestAppInterface.awaitJobStop(15_000L));
+ assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ mTestAppInterface.getLastParams().getStopReason());
+ }
+
@RequiresDevice // Emulators don't always have access to wifi/network
@Test
public void testBackgroundConnectivityJobsThrottled() throws Exception {
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4
new file mode 100755
index 0000000..6b37153
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java
index e7ef396..d91e090 100644
--- a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java
+++ b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java
@@ -964,6 +964,28 @@
}
}
+ @Test
+ public void testTranscodeMultipleFilesConcurrently_longDurationLowVolume() throws Exception {
+ ModernFileOpenerThread[] modernFileOpenerThreads = new ModernFileOpenerThread[5];
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i] = new ModernFileOpenerThread(
+ ModernFileOpenerThread.FileDurationSeconds.HUNDRED);
+ }
+
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i].start();
+ }
+
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i].join();
+ if (modernFileOpenerThreads[i].mException != null) {
+ throw new Exception("Failed ModernFileOpenerThread - " + i + ": "
+ + modernFileOpenerThreads[i].mException.getMessage(),
+ modernFileOpenerThreads[i].mException);
+ }
+ }
+ }
+
private static final class ModernFileOpenerThread extends Thread {
private final FileDurationSeconds mFileDurationSeconds;
Throwable mException;
@@ -993,6 +1015,9 @@
case TWENTIES:
TranscodeTestUtils.stageMediumHevcVideoFile(modernFile);
break;
+ case HUNDRED:
+ TranscodeTestUtils.stageLongHevcVideoFile(modernFile);
+ break;
default:
throw new IllegalStateException(
"Unknown mFileDurationSeconds: " + mFileDurationSeconds);
@@ -1010,7 +1035,8 @@
enum FileDurationSeconds {
FEW,
- TWENTIES
+ TWENTIES,
+ HUNDRED
}
}
}
diff --git a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java
index e13dd46..200dfa3 100644
--- a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java
+++ b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java
@@ -88,6 +88,10 @@
return stageVideoFile(videoFile, R.raw.testVideo_HEVC_medium);
}
+ public static Uri stageLongHevcVideoFile(File videoFile) throws IOException {
+ return stageVideoFile(videoFile, R.raw.testVideo_HEVC_long);
+ }
+
public static Uri stageLegacyVideoFile(File videoFile) throws IOException {
return stageVideoFile(videoFile, R.raw.testVideo_Legacy);
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index 4e0c05e..d127589 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -111,6 +111,8 @@
import android.util.Log;
import android.util.Size;
+import androidx.test.filters.FlakyTest;
+
import com.android.compatibility.common.util.AppOpsUtils;
import com.android.compatibility.common.util.SystemUtil;
@@ -596,6 +598,7 @@
assertEquals(1, mWmState.countStacks(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD));
}
+ @FlakyTest(bugId = 183538974)
@Test
public void testDisallowMultipleTasksInPinnedStack() throws Exception {
// Launch a test activity so that we have multiple fullscreen tasks
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
index 324fb67..571fa9d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -19,6 +19,7 @@
import static android.server.wm.UiDeviceUtils.pressHomeButton;
import static android.server.wm.UiDeviceUtils.pressUnlockButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static android.view.SurfaceControlViewHost.*;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static org.junit.Assert.assertEquals;
@@ -377,4 +378,59 @@
// assert host has focus
assertWindowFocused(mSurfaceView, true);
}
+
+ private static class SurfaceCreatedCallback implements SurfaceHolder.Callback {
+ private final CountDownLatch mSurfaceCreated;
+ SurfaceCreatedCallback(CountDownLatch latch) {
+ mSurfaceCreated = latch;
+ }
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mSurfaceCreated.countDown();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {}
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+ }
+
+ @Test
+ public void testCanCopySurfacePackage() throws Throwable {
+ // Create a surface view and wait for its surface to be created.
+ CountDownLatch surfaceCreated = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ final FrameLayout content = new FrameLayout(mActivity);
+ mSurfaceView = new SurfaceView(mActivity);
+ mSurfaceView.setZOrderOnTop(true);
+ content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+ DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.LEFT | Gravity.TOP));
+ mActivity.setContentView(content, new ViewGroup.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT));
+ mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated));
+
+ // Create an embedded view.
+ mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+ mSurfaceView.getHostToken());
+ mEmbeddedView = new Button(mActivity);
+ mEmbeddedView.setOnClickListener((View v) -> mClicked = true);
+ mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight);
+
+ });
+ surfaceCreated.await();
+
+ // Make a copy of the SurfacePackage and release the original package.
+ SurfacePackage surfacePackage = mVr.getSurfacePackage();
+ SurfacePackage copy = new SurfacePackage(surfacePackage);
+ surfacePackage.release();
+ mSurfaceView.setChildSurfacePackage(copy);
+
+ mInstrumentation.waitForIdleSync();
+ waitUntilEmbeddedViewDrawn();
+
+ // Check if SurfacePackage copy remains valid even though the original package has
+ // been released.
+ CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+ assertTrue(mClicked);
+ }
}
diff --git a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
index 5d780f0..07e0914 100644
--- a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
+++ b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
@@ -209,7 +209,8 @@
AVCProfileConstrainedBaseline, AVCProfileConstrainedHigh});
mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC,
new int[]{HEVCProfileMain, HEVCProfileMain10, HEVCProfileMainStill,
- HEVCProfileMain10HDR10, HEVCProfileMain10HDR10Plus});
+ // TODO: test HDR profiles once they are supported by MediaMuxer
+ /* HEVCProfileMain10HDR10, HEVCProfileMain10HDR10Plus */});
mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_H263,
new int[]{H263ProfileBaseline, H263ProfileH320Coding,
H263ProfileBackwardCompatible, H263ProfileISWV2, H263ProfileISWV3,
@@ -229,8 +230,9 @@
mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{VP8ProfileMain});
mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{VP9Profile0, VP9Profile1});
mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_AV1,
- new int[]{AV1ProfileMain8, AV1ProfileMain10, AV1ProfileMain10HDR10,
- AV1ProfileMain10HDR10Plus});
+ new int[]{AV1ProfileMain8, AV1ProfileMain10,
+ // TODO: test HDR profiles once they are supported by MediaMuxer
+ /* AV1ProfileMain10HDR10, AV1ProfileMain10HDR10Plus */});
mProfileMap.put(MediaFormat.MIMETYPE_AUDIO_AAC,
new int[]{AACObjectMain, AACObjectLC, AACObjectSSR, AACObjectLTP, AACObjectHE,
AACObjectScalable, AACObjectERLC, AACObjectERScalable, AACObjectLD,
diff --git a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
index a78574b..c304806 100644
--- a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
+++ b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
@@ -36,6 +36,7 @@
import static android.appenumeration.cts.Constants.EXTRA_ERROR;
import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
import static android.content.Intent.EXTRA_RETURN_RESULT;
import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
import static android.os.Process.INVALID_UID;
@@ -68,6 +69,9 @@
import android.util.SparseArray;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
public class TestActivity extends Activity {
@@ -87,6 +91,7 @@
backgroundHandler = new Handler(backgroundThread.getLooper());
super.onCreate(savedInstanceState);
handleIntent(getIntent());
+ onCommandReady(getIntent());
}
@Override
@@ -184,6 +189,11 @@
bindService(remoteCallback, packageName);
} else if (Constants.ACTION_GET_SYNCADAPTER_TYPES.equals(action)) {
sendSyncAdapterTypes(remoteCallback);
+ } else if (Constants.ACTION_AWAIT_PACKAGES_SUSPENDED.equals(action)) {
+ final String[] awaitPackages = intent.getBundleExtra(EXTRA_DATA)
+ .getStringArray(Intent.EXTRA_PACKAGES);
+ awaitSuspendedPackagesBroadcast(remoteCallback, Arrays.asList(awaitPackages),
+ Intent.ACTION_PACKAGES_SUSPENDED, TIMEOUT_MS);
} else {
sendError(remoteCallback, new Exception("unknown action " + action));
}
@@ -192,6 +202,13 @@
}
}
+ private void onCommandReady(Intent intent) {
+ final RemoteCallback callback = intent.getParcelableExtra(EXTRA_REMOTE_READY_CALLBACK);
+ if (callback != null) {
+ callback.sendResult(null);
+ }
+ }
+
private void awaitPackageBroadcast(RemoteCallback remoteCallback, String packageName,
String action, long timeoutMs) {
final IntentFilter filter = new IntentFilter(action);
@@ -214,6 +231,34 @@
token, timeoutMs);
}
+ private void awaitSuspendedPackagesBroadcast(RemoteCallback remoteCallback,
+ List<String> awaitList, String action, long timeoutMs) {
+ final IntentFilter filter = new IntentFilter(action);
+ final ArrayList<String> suspendedList = new ArrayList<>();
+ final Object token = new Object();
+ final Runnable sendResult = () -> {
+ final Bundle result = new Bundle();
+ result.putStringArray(Intent.EXTRA_PACKAGES, suspendedList.toArray(new String[] {}));
+ remoteCallback.sendResult(result);
+ finish();
+ };
+ registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Bundle extras = intent.getExtras();
+ final String[] changedList = extras.getStringArray(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ suspendedList.addAll(Arrays.stream(changedList).filter(
+ p -> awaitList.contains(p)).collect(Collectors.toList()));
+ if (suspendedList.size() == awaitList.size()) {
+ mainHandler.removeCallbacksAndMessages(token);
+ sendResult.run();
+ }
+ }
+ }, filter);
+ mainHandler.postDelayed(() -> sendResult.run(), token, timeoutMs);
+ }
+
private void sendGetInstalledPackages(RemoteCallback remoteCallback, int flags) {
String[] packages =
getPackageManager().getInstalledPackages(flags)
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
index 5a3c12e..6321d04 100644
--- a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
@@ -178,8 +178,11 @@
public static final String ACTION_BIND_SERVICE = PKG_BASE + "cts.action.BIND_SERVICE";
public static final String ACTION_GET_SYNCADAPTER_TYPES =
PKG_BASE + "cts.action.GET_SYNCADAPTER_TYPES";
+ public static final String ACTION_AWAIT_PACKAGES_SUSPENDED =
+ PKG_BASE + "cts.action.AWAIT_PACKAGES_SUSPENDED";
public static final String EXTRA_REMOTE_CALLBACK = "remoteCallback";
+ public static final String EXTRA_REMOTE_READY_CALLBACK = "remoteReadyCallback";
public static final String EXTRA_ERROR = "error";
public static final String EXTRA_FLAGS = "flags";
public static final String EXTRA_DATA = "data";
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index a1adf19..39a8537 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -42,6 +42,7 @@
import static android.appenumeration.cts.Constants.EXTRA_ERROR;
import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
import static android.appenumeration.cts.Constants.QUERIES_ACTIVITY_ACTION;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PERM;
@@ -92,6 +93,7 @@
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasItemInArray;
@@ -626,7 +628,7 @@
public void broadcastAdded_notVisibleDoesNotReceive() throws Exception {
final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS,
/* targetUid */ INVALID_UID, /* intentExtra */ null,
- Constants.ACTION_AWAIT_PACKAGE_ADDED);
+ Constants.ACTION_AWAIT_PACKAGE_ADDED, /* waitForReady */ false);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
result.await();
@@ -640,7 +642,7 @@
public void broadcastAdded_visibleReceives() throws Exception {
final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS,
/* targetUid */ INVALID_UID, /* intentExtra */ null,
- Constants.ACTION_AWAIT_PACKAGE_ADDED);
+ Constants.ACTION_AWAIT_PACKAGE_ADDED, /* waitForReady */ false);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
Assert.assertEquals(TARGET_FILTERS,
@@ -654,7 +656,7 @@
public void broadcastRemoved_notVisibleDoesNotReceive() throws Exception {
final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS,
/* targetUid */ INVALID_UID, /* intentExtra */ null,
- Constants.ACTION_AWAIT_PACKAGE_REMOVED);
+ Constants.ACTION_AWAIT_PACKAGE_REMOVED, /* waitForReady */ false);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
result.await();
@@ -668,7 +670,7 @@
public void broadcastRemoved_visibleReceives() throws Exception {
final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS,
/* targetUid */ INVALID_UID, /* intentExtra */ null,
- Constants.ACTION_AWAIT_PACKAGE_REMOVED);
+ Constants.ACTION_AWAIT_PACKAGE_REMOVED, /* waitForReady */ false);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
Assert.assertEquals(TARGET_FILTERS,
@@ -679,6 +681,27 @@
}
@Test
+ public void broadcastSuspended_visibleReceives() throws Exception {
+ assertBroadcastSuspendedVisible(QUERIES_PACKAGE,
+ Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER),
+ Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER));
+ }
+
+ @Test
+ public void broadcastSuspended_notVisibleDoesNotReceive() throws Exception {
+ assertBroadcastSuspendedVisible(QUERIES_NOTHING,
+ Arrays.asList(),
+ Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER));
+ }
+
+ @Test
+ public void broadcastSuspended_visibleReceivesAndNotVisibleDoesNotReceive() throws Exception {
+ assertBroadcastSuspendedVisible(QUERIES_ACTIVITY_ACTION,
+ Arrays.asList(TARGET_FILTERS),
+ Arrays.asList(TARGET_NO_API, TARGET_FILTERS));
+ }
+
+ @Test
public void queriesResolver_grantsVisibilityToProvider() throws Exception {
assertNotVisible(QUERIES_NOTHING_PROVIDER, QUERIES_NOTHING_PERM);
@@ -796,6 +819,24 @@
packageNames, not(hasItemInArray(targetPackageName)));
}
+ private void assertBroadcastSuspendedVisible(String sourcePackageName,
+ List<String> expectedVisiblePackages, List<String> packagesToSuspend)
+ throws Exception {
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_PACKAGES, packagesToSuspend.toArray(new String[] {}));
+ final Result result = sendCommand(sourcePackageName, /* targetPackageName */ null,
+ /* targetUid */ INVALID_UID, extras, Constants.ACTION_AWAIT_PACKAGES_SUSPENDED,
+ /* waitForReady */ true);
+ try {
+ setPackagesSuspended(true, packagesToSuspend);
+ final String[] suspendedPackages = result.await().getStringArray(Intent.EXTRA_PACKAGES);
+ assertThat(suspendedPackages, arrayContainingInAnyOrder(
+ expectedVisiblePackages.toArray()));
+ } finally {
+ setPackagesSuspended(false, packagesToSuspend);
+ }
+ }
+
private PackageInfo getPackageInfo(String sourcePackageName, String targetPackageName)
throws Exception {
Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
@@ -935,12 +976,24 @@
.toArray(String[]::new);
}
+ private void setPackagesSuspended(boolean suspend, List<String> packages) {
+ final StringBuilder cmd = new StringBuilder("pm ");
+ if (suspend) {
+ cmd.append("suspend");
+ } else {
+ cmd.append("unsuspend");
+ }
+ packages.stream().forEach(p -> cmd.append(" ").append(p));
+ runShellCommand(cmd.toString());
+ }
+
interface Result {
Bundle await() throws Exception;
}
private Result sendCommand(String sourcePackageName, @Nullable String targetPackageName,
- int targetUid, @Nullable Parcelable intentExtra, String action) {
+ int targetUid, @Nullable Parcelable intentExtra, String action, boolean waitForReady)
+ throws Exception {
final Intent intent = new Intent(action)
.setComponent(new ComponentName(sourcePackageName, ACTIVITY_CLASS_TEST))
// data uri unique to each activity start to ensure actual launch and not just
@@ -972,7 +1025,11 @@
},
sResponseHandler);
intent.putExtra(EXTRA_REMOTE_CALLBACK, callback);
- InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+ if (waitForReady) {
+ startAndWaitForCommandReady(intent);
+ } else {
+ InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+ }
return () -> {
if (!latch.block(TimeUnit.SECONDS.toMillis(10))) {
throw new TimeoutException(
@@ -986,11 +1043,23 @@
};
}
+ private void startAndWaitForCommandReady(Intent intent) throws Exception {
+ final ConditionVariable latchForReady = new ConditionVariable();
+ final RemoteCallback readyCallback = new RemoteCallback(bundle -> latchForReady.open(),
+ sResponseHandler);
+ intent.putExtra(EXTRA_REMOTE_READY_CALLBACK, readyCallback);
+ InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+ if (!latchForReady.block(TimeUnit.SECONDS.toMillis(10))) {
+ throw new TimeoutException(
+ "Latch timed out while awiating a response from command " + intent.getAction());
+ }
+ }
+
private Bundle sendCommandBlocking(String sourcePackageName, @Nullable String targetPackageName,
@Nullable Parcelable intentExtra, String action)
throws Exception {
final Result result = sendCommand(sourcePackageName, targetPackageName,
- /* targetUid */ INVALID_UID, intentExtra, action);
+ /* targetUid */ INVALID_UID, intentExtra, action, /* waitForReady */ false);
return result.await();
}
@@ -998,7 +1067,7 @@
@Nullable Parcelable intentExtra, String action)
throws Exception {
final Result result = sendCommand(sourcePackageName, /* targetPackageName */ null,
- targetUid, intentExtra, action);
+ targetUid, intentExtra, action, /* waitForReady */ false);
return result.await();
}
}
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 dae5d3a..6c4fcc6 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -28,6 +28,7 @@
import android.app.AppOpsManager.MODE_DEFAULT
import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.MODE_IGNORED
+import android.app.AppOpsManager.OPSTR_PICTURE_IN_PICTURE
import android.app.AppOpsManager.OPSTR_READ_CALENDAR
import android.app.AppOpsManager.OPSTR_RECORD_AUDIO
import android.app.AppOpsManager.OPSTR_WIFI_SCAN
@@ -510,33 +511,33 @@
fun testNonHistoricalStatePersistence() {
// Put a package and uid level data
runWithShellPermissionIdentity {
- mAppOps.setMode(OPSTR_RECORD_AUDIO, Process.myUid(),
+ mAppOps.setMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(),
mOpPackageName, MODE_IGNORED)
- mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(), MODE_ERRORED)
+ mAppOps.setUidMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(), MODE_ERRORED)
// Write the data to disk and read it
mAppOps.reloadNonHistoricalState()
}
// Verify the uid state is preserved
- assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_RECORD_AUDIO,
+ assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_PICTURE_IN_PICTURE,
Process.myUid(), mOpPackageName), MODE_ERRORED)
runWithShellPermissionIdentity {
// Clear the uid state
- mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(),
- AppOpsManager.opToDefaultMode(OPSTR_RECORD_AUDIO))
+ mAppOps.setUidMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(),
+ AppOpsManager.opToDefaultMode(OPSTR_PICTURE_IN_PICTURE))
}
// Verify the package state is preserved
- assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_RECORD_AUDIO,
+ assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_PICTURE_IN_PICTURE,
Process.myUid(), mOpPackageName), MODE_IGNORED)
runWithShellPermissionIdentity {
// Clear the uid state
- val defaultMode = AppOpsManager.opToDefaultMode(OPSTR_RECORD_AUDIO)
- mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(), defaultMode)
- mAppOps.setMode(OPSTR_RECORD_AUDIO, Process.myUid(),
+ val defaultMode = AppOpsManager.opToDefaultMode(OPSTR_PICTURE_IN_PICTURE)
+ mAppOps.setUidMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(), defaultMode)
+ mAppOps.setMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(),
mOpPackageName, defaultMode)
}
}
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
index 9e14260..05ecb78 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -809,6 +809,24 @@
doTestInstallSysTrace(TEST_APK_PROFILEABLE);
}
+ @LargeTest
+ @Test
+ public void testInstallSysTraceNoReadlogs() throws Exception {
+ setSystemProperty("debug.incremental.enforce_readlogs_max_interval_for_system_dataloaders",
+ "1");
+ setSystemProperty("debug.incremental.readlogs_max_interval_sec", "0");
+
+ final int atraceDumpIterations = 30;
+ final int atraceDumpDelayMs = 100;
+ final String expected = "|page_read:";
+
+ // We don't expect any readlogs with 0sec interval.
+ assertFalse(
+ "Page reads (" + expected + ") were found in atrace dump",
+ checkSysTraceForSubstring(TEST_APK, expected, atraceDumpIterations,
+ atraceDumpDelayMs));
+ }
+
private boolean checkSysTraceForSubstring(String testApk, final String expected,
int atraceDumpIterations, int atraceDumpDelayMs) throws Exception {
final int installIterations = 3;
@@ -1094,6 +1112,9 @@
assertEquals(null, getSplits(TEST_APP_PACKAGE));
setDeviceProperty("incfs_default_timeouts", null);
setDeviceProperty("known_digesters_list", null);
+ setSystemProperty("debug.incremental.enforce_readlogs_max_interval_for_system_dataloaders",
+ "0");
+ setSystemProperty("debug.incremental.readlogs_max_interval_sec", "10000");
IoUtils.closeQuietly(mSession);
mSession = null;
}
@@ -1108,5 +1129,9 @@
}
}
+ private void setSystemProperty(String name, String value) throws Exception {
+ executeShellCommand("setprop " + name + " " + value);
+ }
+
}
diff --git a/tests/tests/gamemanager/AndroidManifest.xml b/tests/tests/gamemanager/AndroidManifest.xml
index 2433674..ca874b9 100644
--- a/tests/tests/gamemanager/AndroidManifest.xml
+++ b/tests/tests/gamemanager/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.gamemanager.cts">
- <application>
+ <application android:appCategory="game">
<uses-library android:name="android.test.runner"/>
<activity android:name=".GameManagerCtsActivity"
android:label="GameManagerCtsActivity"
diff --git a/tests/tests/gamemanager/src/android/settings/cts/GameManagerCtsActivity.java b/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerCtsActivity.java
similarity index 100%
rename from tests/tests/gamemanager/src/android/settings/cts/GameManagerCtsActivity.java
rename to tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerCtsActivity.java
diff --git a/tests/tests/gamemanager/src/android/settings/cts/GameManagerTest.java b/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
similarity index 100%
rename from tests/tests/gamemanager/src/android/settings/cts/GameManagerTest.java
rename to tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
index 5718aeb..d9dded5 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
@@ -31,7 +31,6 @@
import androidx.test.runner.AndroidJUnit4;
import org.junit.Assert;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,7 +41,8 @@
@RunWith(AndroidJUnit4.class)
public class SystemPalette {
- private static final double MAX_CHROMA_DISTANCE = 0.1;
+ // Hue goes from 0 to 360
+ private static final int MAX_HUE_DISTANCE = 30;
@Test
public void testShades0and1000() {
@@ -64,8 +64,6 @@
}
}
- // b/183457641
- @Ignore
@Test
public void testAllColorsBelongToSameFamily() {
final Context context = getInstrumentation().getTargetContext();
@@ -74,35 +72,33 @@
getAllNeutral1Colors(context), getAllNeutral2Colors(context));
for (int[] palette : allPalettes) {
- for (int i = 2; i < palette.length; i++) {
+ for (int i = 2; i < palette.length - 1; i++) {
assertWithMessage("Color " + Integer.toHexString((palette[i - 1]))
- + " has different chroma compared to " + Integer.toHexString(palette[i]))
- .that(similarChroma(palette[i - 1], palette[i])).isTrue();
+ + " has different chroma compared to " + Integer.toHexString(palette[i])
+ + " for palette: " + Arrays.toString(palette))
+ .that(similarHue(palette[i - 1], palette[i])).isTrue();
}
}
}
/**
- * Compare if color A and B have similar color, in LAB space.
+ * Compare if color A and B have similar hue, in HSL space.
*
* @param colorA Color 1
* @param colorB Color 2
* @return True when colors have similar chroma.
*/
- private boolean similarChroma(@ColorInt int colorA, @ColorInt int colorB) {
- final double[] labColor1 = new double[3];
- final double[] labColor2 = new double[3];
+ private boolean similarHue(@ColorInt int colorA, @ColorInt int colorB) {
+ final float[] hslColor1 = new float[3];
+ final float[] hslColor2 = new float[3];
- ColorUtils.RGBToLAB(Color.red(colorA), Color.green(colorA), Color.blue(colorA), labColor1);
- ColorUtils.RGBToLAB(Color.red(colorB), Color.green(colorB), Color.blue(colorB), labColor2);
+ ColorUtils.RGBToHSL(Color.red(colorA), Color.green(colorA), Color.blue(colorA), hslColor1);
+ ColorUtils.RGBToHSL(Color.red(colorB), Color.green(colorB), Color.blue(colorB), hslColor2);
- labColor1[1] = (labColor1[1] + 128.0) / 256;
- labColor1[2] = (labColor1[2] + 128.0) / 256;
- labColor2[1] = (labColor2[1] + 128.0) / 256;
- labColor2[2] = (labColor2[2] + 128.0) / 256;
+ float hue1 = Math.max(hslColor1[0], hslColor2[0]);
+ float hue2 = Math.min(hslColor1[0], hslColor2[0]);
- return (Math.abs(labColor1[1] - labColor2[1]) < MAX_CHROMA_DISTANCE)
- && (Math.abs(labColor1[2] - labColor2[2]) < MAX_CHROMA_DISTANCE);
+ return hue1 - hue2 < MAX_HUE_DISTANCE;
}
@Test
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
index 9fa5fd5..5a20570 100644
--- a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
+++ b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
@@ -582,4 +582,20 @@
assertEquals(50.0f, r.getLineWidth(1), 0.0f);
assertEquals(70.0f, r.getLineWidth(2), 0.0f);
}
+
+ @Test
+ public void testLineBreak_ZeroWidthTab() {
+ final String text = "Hi, \tWorld.";
+ final LineBreaker lb = new LineBreaker.Builder()
+ .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+ .build();
+ final ParagraphConstraints c = new ParagraphConstraints();
+ c.setWidth(70.0f);
+ c.setTabStops(null, 0);
+ Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+ .appendStyleRun(sPaint, text.length(), false)
+ .build(), c, 0);
+ float lw = r.getLineWidth(0);
+ assertFalse(Float.isNaN(lw));
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
index c545beb..9f46d39 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
@@ -15,6 +15,8 @@
*/
package android.media.cts;
+import static android.Manifest.permission.MEDIA_CONTENT_CONTROL;
+
import android.platform.test.annotations.AppModeFull;
import com.android.compatibility.common.util.SystemUtil;
@@ -36,7 +38,6 @@
import android.os.Process;
import android.test.InstrumentationTestCase;
import android.test.UiThreadTest;
-import android.util.Log;
import android.view.KeyEvent;
import java.io.IOException;
@@ -44,6 +45,7 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@AppModeFull(reason = "TODO: evaluate and port to instant")
@@ -63,6 +65,7 @@
@Override
protected void tearDown() throws Exception {
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
super.tearDown();
}
@@ -76,6 +79,15 @@
// TODO enable a notification listener, test again, disable, test again
}
+ public void testGetMediaKeyEventSession() throws Exception {
+ try {
+ mSessionManager.getMediaKeyEventSession();
+ fail("Expected security exception for call to getMediaKeyEventSession");
+ } catch (SecurityException ex) {
+ // Expected
+ }
+ }
+
@UiThreadTest
public void testAddOnActiveSessionsListener() throws Exception {
try {
@@ -528,6 +540,23 @@
}
}
+ private class MediaKeyEventSessionListener
+ implements MediaSessionManager.OnMediaKeyEventSessionChangedListener {
+ final CountDownLatch mCountDownLatch;
+ MediaSession.Token mSessionToken;
+
+ MediaKeyEventSessionListener() {
+ mCountDownLatch = new CountDownLatch(1);
+ }
+
+ @Override
+ public void onMediaKeyEventSessionChanged(String packageName,
+ MediaSession.Token sessionToken) {
+ mCountDownLatch.countDown();
+ mSessionToken = sessionToken;
+ }
+ }
+
private static class HandlerExecutor implements Executor {
private final Handler mHandler;
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
index 36d52e6..af70d69 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import android.app.Instrumentation;
import android.app.Notification;
@@ -77,6 +78,10 @@
private Context mContext;
private UiAutomation mUi;
+ private boolean isWatch() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+
@Before
public void setUp() throws IOException {
mUi = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -556,6 +561,7 @@
if (isTelevision()) {
return;
}
+ assumeFalse("Status bar service not supported", isWatch());
setUpListeners();
turnScreenOn();
mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
@@ -587,6 +593,7 @@
if (isTelevision()) {
return;
}
+ assumeFalse("Status bar service not supported", isWatch());
setUpListeners();
turnScreenOn();
mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
@@ -612,6 +619,7 @@
if (isTelevision()) {
return;
}
+ assumeFalse("Status bar service not supported", isWatch());
setUpListeners();
turnScreenOn();
mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 0acd373..4969752 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1987,6 +1987,12 @@
<permission android:name="android.permission.BLUETOOTH_STACK"
android:protectionLevel="signature" />
+ <!-- Allows uhid write access for creating virtual input devices
+ @hide
+ -->
+ <permission android:name="android.permission.VIRTUAL_INPUT_DEVICE"
+ android:protectionLevel="signature" />
+
<!-- Allows applications to perform I/O operations over NFC.
<p>Protection level: normal
-->
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0394.java b/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
index d3278ee..6d504f6 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
@@ -16,12 +16,12 @@
package android.security.cts;
-import static org.junit.Assert.assertFalse;
-
import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import androidx.test.filters.RequiresDevice;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import static org.junit.Assert.assertFalse;
@RunWith(AndroidJUnit4.class)
public class CVE_2021_0394 {
@@ -34,6 +34,9 @@
*/
@SecurityTest(minPatchLevel = "2021-03")
@Test
+ @RequiresDevice
+ // emulators always have checkJNI enabled which causes the test
+ // to abort the VM while passing invalid input to NewStringUTF
public void testPocCVE_2021_0394() throws Exception {
assertFalse(poc());
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index 21abe14..4a550fe 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -40,6 +40,7 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.Uri;
import android.os.Looper;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
@@ -89,6 +90,11 @@
private static final String TAG = "SubscriptionManagerTest";
private static final String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
private SubscriptionManager mSm;
+ private static final List<Uri> CONTACTS = new ArrayList<>();
+ static {
+ CONTACTS.add(Uri.fromParts("tel", "+16505551212", null));
+ CONTACTS.add(Uri.fromParts("tel", "+16505552323", null));
+ }
private int mSubId;
private String mPackageName;
@@ -956,6 +962,17 @@
uiAutomation.dropShellPermissionIdentity();
}
+ @Test
+ public void testSetAndGetD2DSharingContacts() {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity(MODIFY_PHONE_STATE);
+ List<Uri> originalD2DSharingContacts = mSm.getDeviceToDeviceStatusSharingContacts(mSubId);
+ mSm.setDeviceToDeviceStatusSharingContacts(CONTACTS, mSubId);
+ assertEquals(CONTACTS, mSm.getDeviceToDeviceStatusSharingContacts(mSubId));
+ mSm.setDeviceToDeviceStatusSharingContacts(originalD2DSharingContacts, mSubId);
+ uiAutomation.dropShellPermissionIdentity();
+ }
+
@Nullable
private PersistableBundle getBundleFromBackupData(byte[] data) {
try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
index 9c8aba4..d3861c6 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
@@ -1134,8 +1134,8 @@
capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
int networkResp = 200;
String reason = "";
- listener.onPublish();
cb.onNetworkResponse(networkResp, reason);
+ listener.onPublish();
});
// Unregister the publish state callback
@@ -1278,8 +1278,8 @@
pidfQueue.offer(pidfXml);
int networkResp = 200;
String reason = "";
- listener.onPublish();
cb.onNetworkResponse(networkResp, reason);
+ listener.onPublish();
});
LinkedBlockingQueue<ImsRegistrationAttributes> mQueue = new LinkedBlockingQueue<>();
@@ -1409,8 +1409,8 @@
capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
int networkResp = 200;
String reason = "OK";
- listener.onPublish();
cb.onNetworkResponse(networkResp, reason);
+ listener.onPublish();
});
// IMS registers
@@ -1449,8 +1449,8 @@
String reason = "";
int reasonHeaderCause = 400;
String reasonHeaderText = "Bad Request";
- listener.onPublish();
cb.onNetworkResponse(networkResp, reason, reasonHeaderCause, reasonHeaderText);
+ listener.onPublish();
});
// ImsService triggers to notify framework publish device's capabilities.
@@ -1504,8 +1504,8 @@
capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
int networkResp = 200;
String reason = "OK";
- listener.onPublish();
cb.onNetworkResponse(networkResp, reason);
+ listener.onPublish();
});
// Register the callback to listen to the publish state changed
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
index 95935ac..84916bc 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
@@ -83,6 +83,9 @@
private static final String FILE_TRANSFER_HTTP_TAG =
"+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gppapplication.ims.iari.rcs.fthttp\"";
+ private static final String[] DEFAULT_FEATURE_TAGS = {
+ ONE_TO_ONE_CHAT_TAG, GROUP_CHAT_TAG, FILE_TRANSFER_HTTP_TAG};
+
private static class CarrierConfigReceiver extends BroadcastReceiver {
private CountDownLatch mLatch = new CountDownLatch(1);
private final int mSubId;
@@ -149,6 +152,8 @@
// APIs.
sServiceConnector.setDeviceSingleRegistrationEnabled(true);
}
+
+ setFeatureTagsCarrierAllowed(DEFAULT_FEATURE_TAGS);
}
@AfterClass
@@ -871,6 +876,41 @@
assertTrue(Arrays.equals(decodedMsg.getContent(), m.getContent()));
}
+ @Test
+ public void testFeatureTagDeniedByCarrierConfig() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ setFeatureTagsCarrierAllowed(new String[]{FILE_TRANSFER_HTTP_TAG});
+ assertTrue(sServiceConnector.setDefaultSmsApp());
+ connectTestImsServiceWithSipTransportAndConfig();
+ TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+ TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
+ SipDelegateManager manager = getSipDelegateManager();
+ DelegateRequest request = getDefaultRequest();
+ TestSipDelegateConnection delegateConn = new TestSipDelegateConnection(request);
+ Set<String> deniedTags = new ArraySet<>(request.getFeatureTags());
+ deniedTags.remove(FILE_TRANSFER_HTTP_TAG);
+
+ TestSipDelegate delegate = createSipDelegateConnectionAndVerify(manager, delegateConn,
+ transportImpl, getDeniedTagsForReason(deniedTags,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED), 0);
+ assertNotNull(delegate);
+
+ Set<String> registeredTags = new ArraySet<>(
+ Arrays.asList(new String[]{FILE_TRANSFER_HTTP_TAG}));
+ delegateConn.setOperationCountDownLatch(1);
+ DelegateRegistrationState s = getRegisteredRegistrationState(registeredTags);
+ delegate.notifyImsRegistrationUpdate(s);
+ delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
+ delegateConn.verifyRegistrationStateEquals(s);
+ destroySipDelegateAndVerify(manager, transportImpl, delegateConn, delegate, registeredTags);
+ assertEquals("There should be no more delegates", 0,
+ transportImpl.getDelegates().size());
+ setFeatureTagsCarrierAllowed(getDefaultRequest().getFeatureTags().toArray(new String[0]));
+ }
+
private SipMessage generateSipMessage(String str) {
String crlf = "\r\n";
String[] components = str.split(crlf);
@@ -1151,17 +1191,14 @@
}
private DelegateRequest getDefaultRequest() {
- ArraySet<String> features = new ArraySet<>(3);
- features.add(TestSipTransport.ONE_TO_ONE_CHAT_TAG);
- features.add(TestSipTransport.GROUP_CHAT_TAG);
- features.add(TestSipTransport.FILE_TRANSFER_HTTP_TAG);
+ ArraySet<String> features = new ArraySet<>(Arrays.asList(DEFAULT_FEATURE_TAGS));
return new DelegateRequest(features);
}
private DelegateRequest getChatOnlyRequest() {
ArraySet<String> features = new ArraySet<>(3);
- features.add(TestSipTransport.ONE_TO_ONE_CHAT_TAG);
- features.add(TestSipTransport.GROUP_CHAT_TAG);
+ features.add(ONE_TO_ONE_CHAT_TAG);
+ features.add(GROUP_CHAT_TAG);
return new DelegateRequest(features);
}
@@ -1172,12 +1209,18 @@
.addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
.build();
}
+
private ImsFeatureConfiguration getConfigForRcs() {
return new ImsFeatureConfiguration.Builder()
.addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
.build();
}
+ private Set<FeatureTagState> getDeniedTagsForReason(Set<String> deniedTags, int reason) {
+ return deniedTags.stream().map(t -> new FeatureTagState(t, reason))
+ .collect(Collectors.toSet());
+ }
+
private static void overrideCarrierConfig(PersistableBundle bundle) throws Exception {
CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getInstrumentation()
.getContext().getSystemService(CarrierConfigManager.class);
@@ -1187,6 +1230,13 @@
sReceiver.waitForCarrierConfigChanged();
}
+ private static void setFeatureTagsCarrierAllowed(String[] tags) throws Exception {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putStringArray(CarrierConfigManager.Ims.KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY,
+ tags);
+ overrideCarrierConfig(bundle);
+ }
+
private SipDelegateManager getSipDelegateManager() {
ImsManager imsManager = getContext().getSystemService(ImsManager.class);
assertNotNull(imsManager);
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java
index 8fab865..7318e8b 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java
@@ -17,6 +17,8 @@
package android.telephonyprovider.cts;
import static android.provider.Telephony.ServiceStateTable.DATA_NETWORK_TYPE;
+import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.DUPLEX_MODE;
import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
@@ -215,6 +217,99 @@
}
/**
+ * Verifies that the duplex mode is valid and matches ServiceState#getDuplexMode().
+ */
+ @Test
+ public void testGetDuplexMode_query() {
+ try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
+ new String[]{DUPLEX_MODE}, null, null)) {
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+
+ int duplexMode = cursor.getInt(cursor.getColumnIndex(DUPLEX_MODE));
+ assertThat(duplexMode).isEqualTo(mTelephonyManager.getServiceState().getDuplexMode());
+ }
+ }
+
+ /**
+ * Verifies that even we have duplex mode change, the observer should not receive the
+ * notification (duplex mode is a poll-only field).
+ */
+ @Test
+ public void testGetDuplexMode_noChangeObserved() throws Exception {
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+
+ ServiceState newSS = new ServiceState();
+ newSS.setStateOutOfService();
+
+ // Add NRI to trigger SS with duplex mode updated
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+ .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .build();
+ newSS.addNetworkRegistrationInfo(nri);
+ newSS.setChannelNumber(65536); // EutranBand.BAND_65, DUPLEX_MODE_FDD
+
+ verifyNotificationObservedWhenFieldChanged(
+ DUPLEX_MODE, oldSS, newSS, false /*expectChange*/);
+ }
+
+ /**
+ * Verifies that the data reg state is valid and matches ServiceState#getDataRegState()
+ */
+ @Test
+ public void testGetDataRegState_query() {
+ try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
+ new String[]{DATA_REG_STATE}, null, null)) {
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+
+ int dataRegState = cursor.getInt(cursor.getColumnIndex(DATA_REG_STATE));
+ assertThat(dataRegState).isEqualTo(
+ mTelephonyManager.getServiceState().getDataRegState());
+ }
+ }
+
+ /**
+ * Verifies that when data reg state did not change, the observer should not receive the
+ * notification.
+ */
+ @Test
+ public void testGetDataRegState_noChangeObserved() throws Exception {
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+ oldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+
+ ServiceState copyOfOldSS = new ServiceState();
+ copyOfOldSS.setStateOutOfService();
+ copyOfOldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+ // set additional fields which is not related to data reg state
+ copyOfOldSS.setChannelNumber(65536);
+ copyOfOldSS.setIsManualSelection(true);
+
+ verifyNotificationObservedWhenFieldChanged(
+ DATA_REG_STATE, oldSS, copyOfOldSS, false /*expectChange*/);
+ }
+
+ /**
+ * Verifies that when data reg state changed, the observer should receive the notification.
+ */
+ @Test
+ public void testGetDataRegState_changeObserved() throws Exception {
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+
+ ServiceState newSS = new ServiceState();
+ newSS.setStateOutOfService();
+ newSS.setStateOff();
+
+ verifyNotificationObservedWhenFieldChanged(
+ DATA_REG_STATE, oldSS, newSS, true /*expectChange*/);
+ }
+
+ /**
* Insert new ServiceState over the old ServiceState and expect the observer receiving the
* notification over the observed field change.
*/
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
index 81e0a01..71d6835 100644
--- a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
@@ -232,14 +232,14 @@
/** Test implementation of VcnStatusCallback for verification purposes. */
private static class TestVcnStatusCallback extends VcnManager.VcnStatusCallback {
- private final CompletableFuture<Integer> mFutureOnVcnStatusChanged =
+ private final CompletableFuture<Integer> mFutureOnStatusChanged =
new CompletableFuture<>();
private final CompletableFuture<GatewayConnectionError> mFutureOnGatewayConnectionError =
new CompletableFuture<>();
@Override
- public void onVcnStatusChanged(int statusCode) {
- mFutureOnVcnStatusChanged.complete(statusCode);
+ public void onStatusChanged(int statusCode) {
+ mFutureOnStatusChanged.complete(statusCode);
}
@Override
@@ -249,8 +249,8 @@
new GatewayConnectionError(networkCapabilities, errorCode, detail));
}
- public int awaitOnVcnStatusChanged() throws Exception {
- return mFutureOnVcnStatusChanged.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ public int awaitOnStatusChanged() throws Exception {
+ return mFutureOnStatusChanged.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public GatewayConnectionError awaitOnGatewayConnectionError() throws Exception {
@@ -289,7 +289,7 @@
try {
registerVcnStatusCallbackForSubId(callback, subId);
- final int statusCode = callback.awaitOnVcnStatusChanged();
+ final int statusCode = callback.awaitOnStatusChanged();
assertEquals(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED, statusCode);
} finally {
mVcnManager.unregisterVcnStatusCallback(callback);
diff --git a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
index 6ee1441..c6476bf 100644
--- a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
+++ b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
@@ -209,8 +209,11 @@
assertTrue(intended_vsync < now);
assertTrue(vsync < now);
assertTrue(vsync >= intended_vsync);
+
+ // swapBuffers and gpuDuration may happen in parallel, so instead of counting both we need
+ // to take the longer of the two.
assertTrue(totalDuration >= unknownDelay + input + animation + layoutMeasure + draw + sync
- + commandIssue + swapBuffers + gpuDuration);
+ + commandIssue + Math.max(gpuDuration, swapBuffers));
// This is the only boolean metric so far
final long firstDrawFrameMetric = frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME);
diff --git a/tests/tests/voiceinteraction/common/Android.bp b/tests/tests/voiceinteraction/common/Android.bp
index 1d8d77e..e9052de 100644
--- a/tests/tests/voiceinteraction/common/Android.bp
+++ b/tests/tests/voiceinteraction/common/Android.bp
@@ -18,8 +18,6 @@
java_library {
name: "CtsVoiceInteractionCommon",
- srcs: ["src/**/*.java",
- "src/**/*.aidl"
- ],
+ srcs: ["src/**/*.java"],
sdk_version: "current",
}
diff --git a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/ICtsHotwordDetectionServiceCallback.aidl b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/ICtsHotwordDetectionServiceCallback.aidl
deleted file mode 100644
index 3955dc0..0000000
--- a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/ICtsHotwordDetectionServiceCallback.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.voiceinteraction.common;
-
-/**
- * Callback for returning the test result from the HotwordDetectionService.
- */
-oneway interface ICtsHotwordDetectionServiceCallback {
- /**
- * Called to return the test result.
- */
- void onTestResult(int result);
-}
diff --git a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
index 553cbb4..dd60884 100644
--- a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
+++ b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
@@ -139,7 +139,6 @@
public static final String KEY_SERVICE_TYPE = "serviceType";
public static final String KEY_TEST_EVENT = "testEvent";
public static final String KEY_TEST_RESULT = "testResult";
- public static final String KEY_TEST_FAKE_BINDER = "testFakeBinder";
public static final String toBundleString(Bundle bundle) {
if (bundle == null) {
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
index 4b6bb95..29b35b7 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
@@ -19,14 +19,12 @@
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.content.Intent;
-import android.os.Bundle;
-import android.os.RemoteException;
+import android.os.PersistableBundle;
import android.os.SharedMemory;
import android.service.voice.AlwaysOnHotwordDetector;
import android.service.voice.VoiceInteractionService;
import android.system.ErrnoException;
import android.util.Log;
-import android.voiceinteraction.common.ICtsHotwordDetectionServiceCallback;
import android.voiceinteraction.common.Utils;
import java.nio.ByteBuffer;
@@ -39,6 +37,8 @@
// TODO: (b/182236586) Refactor the voice interaction service logic
static final String TAG = "BasicVoiceInteractionService";
+ public static String KEY_FAKE_DATA = "fakeData";
+ public static String VALUE_FAKE_DATA = "fakeData";
public static byte[] FAKE_BYTE_ARRAY_DATA = new byte[] {1, 2, 3};
private boolean mReady = false;
@@ -76,7 +76,7 @@
try {
createAlwaysOnHotwordDetector(/* keyphrase */ "Hello Google",
Locale.forLanguageTag("en-US"),
- createFakeBundleData(),
+ createFakePersistableBundleData(),
createFakeSharedMemoryData(),
new AlwaysOnHotwordDetector.Callback() {
@Override
@@ -137,21 +137,10 @@
}
}
- private Bundle createFakeBundleData() {
+ private PersistableBundle createFakePersistableBundleData() {
// TODO : Add more data for testing
- Bundle bundle = new Bundle();
- bundle.putByteArray("fakeData", FAKE_BYTE_ARRAY_DATA);
- ICtsHotwordDetectionServiceCallback callback =
- new ICtsHotwordDetectionServiceCallback.Stub() {
- @Override
- public void onTestResult(int result) throws RemoteException {
- Log.d(TAG, "onTestResult result = " + result);
- broadcastIntentWithResult(
- Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- result);
- }
- };
- bundle.putBinder(Utils.KEY_TEST_FAKE_BINDER, callback.asBinder());
- return bundle;
+ PersistableBundle persistableBundle = new PersistableBundle();
+ persistableBundle.putString(KEY_FAKE_DATA, VALUE_FAKE_DATA);
+ return persistableBundle;
}
}
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java
index 2ebdc4e..9bfe321 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java
@@ -17,16 +17,13 @@
package android.voiceinteraction.service;
import android.media.AudioFormat;
-import android.os.Bundle;
-import android.os.IBinder;
import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
+import android.os.PersistableBundle;
import android.os.SharedMemory;
import android.service.voice.HotwordDetectionService;
import android.system.ErrnoException;
+import android.text.TextUtils;
import android.util.Log;
-import android.voiceinteraction.common.ICtsHotwordDetectionServiceCallback;
-import android.voiceinteraction.common.Utils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -49,29 +46,22 @@
}
@Override
- public void onUpdateState(@Nullable Bundle options, @Nullable SharedMemory sharedMemory) {
+ public void onUpdateState(@Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory) {
Log.d(TAG, "onUpdateState");
- ICtsHotwordDetectionServiceCallback callback = null;
if (options != null) {
- IBinder binder = options.getBinder(Utils.KEY_TEST_FAKE_BINDER);
- callback = ICtsHotwordDetectionServiceCallback.Stub.asInterface(binder);
- }
-
- if (callback == null) {
- Log.w(TAG, "no callback to return the test result");
- return;
+ String fakeData = options.getString(BasicVoiceInteractionService.KEY_FAKE_DATA);
+ if (!TextUtils.equals(fakeData, BasicVoiceInteractionService.VALUE_FAKE_DATA)) {
+ Log.d(TAG, "options : data is not the same");
+ return;
+ }
}
if (sharedMemory != null) {
try {
sharedMemory.mapReadWrite();
- try {
- callback.onTestResult(
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SHARED_MEMORY_NOT_READ_ONLY);
- } catch (RemoteException e) {
- Log.d(TAG, "call onTestResult RemoteException : " + e);
- }
+ Log.d(TAG, "sharedMemory : is not read-only");
return;
} catch (ErrnoException e) {
// For read-only case
@@ -79,11 +69,7 @@
sharedMemory.close();
}
}
- try {
- callback.onTestResult(
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
- } catch (RemoteException e) {
- Log.d(TAG, "call onTestResult RemoteException : " + e);
- }
+ // Report success
+ Log.d(TAG, "onUpdateState success");
}
}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
index 6fe406e..0b11257 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
@@ -26,6 +26,7 @@
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,6 +40,7 @@
static final String TAG = "HotwordDetectionServiceBasicTest";
@Test
+ @Ignore
public void testHotwordDetectionService_validHotwordDetectionComponentName_triggerSuccess()
throws Throwable {
final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
diff --git a/tests/tests/wifi/TEST_MAPPING b/tests/tests/wifi/TEST_MAPPING
index e80f848..7ddc308 100644
--- a/tests/tests/wifi/TEST_MAPPING
+++ b/tests/tests/wifi/TEST_MAPPING
@@ -9,8 +9,7 @@
]
}
],
- // TODO(b/179512200): move to mainline-presubmit once passing
- "mainline-postsubmit": [
+ "mainline-presubmit": [
{
"name": "CtsWifiTestCases[com.google.android.wifi.apex]",
"options": [
diff --git a/tests/translation/AndroidManifest.xml b/tests/translation/AndroidManifest.xml
index d1803bf..3ea50e9 100644
--- a/tests/translation/AndroidManifest.xml
+++ b/tests/translation/AndroidManifest.xml
@@ -37,6 +37,10 @@
<intent-filter>
<action android:name="android.service.translation.TranslationService"/>
</intent-filter>
+ <meta-data
+ android:name="android.translation_service"
+ android:resource="@xml/translation_config">
+ </meta-data>
</service>
<service android:name=".CtsContentCaptureService"
android:label="CtsContentCaptureService"
diff --git a/tests/translation/res/xml/translation_config.xml b/tests/translation/res/xml/translation_config.xml
new file mode 100644
index 0000000..6ab30d5
--- /dev/null
+++ b/tests/translation/res/xml/translation_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<translation-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="android.translation.cts.SimpleActivity">
+</translation-service>
\ No newline at end of file
diff --git a/tests/translation/src/android/translation/cts/TranslationManagerTest.java b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
index a34d99b..38bd34f 100644
--- a/tests/translation/src/android/translation/cts/TranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
@@ -16,9 +16,13 @@
package android.translation.cts;
+import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
import static com.google.common.truth.Truth.assertThat;
+import android.app.Application;
import android.app.Instrumentation;
+import android.app.PendingIntent;
import android.content.pm.PackageManager;
import android.platform.test.annotations.AppModeFull;
import android.util.ArraySet;
@@ -34,8 +38,11 @@
import android.view.translation.Translator;
import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ActivitiesWatcher;
+import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
import com.android.compatibility.common.util.RequiredFeatureRule;
import com.android.compatibility.common.util.RequiredServiceRule;
@@ -77,6 +84,7 @@
private static final String TAG = "BasicTranslationTest";
private CtsTranslationService.ServiceWatcher mServiceWatcher;
+ private ActivitiesWatcher mActivitiesWatcher;
private static Instrumentation sInstrumentation;
private static CtsTranslationService.TranslationReplier sTranslationReplier;
@@ -95,6 +103,10 @@
@After
public void cleanup() {
Helper.resetTemporaryTranslationService();
+ if (mActivitiesWatcher != null) {
+ final Application app = (Application) ApplicationProvider.getApplicationContext();
+ app.unregisterActivityLifecycleCallbacks(mActivitiesWatcher);
+ }
}
@Test
@@ -210,6 +222,28 @@
});
}
+ @Test
+ public void testGetTranslationSettingsActivityIntent() throws Exception{
+ enableCtsTranslationService();
+
+ final TranslationManager manager = sInstrumentation.getContext().getSystemService(
+ TranslationManager.class);
+ final PendingIntent pendingIntent = manager.getTranslationSettingsActivityIntent();
+
+ assertThat(pendingIntent).isNotNull();
+ assertThat(pendingIntent.isImmutable()).isTrue();
+
+ // Start Settings Activity and verify if the expected Activity resumed
+ mActivitiesWatcher = new ActivitiesWatcher(5_000);
+ final Application app = (Application) ApplicationProvider.getApplicationContext();
+ app.registerActivityLifecycleCallbacks(mActivitiesWatcher);
+ final ActivityWatcher watcher = mActivitiesWatcher.watch(SimpleActivity.class);
+
+ pendingIntent.send();
+
+ watcher.waitFor(RESUMED);
+ }
+
protected void enableCtsTranslationService() {
mServiceWatcher = CtsTranslationService.setServiceWatcher();
Helper.setTemporaryTranslationService(CtsTranslationService.SERVICE_NAME);
diff --git a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
index 398c221..1323093 100644
--- a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
@@ -56,4 +56,7 @@
<!-- No AccessibilityService -->
<option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts" />
+ <!-- No Statsd -->
+ <option name="compatibility:exclude-filter" value="CtsStatsdHostTestCases" />
+
</configuration>